How to build the simplest nRF52 BLE Peripheral application (Lightbulb use case)

Let’s face it…

One of the hardest things when working with BLE is simply getting started.

Whether it’s the setup of the IDE, the configuration of the project, or the implementation the BLE application.

I’ve been there… I’ve felt lost, not knowing where and how to start… This is especially true since I was trying to learn the technology itself (BLE) in addition to learning an SDK, platform, and IDE all at once! It just felt overwhelming and way too many things to learn at one time.

Lately, I’ve been focusing on one platform/chipset: Nordic’s nRF52. This is due to one main reason: I’ve found it to be the most developer-friendly platform out there. (It also helps that you get a FREE commercial license for a professional IDE: Segger Embedded Studio (SES)!)

There’s nothing wrong with the other platforms and chipsets, but it also helps if you stick to one platform that you feel comfortable with (at least for a given period of time, especially in the beginning of your journey in learning a technology).

Ok, enough with the rant, and let’s get into what this post is all about:

To guide you through setting up and developing the most basic BLE peripheral application: a smart BLE lightbulb application you can control from your smartphone.

In the previous blog post (The complete cross-platform nRF development tutorial), we went over how to set up the IDE of choice for developing nRF52 applications (Segger Embedded Studio) on any platform (macOS, Windows, Linux). In this post, we’ll focus on developing the BLE peripheral application, building it, debugging it, and finally testing it from a mobile phone application.

The simplest BLE Peripheral application

We’re going to build a very simple BLE lightbulb application that allows you to turn ON/OFF an LED on the nRF52840 development kit. The Peripheral application will also expose the battery level of a coin-cell battery installed in the development kit. The peripheral will notify the Central (mobile phone application in our case) when the battery level gets updated.

Keeping the example dead-simple is extremely important. You can always customize and expand your application once you’ve learned the basics.

But if you start with a complex application, you can get lost, and ultimately become frustrated before you get something working.

…which is why we’re keeping it simple in this application!

Prerequisites

To follow along with this example, you’ll need to know the basics of BLE. It does not require you to have in-depth knowledge of BLE. In fact, I recommend you do not spend too much time going through the theory and skip right into developing a BLE application and getting your hands dirty once you’ve gone through learning the basics.

If you’re looking to learn the basics, or simply need a refresher, I recommend checking out my FREE 7-day crash course on the Basics of Bluetooth Low Energy (BLE). Sign up by filling out the form in the bottom right-hand corner of the webpage:

Alternatively, you can sign up at the following link: FREE 7-day crash course on Bluetooth Low Energy (BLE).

So, what hardware & software do I need?

For this tutorial, you’ll need the following:

  • nRF52840 development kit (although any nRF5x development will work, with some modifications)
  • A PC running either Windows, Linux, or macOS (for development)
  • Segger Embedded Studio
  • nRF5 SDK (in our case, we’re using the latest SDK provided by Nordic: nRF5 SDK version 15.0.0)
  • A mobile phone
  • BLE client application running on the mobile phone (such as Nordic’s nRF Connect or LightBlue)

Getting Started

Installing the SDK

We’ve already covered how to install Segger Embedded Studio (the FREE License IDE used for nRF5x development) in a previous post. Here, we’ll focus on the steps that follow the installation.

Next, let’s download the latest nRF5 SDK (version 15.0.0) from Nordic’s website.

Once you have it downloaded, place it in a new folder. To make things easier, we’ll put it in a folder alongside the application we’ll be developing.

In my setup, I’ll be creating a new folder named “BLE Projects” which contains the SDK folder as well as another folder for the application.

Folder Structure Setup

Create a new folder to hold the SDK as well as the application (I named it BLE Projects):

  • Copy the SDK folder to the “BLE Projects” folder
  • [Optional] Rename the SDK folder to “nRF5_SDK_Current”. This way, if I need to migrate to a newer SDK, all I have to do is place it in that folder, and my current projects will not have to be updated (other than any changes needed for the APIs to work with the newer SDK)
  • Navigate to the folder examples/ble_peripheral/ and copy the folder ble_app_template to the BLE Projects folder
  • Copying the example application folder outside of the SDK makes it easier to keep it self-contained and independent of any changes you make to the SDK folder (such as installing a newer SDK)
  • Rename ble_app_template to another meaningful name that describes our simple application: ble_lightbulb

Now, it’s time for a little clean-up!

The example application folder contains a few unnecessary files and folders (related to other IDEs). I also find that it contains too many nested folders that make it harder to find important files. We’ll do the following:

  • Move the sdk_config.h file to the root folder of the application (located under pca10056/s140/config)
  • Move the SES Solution file (ble_app_template_pca10056_s140.emProject) along with the flash_placement.xml file to the root application folder (both of these files are located under pca10056/s140/ses).
  • You can also move the *.jlink and *.emSession files, but those are not necessary as SES will re-create them once you open the Solution.
  • Delete the ble_app_template.eww file
  • Delete the pca* folders (don’t forget to move the files listed above before you do this!)

Here is what the resulting folder looks like for me:

SES Project File Configuration

Now that we’ve got the folder structure set up, we need to update the SES project file to reflect these changes (this is something we’ll only have to do once).

First, rename the file “ble_app_template_pca10056_s140.emProject” to “”ble_lightbulb_pca10056_s140.emProject“.

Next, open the file in your text editor of choice.

Here are the modifications we’re going to make to the project file (I’ve already made all the changes and provided the full source code available for you to download if you want to skip these steps, at the end of this post):

  • Rename the Solution and Project names:to:
  • Replace any references to the SDK (since we copied the application folder outside the SDK).
    Replace “../../../../..” with “nRF5_SDK_current”
    E.g.:
    replaced with:

Next, open the Solution (*.emProject file) in SES.

Now, make the following changes to the Project and the source files:

  • In main.c, change the advertised name:
  • Also in main.c, add the following lines (highlighted):
  • Remove any peer manager references (only needed for security features):
    • Changeto:
    • Remove the following block of code:
    • Delete the following block:
    • Changeto
    • Remove the highlighted line:
    • Changeto
    • Modify the following (we are not using any buttons in our application, so we only need to initialize the LEDs):to
    • In main(), modifyto
  • Change the debug print out for the main application:to
  • We need to add a write handler for the LED settings in main.c. This function will get used for the custom service and characteristic we’ll be implementing in our application.

BLE Lightbulb Application Design

Let’s talk about the design of our application, and what elements we need to add to implement our smart BLE-controlled lightbulb.

The application allows the user to:

  • Discover the BLE Peripheral named “BLE_Lightbulb” from a BLE Central application
  • Connect to the Peripheral and discover the Services and Characteristics exposed by the Peripheral
  • Turn ON/OFF LED2 on the nRF52840 development kit as well as read the status of the LED (whether ON or OFF)
  • Subscribe to Notifications to the Battery Level Characteristic exposed by the Peripheral inside the Battery Service

LED Service + LED 2 Characteristic

To be able to control the LED on the development kit from a BLE client (a smartphone application in our case), we’ll need to implement a custom service and characteristic for the LED.

iIn a previous post (Custom Services and Characteristics [BLE MIDI use case]), we described how to create custom services and characteristics, but here’s all you need to know:

  • We need to create one service. We’ll call it led_service.
  • In that service, we’ll add a single characteristic dedicated to LED 2 on our development kit (LED 1 will be used to indicate the state of the BLE peripheral: Advertising or Connected). Let’s call this characteristic: led_2_char.
  • For each (service and characteristic), we’ll need to choose a custom UUID (to learn more about custom/vendor-specific UUIDs and how to pick them, refer to this blog post (Creating custom UUIDs).
  • We’ll define the UUIDs as following:
    • LED Service UUID: E54B0001-67F5-479E-8711-B3B99198CE6C
    • LED 2 Characteristic UUID: E54B0002-67F5-479E-8711-B3B99198CE6C
  • The LED 2 Characteristic will have the following permissions:
    • Write-enabled
    • Read-enabled
    • NO Notifications or Indications enabled (since we’ll be controlling the LED from the BLE client)

Implementation

Instead of including the implementation of the LED service in the main.c file, we’ll be including them in a separate folder and files.

Let’s create two files: led_service.h & led_service.c.

We’ll be placing these in a separate folder named Services.

  • Create a folder named Services in the root folder of the application (from your Operating System’s file explorer)
  • Right-click on Project ‘ble_lightbulb’
  • Click New Folder
    Create new project folder SES
  • Rename New Folder to Services
  • Now you should have an empty Services folder
  • Right-click on Services and choose to Add New File
  • Choose C File (.c), and type led_service.c in the Name field
  • Under Location, click Browse and navigate to the Services folder
  • Select Services and click Choose

  • Click OK
  • Repeat the steps above but choose Header File (.h) instead and enter led_service.h in the Name field

Here are the contents of the source files. We’ll list the full source code first, and then go through the content explaining it.

led_service.h

led_service.c

Let’s explain the most important elements of the implementation:

led_service.h

  • Lines 33-39: define a macro that will be used in main.c to instantiate the LED service data structure
  • Lines 41-53: define the UUIDs for the LED Service and the LED 2 Characteristic
  •  Lines 55-65: define the led write handler function prototype and the LED service initialization data structure
  • Lines 67-79: define the main LED service data structure that stores all the information relevant to the service
  • Lines 81-100: declare the functions needed for initializing the service as well as the event handler

led_service.c

  • Lines 18-23: include the necessary header files as well as define the string
  • Lines 25-44: define the functions for handling the connection and disconnection events
  • Lines 46-61: define the function for handling the BLE write event
  •  Lines 63-112: define the function for adding the LED 2 Characteristic
  • Lines 114-153: define the function used to initialize the LED Service
  • Lines 155-177: define the BLE event handler that gets called by the SoftDevice (to handle the different events such as connection, disconnection, and write events)

Battery Service

In addition to the custom Service and Characteristic, we’ll be using the Bluetooth SIG defined Battery Service, which includes a Battery Level Characteristic. (No need to create our own custom since we can reuse the SIG-defined Service).

Nordic’s SDK already has this Service implemented, so all we need to do is:

  • Enable the Battery Service in sdk_config.h. You can do so by setting the following macro to 1:
  • Add the ble_bas.c file:
    • Right-click on the folder named nRF_BLE under the Project in the Project Explorer window
    • Click on Add Existing File
    • Locate the ble_bas.c file under <SDK folder>/components/ble/ble_services/ble_bas
    • Click on ble_bas.c and hit Add
  • Update NRF_SDH_BLE_VS_UUID_COUNT in sdk_config.h to reflect the number of custom UUIDs (2 in our case: one for the LED Service and one for the LED 2 Characteristic)

Now that we have the Battery Service enabled, we need to implement the actual reading of the battery voltage level from the coin-cell battery installed in the development kit.

The good thing is we don’t have to implement this from scratch. In fact, one of the components within the nRF5 SDK provides exactly what we need!

The component we’ll be borrowing the code from is the Eddystone component (used for a beacon standard called Eddystone).

Navigate to the folder <SDK folder>/components/ble/ble_services/eddystone

There you will find the files:

es_battery_voltage.h
es_battery_voltage_saadc.c

We’ll copy these files into our project folder “ble_lightbulb” under a newly created folder named Battery Level.

Then rename them to “battery_voltage.c” and “battery_voltage.h“:

Now, you’ll need to add this folder to the Project in SES. Follow the same steps we did before with the LED Service files (creating a folder named Battery Level, but then using the Add Existing File option instead of creating a new one).

We’ll make a few slight modifications to functions names and such:

battery_voltage.h

battery_voltage.c