BLE Nordic Services
BLE Nordic Services
BLE Nordic Services
tutorial
MartinBL 26 Aug 2015
Before we begin
Scope
Topics that will be covered include:
1. Before we begin
Necessary equipment and software
Necessary prior knowledge
2. Some basic theory
The Generic Attribute Profile (GATT)
Services
Characteristics
Universally Unique ID (UUID)
3. The example
First thing first
Our first service
Advertising
4. Summary
Change Log
2016.03.14: Updated tutorial to use SDK V11.0.0 and the possibility to use both nRF51 and nRF52 kits.
Other kits, dongles and software versions might work as well, but this is what I have used. This tutorial
will not cover how to install and setup the software. Please search the forum if you run into trouble
before posting questions here.
If you run into troubles please browse devzone, look for documentation on our Infocenter, and read
the user guides for your kits. I urge you to post any questions you might have on the forum and not
below the tutorial. This makes it is easier for other users and Nordic employees to see and search for
your question and you will actually most likely get a faster response(!).
“The GATT Profile specifies the structure in which profile data is exchanged. This structure defines
basic elements such as services and characteristics, used in a profile.”
In other words, it is a set of rules describing how to bundle, present and transfer data using BLE. Read
the Bluetooth Core Specification v4.2, Vol. 3, Part G for more information. It might be a little heavy
reading, but it will certainly pay off in the end.
Services
The Bluetooth Core Specification defines a service like this:
“A service is a collection of data and associated behaviors to accomplish a particular function or
feature. [...] A service definition may contain […] mandatory characteristics and optional
characteristics.”
In other words, a service is a collection of information, like e.g. values of sensors. Bluetooth Special
Interest Group (Bluetooth SIG) has predefined certain services. For example they have defined a service
called Heart Rate service. The reason why they have done this is to make it easier for developers to
make apps and firmware compatible with the standard Heart Rate service. However, this does not
mean that you can't make your own heart rate sensor based on your own ideas and service structures.
Sometimes people mistakenly assumes that since Bluetooth SIG has predefined some services they can
only make applications abiding by these definitions. This is not the case. It is no problem to make
custom services for your custom applications.
Characteristics
The Bluetooth Core Specification defines a characteristic like this:
“A characteristic is a value used in a service along with properties and configuration information
about how the value is accessed and information about how the value is displayed or represented.”
In other words, the characteristic is where the actual values and information is presented. Security
parameters, units and other metadata concerning the information are also encapsulated in the
characteristics.
An analogy might be a storage room filled with filing cabinets and each filing cabinet has a number of
drawers. The GATT profile in this analogy is the storage room. The cabinets are the services, and the
drawers are characteristics holding various information. Some of the drawers might also have locks on
them restricting the access to its information.
Imagine a heart rate monitor watch for example. Watches like this typically use at least two services:
1. A Heart rate service. It encapsulates three characteristics:
a. A mandatory Heart Rate Measurement characteristic holding the heart rate value.
b. An optional Body Sensor Location characteristic.
c. A conditional Heart Rate Control Point characteristic.
2. A Battery service:
a. Mandatory Battery level characteristic.
Now why bother with this? Why not just send whatever data you need directly without the fuzz of
bundling it in characteristics and services? The reasons are flexibility, efficiency, cross platform
compatibilities and ease of implementation. When iPhones, Android tablets or Windows laptops
discover a device advertising a heart rate service they can be 100% sure to find at least the heart rate
measurement characteristic and the characteristic is guaranteed to be presented in a standardized way.
If a device contains more than one service you are free to pick and choose the services and
characteristics you like. By bundling information this way devices can quickly discover what information
is anrfvailable and communicate only what is strictly needed and thereby save precious time and
energy. Remember that BLE is all about low energy.
To continue the analogy: the storage room is located in a small business office and has two filing
cabinets. The first cabinet is used by the accountants. The drawers contain files with financial details of
the business, sorted by date. The drawers are locked and only the accountants and the upper
management have access to them. The second cabinet is used by Human Resources and contains
records over the employees, sorted in alphabetical order. These drawers are also locked and only HR
and upper management have access to them. Everyone in the business knows where the storage room
is and what it is for, but only some people have access to it and use it. It ensures efficiency, security and
order.
The first type is a short 16-bit UUID. The predefined Heart rate service, e.g., has the UUID 0x180D and
one of its enclosed characteristics, the Heart Rate Measurement characteristic, has the UUID 0x2A37.
The 16-bit UUID is energy and memory efficient, but since it only provides a relatively limited number of
unique IDs there is a rule; you can only transmit the predefined Bluetooth SIG UUIDs directly over the
air. Hence there is a need for a second type of UUID so you can transmit your own custom UUIDs as
well.
The second type is a 128-bit UUID, sometimes referred to as a vendor specific UUID. This is the type of
UUID you need to use when you are making your own custom services and characteristics. It looks
something like this: 4A98xxxx-1CC4-E7C1-C757-F1267DD021E8 and is called the “base UUID”. The four x’s
represent a field where you will insert your own 16-bit IDs for your custom services and characteristics
and use them just like a predefined UUID. This way you can store the base UUID once in memory,
forget about it, and work with 16-bit IDs as normal. You can generate base UUIDs using nRFgo Studio. It
is very easy and you can look in the Help menu to learn how.
A little fun fact about UUIDs: There is no database ensuring that no one in the world is sharing the
same UUID, but if you generate two random 128-bit UUIDs there is only a ~3e-39 chance that you will
end up with two identical IDs (that is ~1/340,000,000,000,000,000,000,000,000,000,000,000,000).
The example
First thing first
Download the example code from github. It is based on the template example found in the SDK, but
stripped of all code that is not strictly necessary for our purpose. To compile it download the project
files and copy the folder "nrf5x-ble-tutorial-service" to "your_SDK_folder\examples\ble_peripheral". If
you need help with this please have a look at this thread on devzone.
If using Keil, open the project file "nrf52-ble-tutorial-service.uvprojx" located in the folder
arm5_no_packs. Otherwise if you are using SEGGER, open "nrf52-ble-tutorial.services.emProject"
located in the folder ses. As you can see I have implemented the SEGGER Real-Time Terminal (RTT) to
our project so that we can easily see what is happening. Read this tutorial to learn how to use the RTT:
Debugging with Real Time Terminal.
The example should compile without any errors, but there might be some warnings about unused
variables. So hit build and then download the code to your board. Click "Start scan" in nRF Connect.
Your device should then show up like this:
The Generic Access service. Service UUID 0x1800. Three mandatory characteristics:
The second service is the Generic Attribute Service. Simply put, this service can be used to notify the
central of changes made to the fundamental structure of services and characteristics on the peripheral.
Short explanation here.
our_service_init (&m_our_service);
uint32_t err_code;
ble_uuid_t service_uuid;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
APP_ERROR_CHECK(err_code);
What this code does is to create two variables. One will hold our 16-bit service UUID and the other the
base UUID. In the fifth line we add our vendor specific UUID (hence the ‘vs’) to a table of UUIDs in the
BLE stack. Here and here are some short explanations of the function. Finally in the sixth line we do a
quick error check. It is good practice to do this after all function calls that return some sort of error
code. See this post to learn how to debug with APP_ERROR_CHECK().
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&service_uuid,
&p_our_service->service_handle);
APP_ERROR_CHECK(err_code);
The sd_ble_gatts_service_add() function takes three parameters. In the first parameter we specify
that we want a primary service. The other option here is to use BLE_GATTS_SRVC_TYPE_SECONDARY
to create a secondary service. The use of secondary services are rare, but sometimes used to nest
services inside other services. The second parameter is a pointer to the service UUID that we created. By
passing this variable to sd_ble_gatts_service_add() our service can be uniquely identified by the
BLE stack. The third variable passed to the function is a pointer to where the service_handle number of
this unique service should be stored. The sd_ble_gatts_service_add() function will create a table
containing our services and the service_handle is simply an index pointing to our particular service in
the table.
uint32_t err_code; // Variable to hold return codes from library and softdevice functions
// OUR_JOB: Declare 16-bit service and 128-bit base UUIDs and add them to the BLE stack
ble_uuid_t service_uuid;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&service_uuid,
&p_our_service->service_handle);
APP_ERROR_CHECK(err_code);
Compile, download your code, and open nRF Connect again. Do another scan and hit connect. Now
you should see our service with its custom UUID at the bottom. You can recognize the base UUID from
the #define in our_service.h and if you look closely you should also recognize our 16-bit service UUID:
0000 ABCD -1212-EFDE-1523-785FEF13D123.
If you have opened the Segger RTT you will also see some information about the application flow.
Remember to uncomment the four SEGGER_RTT lines at the bottom of our_service_init(). When the
application starts you can see what values are used in our service. For example, the service handle is set
to 0x000E. If you hover the cursor over our service you can recognize this value:
When you connect to your device using nRF Connect you will also see what events occur in the BLE
stack. These events are "captured" in the ble_event_handler() function in main.c. The first event is a
Generic Access Profile (GAP) event, BLE_GAP_EVT_CONNECTED, indicating that a connection has
been set up with connection handle value 0x00. If you ever make an application that has several
connections you will get several connection handles, each with a unique handle value. After a few
seconds you will get the BLE_GAP_EVT_CONN_PARAM_UPDATE event indicating that the MCP and
your device have renegotiated the connection parameters. After the renegotiation you can once again
recognize the connection parameter values from the #defines in the example, MIN_CONN_INTERVAL,
SLAVE_LATENCY, and CONN_SUP_TIMEOUT. Here is a short explanation of the negotiation process.
Finally, when you hit disconnect in MCP you will receive the BLE_GAP_EVT_DISCONNECTED event and
the reason for the disconnect.
Advertising
In the previous tutorial, "BLE Advertising, a beginner's tutorial", we discussed various aspects of the
advertising packet and now it is time to advertise our base UUID. Since the base UUID is 16 bytes long
and the advertising packet already contains some data there won't be enough space in the advertising
packet itself. Therefore we will need to put it in the scan response packet instead.
{
BLE_UUID_OUR_SERVICE,
BLE_UUID_TYPE_VENDOR_BEGIN
}
};
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
APP_ERROR_CHECK(err_code);
Compile and download the code again. Our device should now show up like this in MCP's list of
discovered devices:
Summary
So now you know how to setup and create your first basic service. If you want to add more services you
can easily just replicate the `our_service_init()` function and define more service UUIDs.
But wait! We are only halfway there you might say. Where are the characteristics supposed to hold all
your data? That is the topic of this tutorial: BLE Characteristics, a beginner's tutorial.
57 comments
1 member is here
Vasiliy Baryshnikov
over 3 years ago
Problem: nrf_log.h no such file or directory
Fix:
3. Copy and replace main.c, our_service.c and our_service.h files provided by this tutorial into the newly
created project directory.
This tutorial project had been made for SDK V15..0.0. By now we have newer versions of SDK which are
not really compatible with the project distributed by the tutorial. But we can still use the source file :)
El Dr. Gusman
over 7 years ago
Guys, AMAZING JOB, this is a true jewel, I'm starting with bt development, and even with examples it's a
harsh area, but this and the previous tutorials shed light on how things work on bt.
A very big thanks.
Two things:
1-Add support to GCC on this tut, I got it working but had to struggle a bit with the makefile, if someone
else wants to follow this using GCC can have a bit of trouble if he's not used to makefiles as me.
2-When can we spect the next tutorial? I will be waiting in front of my computer hitting F5 till I get it... XD
Cheers.
ashokraj
over 4 years ago
How can I create a variable m_our_service of type ble_os_t????
strengthstrong
over 5 years ago
i do not understand why {BLE_UUID_OUR_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN};}?
and i dont
know what BLE_UUID_TYPE_VENDOR_BEGIN, what useful ?
Thanks for;
venerley
over 5 years ago
It would be useful to understand the statement Since the base UUID is 16 bytes long and the advertising
packet already contains some data there won't be enough space in the advertising packet itself. So
presumably the other data has consumed at least 16 bytes already. It would be nice to account for this
and thus the rational for using Scan Response.
View More