
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:
12<solution Name="ble_app_template_pca10056_s140" target="8" version="2"><project Name="ble_app_template_pca10056_s140">
to:
12<solution Name="ble_lightbulb_pca10056_s140" target="8" version="2"><project Name="ble_lightbulb"> - Replace any references to the SDK (since we copied the application folder outside the SDK).
Replace “../../../../..” with “nRF5_SDK_current”
E.g.:
123456<file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_backend_rtt.c" /><file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_backend_serial.c" /><file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_backend_uart.c" /><file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_default_backends.c" /><file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_frontend.c" /><file file_name="../../../../../../components/libraries/experimental_log/src/nrf_log_str_formatter.c" />
replaced with:
123456<file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_backend_rtt.c" /><file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_backend_serial.c" /><file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_backend_uart.c" /><file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_default_backends.c" /><file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_frontend.c" /><file file_name="../nRF5_SDK_current/components/libraries/experimental_log/src/nrf_log_str_formatter.c" />
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:
1#define DEVICE_NAME "BLE_Lightbulb" /**< Name of device. Will be included in the advertising data. */ - Also in main.c, add the following lines (highlighted):
1234567891011121314#include "ble_bas.h"#include "services/led_service.h"#include "Battery Level/battery_voltage.h"#define DEVICE_NAME "BLE_Lightbulb" /**< Name of device. Will be included in the advertising data. */#define MANUFACTURER_NAME "NordicSemiconductor" /**< Manufacturer. Will be passed to Device Information Service. */#define APP_ADV_INTERVAL 300 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 187.5 ms). */// Corresponds to LED2 on the development kit#define LIGHTBULB_LED BSP_BOARD_LED_1 /**< LED to be toggled with the help of the LED Button Service. */#define APP_ADV_DURATION 18000 /**< The advertising duration (180 seconds) in units of 10 milliseconds. */ - Remove any peer manager references (only needed for security features):
- Change
1static void advertising_start(bool erase_bonds);
to:
1static void advertising_start(); - Remove the following block of code:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495/**@brief Function for handling Peer Manager events.** @param[in] p_evt Peer Manager event.*/static void pm_evt_handler(pm_evt_t const * p_evt){ret_code_t err_code;switch (p_evt->evt_id){case PM_EVT_BONDED_PEER_CONNECTED:{NRF_LOG_INFO("Connected to a previously bonded device.");} break;case PM_EVT_CONN_SEC_SUCCEEDED:{NRF_LOG_INFO("Connection secured: role: %d, conn_handle: 0x%x, procedure: %d.",ble_conn_state_role(p_evt->conn_handle),p_evt->conn_handle,p_evt->params.conn_sec_succeeded.procedure);} break;case PM_EVT_CONN_SEC_FAILED:{/* Often, when securing fails, it shouldn't be restarted, for security reasons.* Other times, it can be restarted directly.* Sometimes it can be restarted, but only after changing some Security Parameters.* Sometimes, it cannot be restarted until the link is disconnected and reconnected.* Sometimes it is impossible, to secure the link, or the peer device does not support it.* How to handle this error is highly application dependent. */} break;case PM_EVT_CONN_SEC_CONFIG_REQ:{// Reject pairing request from an already bonded peer.pm_conn_sec_config_t conn_sec_config = {.allow_repairing = false};pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);} break;case PM_EVT_STORAGE_FULL:{// Run garbage collection on the flash.err_code = fds_gc();if (err_code == FDS_ERR_NO_SPACE_IN_QUEUES){// Retry.}else{APP_ERROR_CHECK(err_code);}} break;case PM_EVT_PEERS_DELETE_SUCCEEDED:{advertising_start(false);} break;case PM_EVT_PEER_DATA_UPDATE_FAILED:{// Assert.APP_ERROR_CHECK(p_evt->params.peer_data_update_failed.error);} break;case PM_EVT_PEER_DELETE_FAILED:{// Assert.APP_ERROR_CHECK(p_evt->params.peer_delete_failed.error);} break;case PM_EVT_PEERS_DELETE_FAILED:{// Assert.APP_ERROR_CHECK(p_evt->params.peers_delete_failed_evt.error);} break;case PM_EVT_ERROR_UNEXPECTED:{// Assert.APP_ERROR_CHECK(p_evt->params.error_unexpected.error);} break;case PM_EVT_CONN_SEC_START:case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:case PM_EVT_PEER_DELETE_SUCCEEDED:case PM_EVT_LOCAL_DB_CACHE_APPLIED:case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED:// This can happen when the local DB has changed.case PM_EVT_SERVICE_CHANGED_IND_SENT:case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED:default:break;}} - Delete the following block:
123456789101112131415161718192021222324252627282930313233343536373839404142434445/**@brief Function for the Peer Manager initialization.*/static void peer_manager_init(void){ble_gap_sec_params_t sec_param;ret_code_t err_code;err_code = pm_init();APP_ERROR_CHECK(err_code);memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));// Security parameters to be used for all security procedures.sec_param.bond = SEC_PARAM_BOND;sec_param.mitm = SEC_PARAM_MITM;sec_param.lesc = SEC_PARAM_LESC;sec_param.keypress = SEC_PARAM_KEYPRESS;sec_param.io_caps = SEC_PARAM_IO_CAPABILITIES;sec_param.oob = SEC_PARAM_OOB;sec_param.min_key_size = SEC_PARAM_MIN_KEY_SIZE;sec_param.max_key_size = SEC_PARAM_MAX_KEY_SIZE;sec_param.kdist_own.enc = 1;sec_param.kdist_own.id = 1;sec_param.kdist_peer.enc = 1;sec_param.kdist_peer.id = 1;err_code = pm_sec_params_set(&sec_param);APP_ERROR_CHECK(err_code);err_code = pm_register(pm_evt_handler);APP_ERROR_CHECK(err_code);}/**@brief Clear bond information from persistent storage.*/static void delete_bonds(void){ret_code_t err_code;NRF_LOG_INFO("Erase bonds!");err_code = pm_peers_delete();APP_ERROR_CHECK(err_code);} - Change
12345678910111213141516/**@brief Function for starting advertising.*/static void advertising_start(bool erase_bonds){if (erase_bonds == true){delete_bonds();// Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event}else{ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);APP_ERROR_CHECK(err_code);}}
to
1234567/**@brief Function for starting advertising.*/static void advertising_start(){ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);APP_ERROR_CHECK(err_code);} - Remove the highlighted line:
123services_init();conn_params_init();peer_manager_init(); - Change
1advertising_start(erase_bonds);
to
1advertising_init(); - Modify the following (we are not using any buttons in our application, so we only need to initialize the LEDs):
1234567891011121314151617/**@brief Function for initializing buttons and leds.** @param[out] p_erase_bonds Will be true if the clear bonding button was pressed to wake the application up.*/static void buttons_leds_init(bool * p_erase_bonds){ret_code_t err_code;bsp_event_t startup_event;err_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, bsp_event_handler);APP_ERROR_CHECK(err_code);err_code = bsp_btn_ble_init(NULL, &startup_event);APP_ERROR_CHECK(err_code);*p_erase_bonds = (startup_event == BSP_EVENT_CLEAR_BONDING_DATA);}
to
12345678910** @details Initializes all LEDs used by the application.*/static void leds_init(){ret_code_t err_code;err_code = bsp_init(BSP_INIT_LEDS, bsp_event_handler);APP_ERROR_CHECK(err_code);} - In main(), modify
1buttons_leds_init(&erase_bonds);
to
1leds_init();
- Change
- Change the debug print out for the main application:
1NRF_LOG_INFO("Template example started.");
to
1NRF_LOG_INFO("BLE Lightbulb example started."); - 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.
123456789101112131415161718/**@brief Function for handling write events to the LED characteristic.** @param[in] p_led_service Instance of LED Service to which the write applies.* @param[in] led_state Written/desired state of the LED.*/static void led_write_handler(uint16_t conn_handle, ble_led_service_t * p_led_service, uint8_t led_state){if (led_state){bsp_board_led_on(LIGHTBULB_LED);NRF_LOG_INFO("Received LED ON!");}else{bsp_board_led_off(LIGHTBULB_LED);NRF_LOG_INFO("Received LED OFF!");}}
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
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/* * The MIT License (MIT) * Copyright (c) 2018 Novel Bits * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * */ #ifndef LED_SERVICE_H #define LED_SERVICE_H #include #include "boards.h" #include "ble.h" #include "ble_srv_common.h" #include "nrf_sdh_ble.h" /**@brief Macro for defining a ble_led_service instance. * * @param _name Name of the instance. * @hideinitializer */ #define BLE_LED_SERVICE_BLE_OBSERVER_PRIO 2 #define BLE_LED_SERVICE_DEF(_name) \ static ble_led_service_t _name; \ NRF_SDH_BLE_OBSERVER(_name ## _obs, \ BLE_LED_SERVICE_BLE_OBSERVER_PRIO, \ ble_led_service_on_ble_evt, &_name) // LED service: E54B0001-67F5-479E-8711-B3B99198CE6C // LED 2 characteristic: E54B0002-67F5-479E-8711-B3B99198CE6C // The bytes are stored in little-endian format, meaning the // Least Significant Byte is stored first // (reversed from the order they're displayed as) // Base UUID: E54B0000-67F5-479E-8711-B3B99198CE6C #define BLE_UUID_LED_SERVICE_BASE_UUID {0x6C, 0xCE, 0x98, 0x91, 0xB9, 0xB3, 0x11, 0x87, 0x9E, 0x47, 0xF5, 0x67, 0x00, 0x00, 0x4B, 0xE5} // Service & characteristics UUIDs #define BLE_UUID_LED_SERVICE_UUID 0x0001 #define BLE_UUID_LED_2_CHAR_UUID 0x0002 // Forward declaration of the custom_service_t type. typedef struct ble_led_service_s ble_led_service_t; typedef void (*ble_led_service_led_write_handler_t) (uint16_t conn_handle, ble_led_service_t * p_led_service, uint8_t new_state); /** @brief LED Service init structure. This structure contains all options and data needed for * initialization of the service.*/ typedef struct { ble_led_service_led_write_handler_t led_write_handler; /**< Event handler to be called when the LED Characteristic is written. */ } ble_led_service_init_t; /**@brief LED Service structure. * This contains various status information * for the service. */ typedef struct ble_led_service_s { uint16_t conn_handle; uint16_t service_handle; uint8_t uuid_type; ble_gatts_char_handles_t led_2_char_handles; ble_led_service_led_write_handler_t led_write_handler; } ble_led_service_t; // Function Declarations /**@brief Function for initializing the LED Service. * * @param[out] p_led_service LED Service structure. This structure will have to be supplied by * the application. It will be initialized by this function, and will later * be used to identify this particular service instance. * * @return NRF_SUCCESS on successful initialization of service, otherwise an error code. */ uint32_t ble_led_service_init(ble_led_service_t * p_led_service, const ble_led_service_init_t * p_led_service_init); /**@brief Function for handling the application's BLE stack events. * * @details This function handles all events from the BLE stack that are of interest to the LED Service. * * @param[in] p_ble_evt Event received from the BLE stack. * @param[in] p_context LED Service structure. */ void ble_led_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context); #endif /* LED_SERVICE_H */ |
led_service.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
/* * The MIT License (MIT) * Copyright (c) 2018 Novel Bits * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * */ #include #include "nrf_log.h" #include "led_service.h" static const uint8_t LED2CharName[] = "LED 2"; /**@brief Function for handling the Connect event. * * @param[in] p_led_service LED service structure. * @param[in] p_ble_evt Event received from the BLE stack. */ static void on_connect(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt) { p_led_service->conn_handle = p_ble_evt->evt.gap_evt.conn_handle; } /**@brief Function for handling the Disconnect event. * * @param[in] p_bas LED service structure. * @param[in] p_ble_evt Event received from the BLE stack. */ static void on_disconnect(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt) { UNUSED_PARAMETER(p_ble_evt); p_led_service->conn_handle = BLE_CONN_HANDLE_INVALID; } /**@brief Function for handling the Write event. * * @param[in] p_led_service LED Service structure. * @param[in] p_ble_evt Event received from the BLE stack. */ static void on_write(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt) { ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write; if ( (p_evt_write->handle == p_led_service->led_2_char_handles.value_handle) && (p_evt_write->len == 1) && (p_led_service->led_write_handler != NULL)) { p_led_service->led_write_handler(p_ble_evt->evt.gap_evt.conn_handle, p_led_service, p_evt_write->data[0]); } } /**@brief Function for adding the LED 2 characteristic. * */ static uint32_t led_2_char_add(ble_led_service_t * p_led_service) { ble_gatts_char_md_t char_md; ble_gatts_attr_t attr_char_value; ble_gatts_attr_md_t attr_md; ble_uuid_t ble_uuid; memset(&char_md, 0, sizeof(char_md)); memset(&attr_md, 0, sizeof(attr_md)); memset(&attr_char_value, 0, sizeof(attr_char_value)); char_md.char_props.read = 1; char_md.char_props.write = 1; char_md.p_char_user_desc = LED2CharName; char_md.char_user_desc_size = sizeof(LED2CharName); char_md.char_user_desc_max_size = sizeof(LED2CharName); char_md.p_char_pf = NULL; char_md.p_user_desc_md = NULL; char_md.p_cccd_md = NULL; char_md.p_sccd_md = NULL; // Define the LED 2 Characteristic UUID ble_uuid.type = p_led_service->uuid_type; ble_uuid.uuid = BLE_UUID_LED_2_CHAR_UUID; // Set permissions on the Characteristic value BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); // Attribute Metadata settings attr_md.vloc = BLE_GATTS_VLOC_STACK; attr_md.rd_auth = 0; attr_md.wr_auth = 0; attr_md.vlen = 0; // Attribute Value settings attr_char_value.p_uuid = &ble_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = sizeof(uint8_t); attr_char_value.init_offs = 0; attr_char_value.max_len = sizeof(uint8_t); attr_char_value.p_value = NULL; return sd_ble_gatts_characteristic_add(p_led_service->service_handle, &char_md, &attr_char_value, &p_led_service->led_2_char_handles); } uint32_t ble_led_service_init(ble_led_service_t * p_led_service, const ble_led_service_init_t * p_led_service_init) { uint32_t err_code; ble_uuid_t ble_uuid; // Initialize service structure p_led_service->conn_handle = BLE_CONN_HANDLE_INVALID; // Initialize service structure. p_led_service->led_write_handler = p_led_service_init->led_write_handler; // Add service UUID ble_uuid128_t base_uuid = {BLE_UUID_LED_SERVICE_BASE_UUID}; err_code = sd_ble_uuid_vs_add(&base_uuid, &p_led_service->uuid_type); if (err_code != NRF_SUCCESS) { return err_code; } // Set up the UUID for the service (base + service-specific) ble_uuid.type = p_led_service->uuid_type; ble_uuid.uuid = BLE_UUID_LED_SERVICE_UUID; // Set up and add the service err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_led_service->service_handle); if (err_code != NRF_SUCCESS) { return err_code; } // Add the different characteristics in the service: // Button press characteristic: E54B0002-67F5-479E-8711-B3B99198CE6C err_code = led_2_char_add(p_led_service); if (err_code != NRF_SUCCESS) { return err_code; } return NRF_SUCCESS; } void ble_led_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context) { ble_led_service_t * p_led_service = (ble_led_service_t *)p_context; switch (p_ble_evt->header.evt_id) { case BLE_GAP_EVT_CONNECTED: on_connect(p_led_service, p_ble_evt); break; case BLE_GATTS_EVT_WRITE: on_write(p_led_service, p_ble_evt); break; case BLE_GAP_EVT_DISCONNECTED: on_disconnect(p_led_service, p_ble_evt); break; default: // No implementation needed. break; } } |
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
1234567#define BLE_LED_SERVICE_BLE_OBSERVER_PRIO 2#define BLE_LED_SERVICE_DEF(_name) \static ble_led_service_t _name; \NRF_SDH_BLE_OBSERVER(_name ## _obs, \BLE_LED_SERVICE_BLE_OBSERVER_PRIO, \ble_led_service_on_ble_evt, &_name) - Lines 41-53: define the UUIDs for the LED Service and the LED 2 Characteristic
12345678910111213// LED service: E54B0001-67F5-479E-8711-B3B99198CE6C// LED 2 characteristic: E54B0002-67F5-479E-8711-B3B99198CE6C// The bytes are stored in little-endian format, meaning the// Least Significant Byte is stored first// (reversed from the order they're displayed as)// Base UUID: E54B0000-67F5-479E-8711-B3B99198CE6C#define BLE_UUID_LED_SERVICE_BASE_UUID {0x6C, 0xCE, 0x98, 0x91, 0xB9, 0xB3, 0x11, 0x87, 0x9E, 0x47, 0xF5, 0x67, 0x00, 0x00, 0x4B, 0xE5}// Service & characteristics UUIDs#define BLE_UUID_LED_SERVICE_UUID 0x0001#define BLE_UUID_LED_2_CHAR_UUID 0x0002 - Lines 55-65: define the led write handler function prototype and the LED service initialization data structure
1234567891011// Forward declaration of the custom_service_t type.typedef struct ble_led_service_s ble_led_service_t;typedef void (*ble_led_service_led_write_handler_t) (uint16_t conn_handle, ble_led_service_t * p_led_service, uint8_t new_state);/** @brief LED Service init structure. This structure contains all options and data needed for* initialization of the service.*/typedef struct{ble_led_service_led_write_handler_t led_write_handler; /**< Event handler to be called when the LED Characteristic is written. */} ble_led_service_init_t; - Lines 67-79: define the main LED service data structure that stores all the information relevant to the service
12345678910111213/**@brief LED Service structure.* This contains various status information* for the service.*/typedef struct ble_led_service_s{uint16_t conn_handle;uint16_t service_handle;uint8_t uuid_type;ble_gatts_char_handles_t led_2_char_handles;ble_led_service_led_write_handler_t led_write_handler;} ble_led_service_t; - Lines 81-100: declare the functions needed for initializing the service as well as the event handler
1234567891011121314151617181920// Function Declarations/**@brief Function for initializing the LED Service.** @param[out] p_led_service LED Service structure. This structure will have to be supplied by* the application. It will be initialized by this function, and will later* be used to identify this particular service instance.** @return NRF_SUCCESS on successful initialization of service, otherwise an error code.*/uint32_t ble_led_service_init(ble_led_service_t * p_led_service, const ble_led_service_init_t * p_led_service_init);/**@brief Function for handling the application's BLE stack events.** @details This function handles all events from the BLE stack that are of interest to the LED Service.** @param[in] p_ble_evt Event received from the BLE stack.* @param[in] p_context LED Service structure.*/void ble_led_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context);
led_service.c
- Lines 18-23: include the necessary header files as well as define the string
123456#include <string.h>#include "nrf_log.h"#include "led_service.h"static const uint8_t LED2CharName[] = "LED 2"; - Lines 25-44: define the functions for handling the connection and disconnection events
1234567891011121314151617181920/**@brief Function for handling the Connect event.** @param[in] p_led_service LED service structure.* @param[in] p_ble_evt Event received from the BLE stack.*/static void on_connect(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt){p_led_service->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;}/**@brief Function for handling the Disconnect event.** @param[in] p_bas LED service structure.* @param[in] p_ble_evt Event received from the BLE stack.*/static void on_disconnect(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt){UNUSED_PARAMETER(p_ble_evt);p_led_service->conn_handle = BLE_CONN_HANDLE_INVALID;} - Lines 46-61: define the function for handling the BLE write event
12345678910111213141516/**@brief Function for handling the Write event.** @param[in] p_led_service LED Service structure.* @param[in] p_ble_evt Event received from the BLE stack.*/static void on_write(ble_led_service_t * p_led_service, ble_evt_t const * p_ble_evt){ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;if ( (p_evt_write->handle == p_led_service->led_2_char_handles.value_handle)&& (p_evt_write->len == 1)&& (p_led_service->led_write_handler != NULL)){p_led_service->led_write_handler(p_ble_evt->evt.gap_evt.conn_handle, p_led_service, p_evt_write->data[0]);}} - Lines 63-112: define the function for adding the LED 2 Characteristic
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950/**@brief Function for adding the LED 2 characteristic.**/static uint32_t led_2_char_add(ble_led_service_t * p_led_service){ble_gatts_char_md_t char_md;ble_gatts_attr_t attr_char_value;ble_gatts_attr_md_t attr_md;ble_uuid_t ble_uuid;memset(&char_md, 0, sizeof(char_md));memset(&attr_md, 0, sizeof(attr_md));memset(&attr_char_value, 0, sizeof(attr_char_value));char_md.char_props.read = 1;char_md.char_props.write = 1;char_md.p_char_user_desc = LED2CharName;char_md.char_user_desc_size = sizeof(LED2CharName);char_md.char_user_desc_max_size = sizeof(LED2CharName);char_md.p_char_pf = NULL;char_md.p_user_desc_md = NULL;char_md.p_cccd_md = NULL;char_md.p_sccd_md = NULL;// Define the LED 2 Characteristic UUIDble_uuid.type = p_led_service->uuid_type;ble_uuid.uuid = BLE_UUID_LED_2_CHAR_UUID;// Set permissions on the Characteristic valueBLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);// Attribute Metadata settingsattr_md.vloc = BLE_GATTS_VLOC_STACK;attr_md.rd_auth = 0;attr_md.wr_auth = 0;attr_md.vlen = 0;// Attribute Value settingsattr_char_value.p_uuid = &ble_uuid;attr_char_value.p_attr_md = &attr_md;attr_char_value.init_len = sizeof(uint8_t);attr_char_value.init_offs = 0;attr_char_value.max_len = sizeof(uint8_t);attr_char_value.p_value = NULL;return sd_ble_gatts_characteristic_add(p_led_service->service_handle, &char_md,&attr_char_value,&p_led_service->led_2_char_handles);} - Lines 114-153: define the function used to initialize the LED Service
12345678910111213141516171819202122232425262728293031323334353637383940uint32_t ble_led_service_init(ble_led_service_t * p_led_service, const ble_led_service_init_t * p_led_service_init){uint32_t err_code;ble_uuid_t ble_uuid;// Initialize service structurep_led_service->conn_handle = BLE_CONN_HANDLE_INVALID;// Initialize service structure.p_led_service->led_write_handler = p_led_service_init->led_write_handler;// Add service UUIDble_uuid128_t base_uuid = {BLE_UUID_LED_SERVICE_BASE_UUID};err_code = sd_ble_uuid_vs_add(&base_uuid, &p_led_service->uuid_type);if (err_code != NRF_SUCCESS){return err_code;}// Set up the UUID for the service (base + service-specific)ble_uuid.type = p_led_service->uuid_type;ble_uuid.uuid = BLE_UUID_LED_SERVICE_UUID;// Set up and add the serviceerr_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_led_service->service_handle);if (err_code != NRF_SUCCESS){return err_code;}// Add the different characteristics in the service:// Button press characteristic: E54B0002-67F5-479E-8711-B3B99198CE6Cerr_code = led_2_char_add(p_led_service);if (err_code != NRF_SUCCESS){return err_code;}return NRF_SUCCESS;} - 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)
1234567891011121314151617181920212223void ble_led_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context){ble_led_service_t * p_led_service = (ble_led_service_t *)p_context;switch (p_ble_evt->header.evt_id){case BLE_GAP_EVT_CONNECTED:on_connect(p_led_service, p_ble_evt);break;case BLE_GATTS_EVT_WRITE:on_write(p_led_service, p_ble_evt);break;case BLE_GAP_EVT_DISCONNECTED:on_disconnect(p_led_service, p_ble_evt);break;default:// No implementation needed.break;}}
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:
12345// <e> BLE_BAS_ENABLED - ble_bas - Battery Service//==========================================================#ifndef BLE_BAS_ENABLED#define BLE_BAS_ENABLED 1#endif - 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)
1234// <o> NRF_SDH_BLE_VS_UUID_COUNT - The number of vendor-specific UUIDs.#ifndef NRF_SDH_BLE_VS_UUID_COUNT#define NRF_SDH_BLE_VS_UUID_COUNT 2#endif
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#ifndef BATTERY_VOLTAGE_H__ #define BATTERY_VOLTAGE_H__ #include /**@brief Function for initializing the battery voltage module. */ void battery_voltage_init(void); /**@brief Function for reading the battery voltage. * * @param[out] p_vbatt Pointer to the battery voltage value. */ void battery_voltage_get(uint16_t * p_vbatt); #endif // BATTERY_VOLTAGE_H__ |
battery_voltage.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#include "battery_voltage.h" #include "nrf_drv_saadc.h" #include "sdk_macros.h" #include "nrf_log.h" #define ADC_REF_VOLTAGE_IN_MILLIVOLTS 600 //!< Reference voltage (in milli volts) used by ADC while doing conversion. #define DIODE_FWD_VOLT_DROP_MILLIVOLTS 270 //!< Typical forward voltage drop of the diode (Part no: SD103ATW-7-F) that is connected in series with the voltage supply. This is the voltage drop when the forward current is 1mA. Source: Data sheet of 'SURFACE MOUNT SCHOTTKY BARRIER DIODE ARRAY' available at www.diodes.com. #define ADC_RES_10BIT 1024 //!< Maximum digital value for 10-bit ADC conversion. #define ADC_PRE_SCALING_COMPENSATION 6 //!< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage. #define ADC_RESULT_IN_MILLI_VOLTS(ADC_VALUE) \ ((((ADC_VALUE) *ADC_REF_VOLTAGE_IN_MILLIVOLTS) / ADC_RES_10BIT) * ADC_PRE_SCALING_COMPENSATION) static nrf_saadc_value_t adc_buf; //!< Buffer used for storing ADC value. static uint16_t m_batt_lvl_in_milli_volts; //!< Current battery level. /**@brief Function handling events from 'nrf_drv_saadc.c'. * * @param[in] p_evt SAADC event. */ static void saadc_event_handler(nrf_drv_saadc_evt_t const * p_evt) { if (p_evt->type == NRF_DRV_SAADC_EVT_DONE) { nrf_saadc_value_t adc_result; adc_result = p_evt->data.done.p_buffer[0]; m_batt_lvl_in_milli_volts = ADC_RESULT_IN_MILLI_VOLTS(adc_result) + DIODE_FWD_VOLT_DROP_MILLIVOLTS; NRF_LOG_INFO("ADC reading - ADC:%d, In Millivolts: %d\r\n", adc_result, m_batt_lvl_in_milli_volts); } } void battery_voltage_init(void) { ret_code_t err_code = nrf_drv_saadc_init(NULL, saadc_event_handler); APP_ERROR_CHECK(err_code); nrf_saadc_channel_config_t config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD); err_code = nrf_drv_saadc_channel_init(0, &config); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_sample(); APP_ERROR_CHECK(err_code); } void battery_voltage_get(uint16_t * p_vbatt) { VERIFY_PARAM_NOT_NULL_VOID(p_vbatt); *p_vbatt = m_batt_lvl_in_milli_volts; if (!nrf_drv_saadc_is_busy()) { ret_code_t err_code = nrf_drv_saadc_buffer_convert(&adc_buf, 1); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_sample(); APP_ERROR_CHECK(err_code); } } |
To be able to read the battery level, we have to set up a timer to trigger the reading of the battery voltage in the application’s main.c file.
Here are the changes we need to make to implement this functionality:
- Define the battery timer and the battery level update period:
1234/**< Battery timer. */APP_TIMER_DEF(m_battery_timer_id);#define BATTERY_LEVEL_MEAS_INTERVAL APP_TIMER_TICKS(120000) /**< Battery level measurement interval (ticks). */ - Declare two functions for handling the timeout and another for updating the battery level:
123456789101112131415161718/**@brief Function for handling the Battery measurement timer timeout.** @details This function will be called each time the battery level measurement timer expires.** @param[in] p_context Pointer used for passing some arbitrary information (context) from the* app_start_timer() call to the timeout handler.*/static void battery_level_meas_timeout_handler(void * p_context){UNUSED_PARAMETER(p_context);NRF_LOG_INFO("Battery Level timeout event");// Only send the battery level update if we are connectedif (m_conn_handle != BLE_CONN_HANDLE_INVALID){battery_level_update();}}
12345678910111213141516171819202122/**@brief Function for updating the Battery Level measurement*/static void battery_level_update(void){ret_code_t err_code;uint8_t battery_level;uint16_t vbatt; // Variable to hold voltage readingbattery_voltage_get(&vbatt); // Get new battery voltagebattery_level = battery_level_in_percent(vbatt); //Transform the millivolts value into battery level percent.printf("ADC result in percent: %d\r\n", battery_level);err_code = ble_bas_battery_level_update(&m_bas, battery_level, m_conn_handle);if ((err_code != NRF_SUCCESS) &&(err_code != NRF_ERROR_INVALID_STATE) &&(err_code != NRF_ERROR_RESOURCES) &&(err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)){APP_ERROR_HANDLER(err_code);}} - Next, we need to add the timer that triggers the battery level read operation. We add this to the timers_init() function:
12345678910111213141516/**@brief Function for the Timer initialization.** @details Initializes the timer module. This creates and starts application timers.*/static void timers_init(void){// Initialize timer module.ret_code_t err_code = app_timer_init();APP_ERROR_CHECK(err_code);// Create timers.err_code = app_timer_create(&m_battery_timer_id,APP_TIMER_MODE_REPEATED,battery_level_meas_timeout_handler);APP_ERROR_CHECK(err_code);} - Add a function to start the timer, and then call it from main()
12345678910/**@brief Function for starting application timers.*/static void application_timers_start(void){uint32_t err_code;// Start application timers.err_code = app_timer_start(m_battery_timer_id, BATTERY_LEVEL_MEAS_INTERVAL, NULL);APP_ERROR_CHECK(err_code);} - Enable the SAADC (Analog to digital converter), needed for reading the battery voltage level. Open sdk_config.h and enable the SAADC in two locations:
12345// <e> NRFX_SAADC_ENABLED - nrfx_saadc - SAADC peripheral driver//==========================================================#ifndef NRFX_SAADC_ENABLED#define NRFX_SAADC_ENABLED 1#endif
12345// <e> SAADC_ENABLED - nrf_drv_saadc - SAADC peripheral driver - legacy layer//==========================================================#ifndef SAADC_ENABLED#define SAADC_ENABLED 1#endif - Add the SAADC driver file. Right click on “nRF_Drivers” and click on “Add Existing File“. Then navigate to “nRF5_SDK_current/modules/nrfx/drivers/src/” and select the file “nrfx_saadc.c“
Initializing the Services
Finally, we need to initialize the LED and Battery Services in the main file (main.c).
To do that, we implement the following function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/**@brief Function for initializing services that will be used by the application. */ static void services_init(void) { uint32_t err_code; ble_bas_init_t bas_init; ble_led_service_init_t led_init; nrf_ble_qwr_init_t qwr_init = {0}; // Initialize Queued Write Module. qwr_init.error_handler = nrf_qwr_error_handler; err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init); APP_ERROR_CHECK(err_code); // 1. Initialize the LED service led_init.led_write_handler = led_write_handler; err_code = ble_led_service_init(&m_led_service, &led_init); APP_ERROR_CHECK(err_code); // 2. Initialize Battery Service. memset(&bas_init, 0, sizeof(bas_init)); // Here the sec level for the Battery Service can be changed/increased. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_char_attr_md.cccd_write_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_char_attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&bas_init.battery_level_char_attr_md.write_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_report_read_perm); bas_init.evt_handler = NULL; bas_init.support_notification = true; bas_init.p_report_ref = NULL; bas_init.initial_batt_level = 100; err_code = ble_bas_init(&m_bas, &bas_init); APP_ERROR_CHECK(err_code); } |
The function initializes both Services as well as defines the permissions of the Battery Service and Battery Level Characteristic.
Now, this needs to gets called from the main function main():
1 2 3 4 5 |
gap_params_init(); gatt_init(); services_init(); advertising_init(); conn_params_init(); |
Flashing and debugging
Before we build and flash our program to the development kit (nRF52840), we want to enable logging first so we can better follow the state of events and the state of our application.
To get this working:
- Open sdk_config.h and make sure the following macros are set to the correct debug level (4). This level will enable all debug statements in the debug terminal within SES when you run your application.
1234567891011// <o> NRF_LOG_DEFAULT_LEVEL - Default Severity level// <0=> Off// <1=> Error// <2=> Warning// <3=> Info// <4=> Debug#ifndef NRF_LOG_DEFAULT_LEVEL#define NRF_LOG_DEFAULT_LEVEL 4#endif
Now, we can flash the application to the development kit.
To do this:
- Right click on “Project ‘ble_lightbulb’“
- Select “Build“
- Right-click again on “Project ‘ble_lightbulb’“
- Select “Debug → Start Debugging“
- The application will start and stop at main() waiting for you to continue execution
- You should now see the output in the debug terminal similar to the following:
Testing
Our next and final step is to test our application and make sure it’s working fine. For this, we will use a mobile phone app that acts as a BLE Central.
For this example, we’ll be using an app called LightBlue (available for iOS and Android).
Here’s a video showing the application running on the development kit:
Summary
In this post, we went over how to build a full BLE peripheral application that you can use as a starting point for developing any BLE application on the nRF52 platform. We went over:
- Setting up the Project and Solution file for the application
- Fully implementing a BLE application that allows you to turn ON/OFF an LED on the nRF52840 development kit
- Building, flashing, and debugging the application on the nRF52840 development kit
- Testing the application from a mobile phone app to make sure it is functioning correctly
To be notified when future blog posts are published here on the Novel Bits blog, be sure to enter your email address in the form below!
Nice work, but I think you need to double check your code samples: I found several missing definitions and the source for the led_service.c file does not match the sections you split out for explanations (e.g. check for LED2 vs LED1), making me wonder if you used the wrong versions at some point.
Likewise, you missed documenting a few steps in editing the project file,like making sure the current directory is in the include path.
That said, I really appreciate an extensive example. Thanks.
Jim, thanks for the feedback.
I updated the listed source code to match the downloadable code — I had at some point fixed some issues (such as the LED1 vs. LED2 mismatch) in the source code but forgot to update the post to include the changes.
I don’t see any missing definitions locally on my computer, can you tell me which ones you found missing so I could include them? Also, I didn’t have to do anything with the include path, can you list the changes you had to make to the project file?
Thanks again!
hi Mohammad, I was excited to find your article and step-by-step tutorial as I am new to Nordic BLE.
Unfortunately I am stuck in your “How to build the simplest nRF52 BLE Peripheral application (Lightbulb”
use case) lesson. I downloaded your code. Created the similar environment per your lesson. But cannot
compile your exact code. I am getting following errors and need some help….Thank you
I did not do any modification to your code or environment setting but duplicated exactly per your lesson
————————————————————————————————————
Building ‘ble_lightbulb’ from solution ‘ble_lightbulb_pca10056_s140’ in configuration ‘Debug’
1> Compiling ‘nrf_log_backend_serial.c’
2> Compiling ‘nrf_log_backend_rtt.c’
1> In file included from C:\Users\jsaye\Desktop\nordic-BLE-5-Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_serial.c:40:0:
1> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
1> compilation terminated.
3> Compiling ‘nrf_log_backend_uart.c’
2> In file included from C:\Users\jsaye\Desktop\nordic-BLE-5-Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_rtt.c:40:0:
2> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
2> compilation terminated.
3> In file included from C:\Users\jsaye\Desktop\nordic-BLE-5-Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_uart.c:40:0:
3> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
3> compilation terminated.
Build failed
Hi Jawed,
Did you check if the sdk_config.h file exists in the main lightbulb folder? What SDK version are you using?
Hi Mohammad, Looks like my previous comment was removed, Any reason or so? -thx
Hi Mohammad, Thank you for your quick response.
1. I am using nR5_SDK_15.0.0_a53641a
2. Yes sdk_config.h file exists under the main lightbulb folder.
Here is what I did:
a. create a directory named nordic_BLE_Dev
b. downloaded the SDK_15.0,0_a53641a under nordic_BLE_Dev directory
c. renamed the SDK to nRF5_SDK_current
d. downloaded lightblub from your code link, unzipped it and copied it under nordic_BLE_Dev
e. As it is exact downloaded copy of your code – it has sdk_config.h under the main lightbulb directory
.f. run the project file and got errors. I tried it again and still got same errors.
Building ‘ble_lightbulb’ from solution ‘ble_lightbulb_pca10056_s140’ in configuration ‘Debug’
1> Assembling ‘thumb_crt0.s’
2> Compiling ‘nrf_log_backend_rtt.c’
3> Compiling ‘nrf_log_backend_serial.c’
2> In file included from C:\Users\jsaye\Desktop\nordic_BLE_Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_rtt.c:40:0:
2> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
2> compilation terminated.
4> Compiling ‘nrf_log_backend_uart.c’
3> In file included from C:\Users\jsaye\Desktop\nordic_BLE_Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_serial.c:40:0:
3> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
3> compilation terminated.
4> In file included from C:\Users\jsaye\Desktop\nordic_BLE_Dev\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_uart.c:40:0:
4> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
4> compilation terminated.
Build failed
Hi Jawed,
The only thing I can think of is that the include path for the project does not include the root path for the project. Can you open the solution file (*.emProject file) in a text editor and check if the following exists:
c_user_include_directories=”./
You can also try changing it to use the full absolute path instead of the relative one. Also, make sure the file is included in the Project in SES.
-Mohammad
I added the following to the list of directories and the compiler was able to find the sdk_config.h file and sucessfully compile.
c_user_include_directories = “../ble_lightbulb;
Thanks, Walter and Carl.
The issue seems to occur on Windows only. Here’s the fix: (I’ll fix this in the downloadable project as well)
Modify the Project file (ble_lightbulb_pca10056_s140.emProject) to
“ c_user_include_directories=”.;../nRF5_SDK_current/component”
Instead of
“ c_user_include_directories=”./;../nRF5_SDK_current/component”
So, basically just remove the trailing “/” after the “.”. For some reason, Windows does not like it.
Thanks again.
-Mohammad
Hi Mohammad, Thanks again.
What you mentioned in your email is not possible because my project is exact copy of yours. So you would have see these issues before I. Anyway.. I was able to move forward and compile your project without errors.
To solve my previous problem, I had to:
a. create a sub-directory call “config” same level as my main.c file.
b. move the sdk_config.h file in the new config directory.
c. add “./config” in the emProject file.
I have tried but I don’t know why sdk_config.h needs to be under the config directory.
it does not work without it, even after you change all the paths (2 path). Even if you hard code the paths.
I am okay with a config directory infact I prefer it. so i call this issue resolved.
Now I have issues in the main.c file, linker cannot find functions. (see below) I will work on it later today
and let you know, most probably some additional path issues in my environment.
Do you know – when I installed the Sagger Embedded studio, do I need to set some paths in window 10?
Thanks
…jsayed
— Errors ——
1> Output/ble_itaalaLock Debug/Obj/main.o:C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock/main.c:140: undefined reference to
ble_bas_on_ble_evt'
services_init’:1> Output/ble_itaalaLock Debug/Obj/main.o: In function
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock/main.c:292: undefined reference to
ble_bas_init'
battery_level_update’:1> Output/ble_itaalaLock Debug/Obj/main.o: In function
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock/main.c:670: undefined reference to
ble_bas_battery_level_update'
nrf_drv_saadc_init’:1> Output/ble_itaalaLock Debug/Obj/battery_voltage.o: In function
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock/../nRF5_SDK_current/integration/nrfx/legacy/nrf_drv_saadc.h:134: undefined reference to
nrfx_saadc_init'
battery_voltage_init’:1> Output/ble_itaalaLock Debug/Obj/battery_voltage.o: In function
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:82: undefined reference to
nrfx_saadc_channel_init'
nrfx_saadc_buffer_convert’1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:85: undefined reference to
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:88: undefined reference to
nrfx_saadc_sample'
battery_voltage_get’:1> Output/ble_itaalaLock Debug/Obj/battery_voltage.o: In function
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:97: undefined reference to
nrfx_saadc_is_busy'
nrfx_saadc_buffer_convert’1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:99: undefined reference to
1> C:\Users\jsaye\Desktop\nordic_BLE_Dev\ble_itaalaLock\Battery Level/battery_voltage.c:102: undefined reference to `nrfx_saadc_sample’
Build failed
Hi Jawed,
Unfortunately, I do not have a Windows machine to test this with at the moment. You may find some helpful instructions here though:
https://www.segger.com/downloads/embedded-studio/EmbeddedStudio_Manual
https://www.youtube.com/watch?v=YZouRE_Ol8g&list=PLx_tBuQ_KSqGHmzdEL2GWEOeix-S5rgTV
Same problem
Building ‘ble_lightbulb’ from solution ‘ble_lightbulb_pca10056_s140’ in configuration ‘Debug’
1> Compiling ‘nrf_log_backend_serial.c’
2> Compiling ‘nrf_log_backend_rtt.c’
1> In file included from C:\BLE Projects\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_serial.c:40:0:
1> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
1> compilation terminated.
3> Compiling ‘nrf_log_backend_uart.c’
2> In file included from C:\BLE Projects\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_rtt.c:40:0:
2> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
2> compilation terminated.
3> In file included from C:\BLE Projects\nRF5_SDK_current\components\libraries\experimental_log\src\nrf_log_backend_uart.c:40:0:
3> ../nRF5_SDK_current/components/libraries/util/sdk_common.h:56:10: fatal error: sdk_config.h: No such file or directory
3> compilation terminated.
Build failed
Modify the Project file (ble_lightbulb_pca10056_s140.emProject) to
“ c_user_include_directories=”.;../nRF5_SDK_current/component”
Instead of
“ c_user_include_directories=”./;../nRF5_SDK_current/component”
So, basically just remove the trailer “/” after the “.”. For some reason, Windows does not like it.
Hi,
Thanks for the code. I tried a few things extra in this exercise like adding LED3 and LED4 into the code. I duplicated the format to initiate the LED2 for LED3 and LED4. The code compiled however The service did not come up on the app. Is there something I did wrong? I literally just copied the format for LED 3 and LED 4.
Thanks,
Rahmat
Hi,
Since Nordic published the new version of SDK (15.2.0), I find a lot of problem while trying to compile the code downloaded from your link. Mainly two problems : 1) directory path error, which is may caused by the path changing of the SDK; 2) Function and structure elements name error, which is also may caused by the declaration/definition differences between the new version SDK and the old one you used. I cannot attach the bug logout here because the number of errors is too too many. Maybe the project can work before, but I really spend a lot of time on fixing the bugs, and finally give up because I can’t quiet understand what the code try to do whiling using the SDK especially with so many directory path and definition errors . I’m a new guy for using the nRF52840 kit, and really thanks for your instruction of how to start a simple BLE application. If convenient, please update the source code with new version SDK (I cannot find the old version 15.0.0 from the website), or combine the code and the SDK used for the code in one file. Thanks a lot.
Best
Bo
Hi Bo,
Thanks for your feedback. I’ll work on updating the examples for SDK version 15.2.0. In the meantime, you can actually download the 15.0.0 SDK at https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/
Thanks,
Mohammad
Hi Mohammad,
I have project code ported to the current version of Nordic SDK (15.2.0). I can share it on my Github account.
Thanks, Robert! That would be great!
The code is available in the repo: https://github.com/gruberski/ble_lightbulb
Thanks for sharing the code, Robert!