Getting a Simple On/Off BLE Peripheral Example To Work

I was getting a little frustrated with the Nordic examples and tutorials all being not quite ready for use. Not as badly frustrated as this guy but there always seemed to be some mismatch between what was required and what I was using, even when I chose the preferred tools. OK, I made a mistake assuming that I should start with the nRF51-DK because that is a smaller, simpler, cheaper chip.

nRF51 vs nRF52 features

Although when you look into pricing, you find that nRF51's are not necessarily cheaper

Some nRF chips features and pricings

So I got myself onto the nRF52-DK so that I could use the latest SDK 16 and the Segger SES toolchain, which meets all the requirements in the Getting Starting guide. After going through that, I wanted to find a tutorial that was written from outside Nordic Semi so that it could be more objective about the tools. Given that Mohammad Afaneh is presenting these Ellisys BT videos  he seems to be an independent (of Nordic) expert, so his post on How to build the simplest nRF52 BLE Peripheral application (Lightbulb use case) seemed to be a good fit with what I wanted to learn and how to learn it. The rest of this post is my attempt to implement that example.

The first hurdle was that he uses the nRF52840 Dev Kit, which I didn't have, although I have the nRF52840 Dongle it is not the same thing, and the nRF52-DK which is not identical and uses the nRF52832 chip.

The second hurdle was that he is using the nRF5 SDK version 15, but the latest is version 16. That is ok, I can download version 15 and it is compatible with the my dev kit and Segger SES. Also note that the requirements include a mobile phone running nRF Connect or LightBlue by PunchThrough, and I had an iPhone 6s with both of these apps installed, but that each of these is problematic as we will see later.

Mohammad sets up this project next to rather than within the SDK folders, so that the SDK can be independently upgraded. I can see the sense in this, but it means a lot of editing of paths in the project file which is really not necessary if we just leave the project in the SDK\examples\ble_peripheral folder, which could be justified as this is, after all, an example, just not one provided by Nordic. So I left my project in the SDK\examples\ble_peripheral to avoid having to change all the paths.

Since I was using the nRF52-DK, I used the pca10040 folder and also left the files in the same relative locations as the ble_app_template, for consistency.

Changes Required to Build

Where he says change advertising_start(erase_bonds); to advertising_init();
I think this is wrong - should be to advertising_start(); and that the function prototype needs to be changed to take a void as well. Otherwise advertising_start never gets called. This is borne out by the downloadable source code.

It is not clear that application_timers_start and services_init are replacement definitions, not additional.

BLE_BAS_DEF(m_bas); was omitted from top of main but is admitted in comment Oct 31, 2019.

Since he doesn't use function prototypes for static functions in the blog post, it is critical that they are defined before being used (referenced), otherwise you will get errors like

'nrf_qwr_error_handler' undeclared (first use in this function); did you mean 'app_error_handler'?
'led_write_handler' undeclared (first use in this function)

so led_write_handler needs to be above services_init() 

However, the downloaded source code does have function prototypes so the order of the function definitions can be different.

Fixing Problems When Running in Debug

With the above changes, I found that the program immediately hits NRF_BREAKPOINT_COND; at line 99 in app_error_weak.c. At first I thought this was because I haven't yet made the changes required to move from nRF52840 to nRF52832 as per the comments Oct 25, 2019, but in fact since I started with a pca10040 project so those steps are already done.

If you use the linker_section_placement_macros and linker_section_placements_segments from the wrong source (not matching your chip/dev kit) you will not notice on compile & build but will get immediate crashes on running at reset such as "Stopped by vector crash", might be at 0x000008C8, with the Breakpoints pane showing ExceptionEntryReturnFault. Make sure these lines in particular match with a sample emProject file for your chip/dev kit eg PCA10040. Rebuild the project if you edit this file.

The next action was to compare to what we have so far by following the blog post to his project downloaded source code (available by email).

In main.c

#define BATTERY_LEVEL_MEAS_INTERVAL APP_TIMER_TICKS(30000) //30s instead of 120s

in gap_params_init() the appearance is set to unknown
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_UNKNOWN);

in main()

battery_voltage_init()was not called, should be called after timers_init()

in sdk_config.h, I needed to disable the peer manager and enable the RTT log backend, so change these definitions where you find them:


Note: If you are targetting SDK16 and SES 4.52a, you also need to edit another value in sdk_config.h to see the log output


then the logs will show that the reason the program immediately hits NRF_BREAKPOINT_COND; at line 99 in app_error_weak.c is

<warning> nrf_sdh_ble: Insufficient RAM allocated for the SoftDevice.
<warning> nrf_sdh_ble: Change the RAM start location from 0x20002218 to 0x20002230.
<warning> nrf_sdh_ble: Maximum RAM size for application is 0xDDD0.
<error> nrf_sdh_ble: sd_ble_enable() returned NRF_ERROR_NO_MEM.
<error> app: ERROR 4 [NRF_ERROR_NO_MEM] at C:\Bluetooth\Nordic_Semi\nRF5_SDK_current\examples\ble_peripheral\ble_lightbulb as per MA\main.c:481
PC at: 0x000307CF
<error> app: End of error report

Right click on Project - Options - Dropdown box for Common, Linker - Section Placement Macros 

Suggested placement macros

Project Starts Advertising

LED 1 quick flash every 2 sec and Debug Terminal shows

<info> app: BLE Lightbulb example started.
<info> app: Fast advertising.

iOS Apps Show Incorrect Services

But the iPhone apps LightBlue and nRF Connect show the UART service from the last application running on the dev kit, not the LED service or battery level characteristic.

LightBlue showing UART service 6E4 instead of LED service E54B


iOS nRF Connect showing Nordic UART service not LED service

Since two independent apps show the same data, it is natural to think that there the problem must lay with the firmware in the dev kit. After all, we have had so many problems with this project which needed correction from the original blog post that it would be the first place to check. After much checking and research, it turns out that it is a bug in the iOS Bluetooth stack that keeps hold of old information in its cache, which explains the effects being common to both apps. This might be in the iPhone Bluetooth firmware rather than iOS, in any case, it was found in iOS 12.4.1 on iPhone 6s.

One solution is to go into the settings on the iPhone, Bluetooth and turn it off and back on again. Disabling and reenebling the connection (white icon) is insufficient.

Using nRF Connect Desktop App with Dongle

How do we prove it is the iOS apps that are wrong? Using a nRF52840 Dongle with the nRF Connect desktop app, run the "Bluetooth Low Energy" App (as shown in this tutorial), select the device that is NOT the PCA10040 for your interface,

select nRF Dongle in nRF Connect for desktop

start a Scan and see the device "BLE_Lightbulb". (Options - Sort by signal strength can be helpful assuming your dongle is close to your dev kit). Before clicking Connect for the BLE_Lightbulb, click instead on Details to see what is being advertised.

nRF Connect for desktop showing ad data only device information

Once connected, the service information looks like this

nRF Connect for desktop showing services after connection

Although the initial value for the LED Service is 7C, writing 01 to turn the LED on and 00 to turn it off does work.

Attempting to solve the iOS wrong services display

When running this project, it was noticed that the log showed that the Central, iOS, requested an MTU of 185 bytes but the project is set for 23 bytes max. To see if this is the cause of the iOS wrong services display, after adding

    err_code=sd_ble_gatts_exchange_mtu_reply(p_ble_evt->evt.gatts_evt.conn_handle, 23);
to ble_evt_handler()  as mentioned in this post.

Produces this error log
<debug> nrf_ble_gatt: Peer on connection 0x0 requested an ATT MTU of 185 bytes.
<debug> nrf_ble_gatt: Updating ATT MTU to 23 bytes (desired: 23) on connection 0x0.
<error> app: ERROR 8 [NRF_ERROR_INVALID_STATE] at C:\Bluetooth\Nordic_Semi\nRF5_SDK_current\examples\ble_peripheral\ble_lightbulb as per MA\main.c:457
PC at: 0x00030763

but adding cases to ble_evt_handler() from the downloaded source code of the project,

    // Pairing not supported
    err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
    // No system attributes have been stored.
    err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);

and changing NRF_SDH_BLE_GATT_MAX_MTU_SIZE to 185, now produces an error log

<warning> nrf_sdh_ble: Insufficient RAM allocated for the SoftDevice.
<warning> nrf_sdh_ble: Change the RAM start location from 0x20002230 to 0x200028F8.
<warning> nrf_sdh_ble: Maximum RAM size for application is 0xD708. was DDD0
<error> nrf_sdh_ble: sd_ble_enable() returned NRF_ERROR_NO_MEM.
<error> app: ERROR 4 [NRF_ERROR_NO_MEM] at C:\Bluetooth\Nordic_Semi\nRF5_SDK_current\examples\ble_peripheral\ble_lightbulb as per MA\main.c:502
PC at: 0x0003092F

Updating the Section Placement Macros as indicated, by right click on Project - Options - Dropdown box for Common, Linker - Section Placement Macros solved this error. 

It now works in that MTU update is successful but iOS apps still see the UART services 6E4...

Lesson here is that there are two options that work

1) keeping NRF_SDH_BLE_GATT_MAX_MTU_SIZE to 23 and not handling BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST in ble_evt_handler() or
2) changing NRF_SDH_BLE_GATT_MAX_MTU_SIZE to 185 as requested by iOS and handling BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST in ble_evt_handler as above.

This shows that increasing the MTU from 23 to 185 to suit iOS does not solve the wrong services display in the iOS apps. But I haven't fixing all the problems yet; the project does not advertise its services correctly. 

Advertising the Services

It should include the battery service in the advertising packets. Add the second line in this array so that the device advertises the standard battery service

static ble_uuid_t m_adv_uuids[] = /**< Universally unique service identifiers. */

We can see that the Battery Service is now advertised in the nRF Connect for Desktop and for iOS

nRF Connect for iOS showing battery service advertised

but the nRF Connect for iOS is still showing the phantom UART services (not present in that device)

To add the custom service to the scan response data, in advertising_init()
add these two lines after setting init.advdata structure

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

and add

static ble_uuid_t m_sr_uuids[] = 

at the top of main.c after definition of m_adv_uuids to include the vendor specific LED service in the advertising scan responses.

For this to work properly, main() needs to call service_init() then advertising_init(), in that order as recommended in this post

The custom LED service is now seen in nRF Connect in iOS, but nRF Connect for desktop now only shows the custom service, not the standard one in the same situation!

nRF Connect for desktop ONLY shows custom service

Meanwhile LightBlue shows the 3 services before connection takes place, and shows them in the list of advertisement data after connection, but is still showing the UUID of the phantom UART service 6E4... as well as the new LED service E54B...
LightBlue showing 3 services still with phantom UART service

Lesson here is that while nRF Connect for Desktop is generally more reliable at showing the correct information than the iOS apps, this is not always the case, so you need to check with both platforms.

Clearing the Phantom Services from iOS

  • Settings - Bluetooth - Off
  • Bluetooth - On
How to switch Bluetooth Off and On in iOS

Back to nRF Connect for iOS and LightBlue

nRF Connect for iOS showing the correct 3 services
nRF Connect for iOS showing correct 3 services
LightBlue showing the correct 3 services
LightBlue showing correct 3 services

Both iOS apps and the nRF Connect for desktop can control the LED state and read it back, and read the battery level.

Complexity of BLE Solution

Now that we have got all this to work, what is the code size?
nRF52 memory usage for BLE Lightbulb application on SDK15

This is about the simplest BLE peripheral project which has some practical use, and while it fits in the nRF52832-QFAA-R7 on the nRF52-DK which has 512k Flash and 64k RAM, that is a much bigger and more expensive (+80%) chip than the minimal nRF52810-QCAA-R at 192k Flash, 24k RAM.

It remain to be seen if this minimal project will fit on the nRF52810-QCAA-R or indeed the nRF51 chips with their 128k Flash and 16k RAM.


Thanks to David Meiklejohn for his nrf52test-led-battery project on GitHub which is a SDK16 port of the same project and which solves a few of the above problems in addition to the changes required for the different GAP connection security structure.

Thanks to MartinBL on the Nordic forums for the Tutorial on BLE Services which got me started on using nRF Connect for Desktop.