MartinBL gravatar image

Posted 2015-08-26 14:37:58 +0200

BLE Services, a beginner's tutorial

The purpose of this tutorial is to get you started with the basics of BLE, the nRF5x DK, and the SDK. More specifically we will go through the bare minimum of steps to get your first service up and running. The tutorial is meant to be a fun way to get you started with something functional, and maybe inspire you to go further with BLE. It is also intended to be a natural continuation of the tutorial "BLE Advertising, a beginner's tutorial". Here you will not learn details of the BLE protocol, but we will have a quick look at what you should know before building a service.

Before we begin


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.

Equipment and software

To complete this tutorial you will need the following:

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.

Necessary prior knowledge

This tutorial is intended to be a natural continuation of the tutorial "BLE Advertising, a beginner's tutorial". It is recommended, but not necessary, to go through this tutorial first. It is expected that you have basic knowledge of how to use Keil, MCP and nRFgo Studio and how to download your application to your kit. Read the tutorial Setting up an example project on the nRF51 DK to learn how to use your equipment and compile your first BLE application.

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(!).

Some basic theory

The Generic Attribute Profile (GATT)

The Bluetooth Core Specification defines the GATT like this:

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.


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.


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:

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 available 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.

Universally Unique ID (UUID)

A UUID is an abbreviation you will see a lot in the BLE world. It is a unique number used to identify services, characteristics and descriptors, also known as attributes. These IDs are transmitted over the air so that e.g. a peripheral can inform a central what services it provides. To save transmitting air time and memory space in your nRF51 there are two kinds of UUIDs:

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.

Open the project file "nrf51-ble-tutorial-service.uvprojx". 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. Your device should then show up in MCP like this:

Initial Advertising

If you click on “Select device” and then “Discover services” you should see this:

Initial Connection

As you can see, even before we have done anything, there are already two mandatory services set up for us:

The Generic Access service. Service UUID 0x1800. Three mandatory characteristics:

  1. Characteristic: Device name. UUID 0x2A00.
  2. Characteristic: Appearance. UUID 0x2A01.
  3. Characteristic: Peripheral Preferred Connection Parameters. UUID 0x2A04.

The Generic Attribute service. UUID 0x1801. One optional characteristic:

  1. Characteristic: Service Changed. UUID 0x2A05.

The Generic Access Service contains general information about the device. You can recognize a characteristic holding the device name “OurService”. The second characteristic holds the appearance value and in our case we haven't set the value to anything so it just shows 0x0000. The third characteristic holds various parameters used to establish a connection. You can recognise values from the #defines in the example called: MIN_CONN_INTERVAL, MAX_CONN_INTERVAL, SLAVE_LATENCY, and CONN_SUP_TIMEOUT. Here is a short explanation regarding these parameters.

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 first service

As you can see I have included two files in the example; our_service.h and our_service.c. Inside I have declared some empty functions and a little struct so that it will be easier for us to get started. I have also used nRFgo Studio to create and define a 128-bit base UUID, defined as BLE_UUID_OUR_BASE_UUID. Search for STEP 1-7 in the project files and you will find the places where we need to add code.

Step 1: Declare a service structure

First of all we need a place to store all data and information relevant to our service and to do this we will use the ble_os_t structure. As you can see in our_service.h the structure only holds one entry by now. The service_handle is a number identifying this particular service and is assigned by the SoftDevice. Declare a variable called m_our_service of type ble_os_t in main.c so that we can pass it to various functions and have complete control of our service.

Step 2: Initialize the service

In main.c there is already a function called services_init() waiting for you. Inside this function we will call our_service_init(). It takes a pointer to a ble_os_t struct as a parameter so make sure that you point to our m_our_service variable:

our_service_init (&m_our_service);

Step 3: Add UUIDs to BLE stack table

Look up the definition of our_service_init() in our_service.c. As you can see there is almost no code so we have some work to do. We must first create a UUID for our service. Since we are going to make a custom service we will use the defined base UUID together with a 16-bit UUID. So type in the following:

uint32_t   err_code;
ble_uuid_t        service_uuid;
ble_uuid128_t     base_uuid = BLE_UUID_OUR_BASE_UUID;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);

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 fourth 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 fifth 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().

Step 4: Add our service

Now we are ready to initialize our service. Type in the following right after the previous code:

err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,

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.

our_service_init() should now look something like this:

void our_service_init(ble_os_t * p_our_service)
    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;
    ble_uuid128_t     base_uuid = BLE_UUID_OUR_BASE_UUID;
    service_uuid.uuid = BLE_UUID_OUR_SERVICE;
    err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);

    // OUR_JOB: Add our service
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,

    SEGGER_RTT_WriteString(0, "Exectuing our_service_init().\n");
    SEGGER_RTT_printf(0, "Service UUID: 0x%#04x\n", service_uuid.uuid);
    SEGGER_RTT_printf(0, "Service UUID type: 0x%#02x\n", service_uuid.type);
    SEGGER_RTT_printf(0, "Service handle: 0x%#04x\n", p_our_service->service_handle);

Compile, download your code, and open MCP again. Hit connect and do another service discovery. 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.

First Service

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 0x000C. If you highlight our service in MCP you can recognize this value:

image description

When you connect to your device using MCP you will also see what events occur in the BLE stack. These events are "captured" in the on_ble_evt() 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.

Real Time Terminal


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.

Step 5: Declare variable holding our service UUID

Inside the advertising_init() function in main.c declare a variable holding our service uuid like this:

ble_uuid_t m_adv_uuids[] = {BLE_UUID_OUR_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN};

BLE_UUID_OUR_SERVICE is, as you know, our service UUID and BLE_UUID_TYPE_VENDOR_BEGIN indicates that it is a part of a vendor specific base UUID. More specifically BLE_UUID_TYPE_VENDOR_BEGIN is an index pointing to our base UUID in the table of UUIDs that we initiated in our_service_init().

Step 6: Declare and instantiate the scan response

Declare and instantiate the scan response:

ble_advdata_t srdata;
memset(&srdata, 0, sizeof(srdata));

Then add the UUID to the scan response packet like this:

srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
srdata.uuids_complete.p_uuids = m_adv_uuids;

Step 7: Include scan response packet in advertising

Finally initiate advertising with scan response:

err_code = ble_advertising_init(&advdata, &srdata, &options, on_adv_evt, NULL);

advertising_init() should now look something like this:

static void advertising_init(void)
    uint32_t      err_code;
    ble_advdata_t advdata;

    // Build advertising data struct to pass into ble_advertising_init().
    memset(&advdata, 0, sizeof(advdata));

    advdata.name_type               = BLE_ADVDATA_FULL_NAME;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    // OUR_JOB: Create a scan response packet and include the list of UUIDs 
    ble_uuid_t m_adv_uuids[] = {BLE_UUID_OUR_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN}; 

    ble_advdata_t srdata;
    memset(&srdata, 0, sizeof(srdata));
    srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    srdata.uuids_complete.p_uuids = m_adv_uuids;

    err_code = ble_advertising_init(&advdata, &srdata, &options, on_adv_evt, NULL);

Compile and download the code again. Our device should now show up like this in MCP's list of discovered devices:

Advertising UUID


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.


gusmanb gravatar image

Posted Aug. 28, 2015, 11:35 a.m.

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


ivang.boletus gravatar image

Posted Sept. 2, 2015, 5:23 p.m.

This is simply brilliant. Really waiting for more!!! Good job.

Best Regards,

Iván Gómez

MartinBL gravatar image

Posted Sept. 4, 2015, 9:54 a.m.

Hi. Thank you very much for your inspiring input. I have added GCC and Eclipse Kepler support (hope it works!). The next tutorial is a work in progress. I'm afraid it will take a while as I am a little busy with other things and I need some time to figure out how to present the contents in a comprehensible manner. After all it is starting to get quite complex. My goal is to make it as simple as possible, but still adhere to all the nooks and crannies of the BLE Core Specification. I also want to keep the code style more or less consistent with our SDKs to make advancing from these tutorials to the more complicated examples in the SDKs as easy as possible.

In the mean time I can only advise you to have a look at the examples in the SDKs to learn how to add characteristics. There is also this Application Note, nAN-36, with accompanying code. If you haven't seen this already it is sort of a similar tutorial as this, though with a different approach.

vandita5 gravatar image

Posted Nov. 13, 2015, 12:52 a.m.

Hey, Thank you for the tutorials. They are really helpful. I went through the advertising one smoothly but for this one I'm not seeing any services on lightblue. Not even after creating m_our_service. I can see the UUID written but can't access it to see its details. Please see screenshot. Nothing is expandable beyond what is visible in the screenshot.

Progress on the tutorial: I have completed till editing our_service_init as described and called it in services_init. I have tried on the nrf51 DK and my custom board using nrf51822. What could be wrong with the setup?

Thanks, Vandita Slack for iOS Upload-1.png.jpeg

Petter gravatar image

Posted Nov. 17, 2015, 9:13 a.m.

@Vandita If you get into trouble, please add a question in the Question Section and refer to this tutorial.

ShannonRichard gravatar image

Posted Nov. 24, 2015, 8:59 p.m.

Thank you for the tutorial. it has been very helpful, and I also appreciate the links to other peoples questions regarding how to use the various functions etc.

As a note to other people in doing the tutorial, make sure to read everything, don't skip steps or leap ahead. maybe even read it over once to get an idea of what your going to do, then read it again while doing the suggestions. if you miss a step, you'll fall on your face.

Thanks Martin, I am hoping this will be enough to get me off and running.

shraken gravatar image

Posted Jan. 20, 2016, 3:54 a.m.

Thanks for tutorial Nordic and Martin, very helpful.

@Vandita: This is a bit late but just found your comment. What is your build enviorment? armgcc? I had to add '$(abspath ../../../our_service.c) \' to the C_SOURCE_FILES variable in the armgcc Makefile that I cloned from github 'nrf51-ble-tutorial-service' master.

Walkersun gravatar image

Posted Feb. 5, 2016, 5:34 a.m.

Where can I download the example code of this turial? I have searched in GitHub with key word "nrf51-ble-tutorial-simple-service" and "Our_Service" or "Our-Service", but had not found the right code.

Thank you!

tesc gravatar image

Posted Feb. 10, 2016, 10:33 a.m.

@Walkersun: Link in the list under "necessary equipment and software" near the top of the tutorial:

Sensors gravatar image

Posted Feb. 19, 2016, 6:43 p.m.

Hi, just a reminder to update this post to link to the characteristics tutorial. It's not even linked to in the list of tutorials, I found out about it via asking a question.

MartinBL gravatar image

Posted Feb. 24, 2016, 12:48 p.m.

The characteristic tutorial is not completely finished yet and not supposed to be published ;) I'll publish it and update links when we have released the final release of SDK 11.0.0 (should be pretty soon now). Then I'll also update this tutorial to use SDK 11.

MartinBL gravatar image

Posted March 23, 2016, 9:23 a.m.

"BLE Characteristics, a beginner's tutorial" is now available here.

Vijayalakshmi gravatar image

Posted April 5, 2016, 12:48 p.m.

Hi, can you please tell how to add more than one custom service

bensch128 gravatar image

Posted June 1, 2016, 8:42 p.m.

I love the tutorial but I wanted to point out that the link to the next tutorial about BLE characteristics is wrong. It points back to here. I believe that it should point to


Gustavo Reynaga gravatar image

Posted July 14, 2016, 8:29 p.m.

Hi, im a Teacher in a community college in Mazatlan, Mexico, it is posible, to do all tutorials, using only open source software like Linux, gcc, etc.? (No Keils for example)

MartinBL gravatar image

Posted July 19, 2016, 10:54 a.m.

Hi, Gustavo. Yes it is possible. You can use GCC or Segger Embedded Studio (SES) which is also a free cross platform IDE. I have not had the time to make SES project files for the tutorials, but there are GCC files for all three tutorials. There are two blog posts written by a colleague of mine that show how to import Keil projects into SES though.

Segger Embedded Studio - Cross Platform IDE w/ no Code Limit! (Mac OS X, Linux support)

Segger Embedded Studio - Cross Platform IDE w/ no Code Limit! (Mac OS X, Linux support) -- Revised

Here is a tutorial showing how to use GCC. Development with GCC and Eclipse

Chawoo gravatar image

Posted July 29, 2016, 8:28 a.m.

You have to modify 2 lines


  2. "base_uuid.uuid128 = BLE_UUID_OUR_BASE_UUID;" to "ble_uuid128_t base_uuid = {BLE_UUID_OUR_BASE_UUID};". also more braces pair

if you using SDK v11

vit bernatik gravatar image

Posted Aug. 5, 2016, 4:56 p.m.

First thx very much for tutorial. But there is error in computation: 2^128 is little bit more than 3e38 but far less than 3e39. Can you fix it? (erase one zero...). Optionally you can use scientific notation: 3e38 - after all programmers are not scientists but can spend a time on better things than counting zeros.

Webmobi gravatar image

Posted Oct. 14, 2016, 9:57 a.m.

how to assign name to service ?

Webmobi gravatar image

Posted Oct. 14, 2016, 10:44 a.m.

how to assign name to service ?

MartinBL gravatar image

Posted Nov. 29, 2016, 9:42 a.m.

@vit bernatik: I guess my goal was more of a "visual" representation of the number. Anyway I have added scientific notation and fixed the computation error. Thanks for reporting.

For newcomers: Please ask questions in the forum section for increased visibility. The question sections in the tutorials are not being monitored very closely (the system doesn't notify me about new activity here). The forum section is patrolled by Nordic employees as well as some incredibly skilled users every day.

Sign in to comment.

Related posts by tag