element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Code Exchange
  • Technologies
  • More
Code Exchange
Blog Creating a Button-LED BLE Application on a PAN1780 (nRF52840) development board using ARM Mbed OS v6.15
  • Blog
  • Forum
  • Documents
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Code Exchange to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: BigG
  • Date Created: 21 Feb 2022 10:13 PM Date Created
  • Views 6525 views
  • Likes 9 likes
  • Comments 2 comments
  • coding
  • ble
  • pan1780
  • mbedos
  • nrf52840
  • c++
Related
Recommended

Creating a Button-LED BLE Application on a PAN1780 (nRF52840) development board using ARM Mbed OS v6.15

BigG
BigG
21 Feb 2022

Introduction

Arm, the owners Mbed OS, has been in the news recently following the decision to withdraw from the Nvidia acquisition. Whilst this had nothing to do with Mbed OS itself, it did somehow renew my interest in this free open source Internet of Things (IoT) operating system.

It just so happened that I was in two minds deciding which Bluetooth engine to use for my dust monitoring road test, and so I decided to give the latest version (v6.15) of Mbed OS together with Mbed Studio (their dedicated desktop IDE) another try.

I liked it and as I had made considerable progress understanding how it all fits together I decided to develop this Bluetooth Low Energy (BLE) application and share the experience on Element14.

As many of you know, Arm Mbed OS has come a long way since Mbed OS was initially released in September 2009. It has not been a smooth journey mind you. I recall that Mbed OS 2 was very popular when it was released with it’s online compiler, open source libraries and shared examples. But when Mbed OS 5 was released it introduced so many breaking changes that this led to frustration and confusion as all the Mbed 2 code examples and libraries no longer worked. The website search engine also did not help as it kept referring you back to Mbed 2. Hence I gave up.

However, since the release of Mbed OS 6 (in June 2020), together with the launch of Mbed Studio and a redesign of the mbed website, things have improved noticeably, in my opinion. There is now much clearer and better structured documentation and there's a good core set of libraries, although it is nowhere as comprehensive as before.

So, in my opinion, it's a good time to jump back in as the website provides a pretty good explanation on how to get started with numerous tutorials and it also provides a good detailed reference on the various API’s available for use.

https://os.mbed.com/docs/mbed-os/v6.15/introduction/index.html

The MbedOS website also provides a useful explanation of the operating system's architecture to help you understand how it all fits together:

https://os.mbed.com/docs/mbed-os/v6.7/introduction/architecture.html

This includes a good explanation of the BLE API: https://os.mbed.com/docs/mbed-os/v6.15/apis/bluetooth-apis.html

Getting started with the BLE API

If you’re like me, then you will want to learn by example.

Thankfully there are a range of generic examples available, which are also referenced in the tutorials section (https://os.mbed.com/docs/mbed-os/v6.15/tutorials/index.html) and can be found on the Arm Mbed GitHub repo: https://github.com/ARMmbed/mbed-os-example-ble/

I initially found these examples quite difficult to follow, especially when unfamiliar with the BLE API. So to help, here is my take on how to make sense of these examples.

There are two architectural models or design approaches used.

The first is what I would refer to as the flat model, where you have one primary class for your app and then you link in other services and any API functions as required. The other is a more hierarchical model, which comprises three linked wrappers. The lowest layer handles BLE processes, the middle layer handles either server or client functions and the top layer, which you create yourself, defines the app class.

To understand these models and structures you need to refer to another Arm Mbed repository which contains these code wrappers, namely: https://github.com/ARMmbed/mbed-os-ble-utils

These wrappers are generic. The header file “ble_app.h” demonstrates the flat structure and the files “ble_process.h”, “gatt_client_process.h” and “gatt_server_process.h” create a hierarchical model.

Returning back to the ready made examples on GitHub, we can split these examples into the two camps. The hierarchical model is used in the two Gatt Client examples (“BLE_GattClient_Characteristicxxx”) and two Gatt Servers examples (start with the name “BLE_GattServer_Characteristicxxx”). All other examples use the flat structure.

So, with that out the way, let me jump straight into a simple example of my own.

Button-LED BLE App

I used the Panasonic PAN1780 (nRF52840) development board as my microcontroller. I have also road tested this evaluation kit, so if you want to learn more you can click here to read the review.

image

As you may have noticed, from the above picture, I used a SWD compliant Particle.io DAPlink debugger to flash the PAN1780, as Mbed Studio cannot use Segger Jlink by default. The nice thing about the PAN1780 development board is that the SWDIO and SWDCLK pins are broken out for you so it is very straightforward, as shown here:

image

The code for my Mbed OS example was developed on Mbed Studio version 1.4.3 with the Target configured as Nordic nRF52840-DK, as shown here:

image

My code example is based on a flat-file architecture or design approach and makes use of a modified ble_app.h header file, which I’ve named ble_app2.h, can be found here: https://github.com/Gerriko/mbedOS_blebuttonleddemo).

This modified header file includes Gatt Server event handlers to inform you when a central client device connects or disconnects, when notifications are enabled or disabled, and when there is a read or write request or when data is updated/sent.

With this new header file, developing the rest of the application code is very straightforward.

But before I get to the detail of my application code, there is some housekeeping needed to get the BLE code project to work, i.e. a mbed_app.json file is required to provide the compiler with the appropriate settings.

My mbed_app.json file contains parameters for enabling serial output via printf statements, BLE trace logging (disabled by default), maximum desire att mtu size, and then finally adding the BLE feature to the target board.

{
    "target_overrides": {
        "*": {
            "platform.stdio-baud-rate": 115200,
            "platform.stdio-buffered-serial": 1,
            "mbed-trace.enable": false,
            "mbed-trace.max-level": "TRACE_LEVEL_DEBUG",
            "cordio.desired-att-mtu": 48,
            "cordio.rx-acl-buffer-size": 96,
            "cordio.trace-hci-packets": false,
            "cordio.trace-cordio-wsf-traces": false,
            "ble.trace-human-readable-enums": false
        },
        "NRF52840_DK": {
            "target.features_add": ["BLE"]
        },
        "NRF52_DK": {
            "target.features_add": ["BLE"]
        }
    }

}

And now, here’s the app code.

/* mbed Microcontroller Application
 * Copyright (c) 2022 C Gerrish (Gerrikoio)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mbed.h"

#include "ble_app2.h"

// Handles for button and led and connection
uint8_t btnvaluehandle = 0;
uint8_t ledvaluehandle = 0;
uint8_t connectionhandle = 0;

// Initial values for button and led
uint8_t btnvalue = 0;
uint8_t prev_btnvalue = btnvalue;
uint8_t ledvalue = 0;

// Initialise the digital pin LED1 as an output
DigitalOut led(LED1);

// Initialise the button pin 
DigitalIn button(BUTTON1, PullUp);

Ticker BTN_ticker;

BLEApp app;

void BTN_tickerhandler()
{
    btnvalue = !button.read();

    if (btnvalue != prev_btnvalue) {
        app.updateCharacteristicValue(btnvaluehandle, btnvalue, sizeof(btnvalue));
    }    
    prev_btnvalue = btnvalue;
}



void bleApp_InitCompletehandler(BLE &ble, events::EventQueue &_event)
{
    printf("Setting up 2M PHY\r\n");
    /* setup the default phy used in connection to 2M to reduce power consumption */
    if (ble.gap().isFeatureSupported(ble::controller_supported_features_t::LE_2M_PHY)) {
        ble::phy_set_t phys(/* 1M */ false, /* 2M */ true, /* coded */ false);
        ble_error_t error = ble.gap().setPreferredPhys(/* tx */&phys, /* rx */&phys);
        
        /* PHY 2M communication will only take place if both peers support it */
        if (error) {
            print_error(error, "GAP::setPreferedPhys failed\r\n");
        }
    } else {
        /* otherwise it will use 1M by default */
        printf("2M not supported. Sticking with 1M PHY\r\n");
    }
}

void bleApp_Connectionhandler(BLE &ble, events::EventQueue &_event, const ble::ConnectionCompleteEvent &event)
{
    connectionhandle = event.getConnectionHandle();

    printf("Callback alert for Connection handle %u. Now connected to: ", connectionhandle);
    print_address(event.getPeerAddress());

    // Initialise a ticker to monitor button status
    BTN_ticker.attach(&BTN_tickerhandler, 250ms);

}

void bleApp_Disconnectionhandler(BLE &ble, events::EventQueue &_event, const ble::DisconnectionCompleteEvent &params)
{
    printf("Callback alert following Disconnection event.\r\n");

    // Detach ticker
    BTN_ticker.detach(); 
}

void bleApp_UpdatesEnabledhandler(const GattUpdatesEnabledCallbackParams &params)
{
    printf("Callback alert following Updates Enabled event.\r\n");
    
}

void bleApp_UpdatesDisabledhandler(const GattUpdatesDisabledCallbackParams &params)
{
    printf("Callback alert following Updates Disabled event.\r\n");
    
}

void bleApp_WriteEventhandler(const GattWriteCallbackParams &params)
{
    printf("Write Event callback alert via connection handle %u.\r\n", params.connHandle);
    if (params.handle == ledvaluehandle) {
        ledvalue = params.data[0];
        printf("Update LED to %s\r\n", ledvalue == 1 ? "ON" : "OFF");
        led = !ledvalue;
    }
    
}

void bleApp_ReadEventhandler(const GattReadCallbackParams &params)
{
    printf("Read Event callback alert via connection handle %u.\r\n", params.connHandle);
    if (params.handle == ledvaluehandle) printf("LED characteristic data read: %u\r\n", params.data[0]);
    else if (params.handle == btnvaluehandle) printf("BTN characteristic data read: %u\r\n", params.data[0]);
    
}

/**
    * Handler called when a notification or an indication has been sent.
    */
void onDataSenthandler(const GattDataSentCallbackParams &params)
{
    if (params.attHandle == btnvaluehandle) {
        printf("BTN changed to %s\r\n", btnvalue == 1 ? "Pressed" : "Released");
    }
}


void bleApp_MTUchangehandler(ble::connection_handle_t connectionHandle, uint16_t attMtuSize)
{
    printf("MTU change alert.\r\n");
    printf("connection handle: %u\r\n", connectionHandle);
    printf("New Mtu Size: %u\r\n", attMtuSize);
    
}


int main()
{
    // We declare these after the class definition
    const char *DEVICE_NAME =         "BtnLED";
    // We add the Gatt Service UUID to GattServerProcess as its used to advertise the service
    const char *GATTSERVICE_UUID =    "00001523-1212-efde-1523-785feabcd123";
    // Button and LED Characteristics
    const char *BUTTONCHAR_UUID =     "00001524-1212-efde-1523-785feabcd123";
    const char *LEDCHAR_UUID =        "00001525-1212-efde-1523-785feabcd123";

    printf("nRF52840 Button LED BLE Application\r\n");

    led = !ledvalue;                        // This LED works in reverse
    btnvalue = !button.read();               // Read the button GPIO value (active low so reverse)

    app.set_advertising_name(DEVICE_NAME);
    app.set_GattUUID_128(GATTSERVICE_UUID);

    // Create our Gatt Service Profile
    // For Button Characteristic, we add in an additional notification property
    ReadOnlyGattCharacteristic<uint8_t> btn_characteristic(UUID(BUTTONCHAR_UUID), &btnvalue, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
    ReadWriteGattCharacteristic<uint8_t> led_characteristic(UUID(LEDCHAR_UUID), &ledvalue);

    GattCharacteristic *charTable[] = { &btn_characteristic, & led_characteristic };
    GattService BLS_GattService(UUID(GATTSERVICE_UUID), charTable, sizeof(charTable) / sizeof(charTable[0]));
    
    // We now add in our button & led service
    app.add_new_gatt_service(BLS_GattService);

    // We set up all our optional Gatt Server event handlers   
    app.on_connect(bleApp_Connectionhandler);
    app.on_disconnect(bleApp_Disconnectionhandler);
    app.on_updatesenabled(bleApp_UpdatesEnabledhandler);
    app.on_updatesdisabled(bleApp_UpdatesDisabledhandler);
    app.on_serverwriteevent(bleApp_WriteEventhandler);
    app.on_serverreadevent(bleApp_ReadEventhandler);
    app.on_serversentevent(onDataSenthandler);
    app.on_AttMtuChange(bleApp_MTUchangehandler);

    btnvaluehandle = btn_characteristic.getValueHandle();
    ledvaluehandle = led_characteristic.getValueHandle();

    // We start our app and need to include a BLE Initialise Complete handler
    app.start(bleApp_InitCompletehandler);

    while (true) {
        ThisThread::sleep_for(1s);
    }
}

It’s worth noting a couple of things inside the code.

First, instead of using debounce and interrupts for the GPIO connected to the push button, I used a timer interrupt to poll the button state every 250 milliseconds. The ticker API is used, which is enabled when a ble device connects and is detached when the client device disconnects.

Ticker BTN_ticker;

Second, the BLEapp class (which is found in ble_app.h)  is instantiated as a global object.

BLEApp app;

The led and button objects are also defined as global.

// Initialise the digital pin LED1 as an output
DigitalOut led(LED1);
// Initialise the button pin as digital input with pullup
DigitalIn button(BUTTON1, PullUp);

Then inside the main() function, the GATT services and characteristics are defined.

// We declare these after the class definition
const char *DEVICE_NAME = "BtnLED";
// We add the Gatt Service UUID to GattServerProcess as its used to advertise the service
const char *GATTSERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
// Button and LED Characteristics
const char *BUTTONCHAR_UUID = "00001524-1212-efde-1523-785feabcd123";
const char *LEDCHAR_UUID = "00001525-1212-efde-1523-785feabcd123";

app.set_advertising_name(DEVICE_NAME);
app.set_GattUUID_128(GATTSERVICE_UUID);

// Create our Gatt Service Profile
// For Button Characteristic, we add in an additional notification property
ReadOnlyGattCharacteristic<uint8_t> btn_characteristic(UUID(BUTTONCHAR_UUID), &btnvalue, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
ReadWriteGattCharacteristic<uint8_t> led_characteristic(UUID(LEDCHAR_UUID), &ledvalue);

GattCharacteristic *charTable[] = { &btn_characteristic, & led_characteristic };
GattService BLS_GattService(UUID(GATTSERVICE_UUID), charTable, sizeof(charTable) / sizeof(charTable[0]));
// We now add in our button & led service
app.add_new_gatt_service(BLS_GattService);

Note that the set_GattUUID_128 and add_new_gatt_services are new functions created within ble_app2.h.

The ble_app library also makes use of an OOP concept called a “Chain-of-responsibility pattern”. Basically this concept is beyond my level of expertise to explain here. Hopefully wikipedia provides a good reference for those who want to know more: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern

This is why ble_app.h includes the headers ChainableGapEventHandler.h for the GAP and a new one ChainableGattServerEventHandler.h for the Gatt Server. This then allows you to access the GAP and Gatt Serve events via these two internal event handler declarations:

ChainableGapEventHandler _gap_handler;
ChainableGattServerEventHandler _gatt_server_handler;

In my example I have used specific callbacks to provide access to all the BLE application events:

app.on_connect(bleApp_Connectionhandler);
app.on_disconnect(bleApp_Disconnectionhandler);
app.on_updatesenabled(bleApp_UpdatesEnabledhandler);
app.on_updatesdisabled(bleApp_UpdatesDisabledhandler);
app.on_serverwriteevent(bleApp_WriteEventhandler);
app.on_serverreadevent(bleApp_ReadEventhandler);
app.on_serversentevent(onDataSenthandler);
app.on_AttMtuChange(bleApp_MTUchangehandler);

This makes the whole application event driven, which is great.

Finally, it’s worth noting how the application actually starts. It is via this function:

app.start(bleApp_InitCompletehandler);

Then within the bleApp_InitCompletehandler callback function, I decided to demonstrate the use of adding in some custom code to be triggered once initialisation is complete. Here I added in code to change the PHY settings (which is not really required for the app to function, it’s just here for show). This is all thanks to this OOP chainable wizardry.

printf("Setting up 2M PHY\r\n");
/* setup the default phy used in connection to 2M to reduce power consumption */
if (ble.gap().isFeatureSupported(ble::controller_supported_features_t::LE_2M_PHY)) {
ble::phy_set_t phys(/* 1M */ false, /* 2M */ true, /* coded */ false);
ble_error_t error = ble.gap().setPreferredPhys(/* tx */&phys, /* rx */&phys);
/* PHY 2M communication will only take place if both peers support it */
if (error) {
print_error(error, "GAP::setPreferedPhys failed\r\n");
}
} else {
/* otherwise it will use 1M by default */
printf("2M not supported. Sticking with 1M PHY\r\n");
}

So, if you are still confused by it all, here is a video demonstrating the app in action.

Demonstration

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

And that's basically it. I hope you found this useful.

  • Sign in to reply
  • BigG
    BigG over 3 years ago in reply to DAB

    Thanks DAB

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB over 3 years ago

    Good start.

    I like the way you tested out your main information transfer threads.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube