This is a fork of mbed-os-example-ble-HeartRate maintained for Sequana compatibility. This application transmits a heart rate value using the Bluetooth SIG Heart Rate Profile. The heart rate value is provided by the application itself, not by a sensor, so that you don't have to get a sensor just to run the example. The canonical source for this example lives at https://github.com/ARMmbed/mbed-os-example-ble/tree/master/BLE_HeartRate

Files at this revision

API Documentation at this revision

Comitter:
lru
Date:
Tue Feb 12 14:03:29 2019 +0000
Commit message:
Initial version.

Changed in this revision

.mbed Show annotated file Show diff for this revision Revisions of this file
img/connection.png Show annotated file Show diff for this revision Revisions of this file
img/discovery.png Show annotated file Show diff for this revision Revisions of this file
img/notifications.png Show annotated file Show diff for this revision Revisions of this file
img/register_to_notifications.png Show annotated file Show diff for this revision Revisions of this file
img/scan_result.png Show annotated file Show diff for this revision Revisions of this file
img/start_scan.png Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
mbed_app.json Show annotated file Show diff for this revision Revisions of this file
module.json Show annotated file Show diff for this revision Revisions of this file
readme.md Show annotated file Show diff for this revision Revisions of this file
shields/TARGET_CORDIO_BLUENRG.lib Show annotated file Show diff for this revision Revisions of this file
shields/TARGET_CORDIO_BLUENRG/BlueNrgHCIDriver.cpp Show annotated file Show diff for this revision Revisions of this file
shields/TARGET_CORDIO_BLUENRG/README.md Show annotated file Show diff for this revision Revisions of this file
shields/TARGET_CORDIO_BLUENRG/bluenrg_targets.h Show annotated file Show diff for this revision Revisions of this file
shields/TARGET_CORDIO_BLUENRG/mbed_lib.json Show annotated file Show diff for this revision Revisions of this file
source/main.cpp Show annotated file Show diff for this revision Revisions of this file
source/pretty_printer.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.mbed	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,3 @@
+TOOLCHAIN=GCC_ARM
+TARGET=FUTURE_SEQUANA
+ROOT=.
Binary file img/connection.png has changed
Binary file img/discovery.png has changed
Binary file img/notifications.png has changed
Binary file img/register_to_notifications.png has changed
Binary file img/scan_result.png has changed
Binary file img/start_scan.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os.lib	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/mbed-os/#ecb3c8c837162c73537bd0f3592c6e2a42994045
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed_app.json	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,30 @@
+{
+    "target_overrides": {
+        "K64F": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_BLUENRG"]
+        },
+        "NUCLEO_F401RE": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_BLUENRG"]
+        },
+        "DISCO_L475VG_IOT01A": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_BLUENRG"]
+        },
+        "NRF52840_DK": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_LL", "SOFTDEVICE_NONE", "NORDIC_CORDIO"],
+            "target.extra_labels_remove": ["SOFTDEVICE_COMMON", "SOFTDEVICE_S140_FULL", "NORDIC_SOFTDEVICE"]
+        },
+        "NRF52_DK": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_LL", "SOFTDEVICE_NONE", "NORDIC_CORDIO"],
+            "target.extra_labels_remove": ["SOFTDEVICE_COMMON", "SOFTDEVICE_S132_FULL", "NORDIC_SOFTDEVICE"]
+        },
+        "FUTURE_SEQUANA": {
+            "target.features_add": ["BLE"],
+            "target.hex_filename": "psoc63_m0_ble_controller_1.05.hex"
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/module.json	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,16 @@
+{
+  "name": "ble-heartrate",
+  "version": "0.0.1",
+  "description": "BLE Heartreate example, building with yotta",
+  "licenses": [
+    {
+      "url": "https://spdx.org/licenses/Apache-2.0",
+      "type": "Apache-2.0"
+    }
+  ],
+  "dependencies": {
+    "ble": "^2.0.0"
+  },
+  "targetDependencies": {},
+  "bin": "./source"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/readme.md	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,64 @@
+# BLE Heart Rate Monitor
+
+This application transmits a heart rate value using the [Bluetooth SIG Heart Rate Profile](https://developer.bluetooth.org/TechnologyOverview/Pages/HRP.aspx). The heart rate value is provided by the application itself, not by a sensor, so that you don't have to get a sensor just to run the example.
+
+Technical details are better presented [in the mbed Classic equivalent of this example](https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_HeartRate/).
+
+# Running the application
+
+## Requirements
+
+To see the heart rate information on your phone, use a BLE scanner:
+
+- [nRF Master Control Panel](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp) for Android.
+
+- [LightBlue](https://itunes.apple.com/gb/app/lightblue-bluetooth-low-energy/id557428110?mt=8) for iPhone.
+
+Hardware requirements are in the [main readme](https://github.com/ARMmbed/mbed-os-example-ble/blob/master/README.md).
+
+## Building instructions
+
+Building instructions for all samples are in the [main readme](https://github.com/ARMmbed/mbed-os-example-ble/blob/master/README.md).
+
+## Checking for success
+
+**Note:** Screens captures depicted below show what is expected from this example if the scanner used is *nRF Master Control Panel* version 4.0.5. If you encounter any difficulties consider trying another scanner or another version of nRF Master Control Panel. Alternative scanners may require reference to their manuals.
+
+1. Build the application and install it on your board as explained in the building instructions.
+1. Open the BLE scanner on your phone.
+1. Start a scan.
+
+    ![](img/start_scan.png)
+
+    **figure 1** How to start scan using nRF Master Control Panel 4.0.5
+
+1. Find your device; it should be named `HRM`.
+
+    ![](img/scan_result.png)
+
+    **figure 2** Scan results using nRF Master Control Panel 4.0.5
+
+1. Establish a connection with your device.
+
+    ![](img/connection.png)
+
+    **figure 3**  How to establish a connection using Master Control Panel 4.0.5
+
+1. Discover the services and the characteristics on the device. The *Heart Rate* service has the UUID `0x180D` and includes the *Heart Rate Measurement* characteristic which has the UUID `0x2A37`.
+
+    ![](img/discovery.png)
+
+    **figure 4** Representation of the Heart Rate service using Master Control Panel 4.0.5
+
+1. Register for the notifications sent by the *Heart Rate Measurement* characteristic.
+
+    ![](img/register_to_notifications.png)
+
+    **figure 5** How to register to notifications using Master Control Panel 4.0.5
+
+
+1. You should see the heart rate value change every half second. It begins at 100, goes up to 175 (in steps of 1), resets to 100 and so on.
+
+    ![](img/notifications.png)
+
+    **figure 6** Notifications view using Master Control Panel 4.0.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shields/TARGET_CORDIO_BLUENRG.lib	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/cordio-ble-x-nucleo-idb0xa1/#811f3fea7aa8083c0bbf378e1b51a8b131d7efcc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shields/TARGET_CORDIO_BLUENRG/BlueNrgHCIDriver.cpp	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,601 @@
+#include <stdio.h>
+#include "CordioBLE.h"
+#include "CordioHCIDriver.h"
+#include "CordioHCITransportDriver.h"
+#include "mbed.h"
+#include "hci_api.h"
+#include "hci_cmd.h"
+#include "hci_core.h"
+#include "dm_api.h"
+#include "bstream.h"
+#include "hci_mbed_os_adaptation.h"
+#include "bluenrg_targets.h"
+#include "Thread.h"
+#include "Semaphore.h"
+#include "Mutex.h"
+
+#define HCI_RESET_RAND_CNT              4
+
+#define VENDOR_SPECIFIC_EVENT           0xFF
+#define EVT_BLUE_INITIALIZED            0x0001
+#define ACI_READ_CONFIG_DATA_OPCODE     0xFC0D
+#define ACI_WRITE_CONFIG_DATA_OPCODE    0xFC0C
+#define ACI_GATT_INIT_OPCODE            0xFD01
+#define ACI_GAP_INIT_OPCODE             0xFC8A
+
+#define PUBLIC_ADDRESS_OFFSET           0x00
+#define RANDOM_STATIC_ADDRESS_OFFSET    0x80
+#define LL_WITHOUT_HOST_OFFSET          0x2C
+#define ROLE_OFFSET                     0x2D
+
+#define SPI_STACK_SIZE                  1024
+
+namespace ble {
+namespace vendor {
+namespace bluenrg {
+
+/**
+ * BlueNRG HCI driver implementation.
+ * @see cordio::CordioHCIDriver
+ */
+class HCIDriver : public cordio::CordioHCIDriver
+{
+public:
+    /**
+     * Construction of the BlueNRG HCIDriver.
+     * @param transport: Transport of the HCI commands.
+     * @param rst: Name of the reset pin
+     */
+    HCIDriver(cordio::CordioHCITransportDriver& transport_driver, PinName rst) :
+        cordio::CordioHCIDriver(transport_driver), rst(rst) { }
+
+    /**
+     * @see CordioHCIDriver::do_initialize
+     */
+    virtual void do_initialize() {
+        bluenrg_reset();
+    }
+
+    /**
+     * @see CordioHCIDriver::get_buffer_pool_description
+     */
+    ble::vendor::cordio::buf_pool_desc_t get_buffer_pool_description()
+    {
+        // Use default buffer pool
+        return ble::vendor::cordio::CordioHCIDriver::get_default_buffer_pool_description();
+    }
+
+    /**
+     * @see CordioHCIDriver::start_reset_sequence
+     */
+    virtual void start_reset_sequence() {
+        reset_received = false;
+        bluenrg_initialized = false;
+        enable_link_layer_mode_ongoing = false;
+        /* send an HCI Reset command to start the sequence */
+        HciResetCmd();
+    }
+
+    /**
+     * @see CordioHCIDriver::do_terminate
+     */
+    virtual void do_terminate() {
+
+    }
+
+    /**
+     * @see CordioHCIDriver::handle_reset_sequence
+     */
+    virtual void handle_reset_sequence(uint8_t *pMsg) {
+        uint16_t       opcode;
+        static uint8_t randCnt;
+        //wait_ms(5);
+
+        /* if event is a command complete event */
+        if (*pMsg == HCI_CMD_CMPL_EVT)
+        {
+            /* parse parameters */
+            pMsg += HCI_EVT_HDR_LEN;
+            pMsg++;                   /* skip num packets */
+            BSTREAM_TO_UINT16(opcode, pMsg);
+            pMsg++;                   /* skip status */
+
+            /* decode opcode */
+            switch (opcode)
+            {
+            case HCI_OPCODE_RESET: {
+                /* initialize rand command count */
+                randCnt = 0;
+                reset_received = true;
+                // important, the bluenrg_initialized event come after the
+                // hci reset event (not documented)
+                bluenrg_initialized = false;
+            } break;
+
+            // ACL packet ...
+            case ACI_WRITE_CONFIG_DATA_OPCODE:
+                if (enable_link_layer_mode_ongoing) {
+                    enable_link_layer_mode_ongoing = false;
+                    aciSetRole();
+                } else {
+                    aciGattInit();
+                }
+                break;
+
+            case ACI_GATT_INIT_OPCODE:
+                aciGapInit();
+                break;
+
+            case ACI_GAP_INIT_OPCODE:
+                aciReadConfigParameter(RANDOM_STATIC_ADDRESS_OFFSET);
+                break;
+
+            case ACI_READ_CONFIG_DATA_OPCODE:
+                // note: will send the HCI command to send the random address
+                cordio::BLE::deviceInstance().getGap().setAddress(
+                    BLEProtocol::AddressType::RANDOM_STATIC,
+                    pMsg
+                );
+                break;
+
+            case HCI_OPCODE_LE_SET_RAND_ADDR:
+                HciSetEventMaskCmd((uint8_t *) hciEventMask);
+                break;
+
+            case HCI_OPCODE_SET_EVENT_MASK:
+                /* send next command in sequence */
+                HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask);
+                break;
+
+            case HCI_OPCODE_LE_SET_EVENT_MASK:
+// Note: the public address is not read because there is no valid public address
+// provisioned by default on the target
+// Enable if the
+#if MBED_CONF_CORDIO_BLUENRG_VALID_PUBLIC_BD_ADDRESS == 1
+                /* send next command in sequence */
+                HciReadBdAddrCmd();
+                break;
+
+            case HCI_OPCODE_READ_BD_ADDR:
+                /* parse and store event parameters */
+                BdaCpy(hciCoreCb.bdAddr, pMsg);
+
+                /* send next command in sequence */
+#endif
+                HciLeReadBufSizeCmd();
+                break;
+
+            case HCI_OPCODE_LE_READ_BUF_SIZE:
+                /* parse and store event parameters */
+                BSTREAM_TO_UINT16(hciCoreCb.bufSize, pMsg);
+                BSTREAM_TO_UINT8(hciCoreCb.numBufs, pMsg);
+
+                /* initialize ACL buffer accounting */
+                hciCoreCb.availBufs = hciCoreCb.numBufs;
+
+                /* send next command in sequence */
+                HciLeReadSupStatesCmd();
+                break;
+
+            case HCI_OPCODE_LE_READ_SUP_STATES:
+                /* parse and store event parameters */
+                memcpy(hciCoreCb.leStates, pMsg, HCI_LE_STATES_LEN);
+
+                /* send next command in sequence */
+                HciLeReadWhiteListSizeCmd();
+                break;
+
+            case HCI_OPCODE_LE_READ_WHITE_LIST_SIZE:
+                /* parse and store event parameters */
+                BSTREAM_TO_UINT8(hciCoreCb.whiteListSize, pMsg);
+
+                /* send next command in sequence */
+                HciLeReadLocalSupFeatCmd();
+                break;
+
+            case HCI_OPCODE_LE_READ_LOCAL_SUP_FEAT:
+                /* parse and store event parameters */
+                BSTREAM_TO_UINT16(hciCoreCb.leSupFeat, pMsg);
+
+                /* send next command in sequence */
+                hciCoreReadResolvingListSize();
+                break;
+
+            case HCI_OPCODE_LE_READ_RES_LIST_SIZE:
+                /* parse and store event parameters */
+                BSTREAM_TO_UINT8(hciCoreCb.resListSize, pMsg);
+
+                /* send next command in sequence */
+                hciCoreReadMaxDataLen();
+                break;
+
+            case HCI_OPCODE_LE_READ_MAX_DATA_LEN:
+                {
+                    uint16_t maxTxOctets;
+                    uint16_t maxTxTime;
+
+                    BSTREAM_TO_UINT16(maxTxOctets, pMsg);
+                    BSTREAM_TO_UINT16(maxTxTime, pMsg);
+
+                    /* use Controller's maximum supported payload octets and packet duration times
+                    * for transmission as Host's suggested values for maximum transmission number
+                    * of payload octets and maximum packet transmission time for new connections.
+                    */
+                    HciLeWriteDefDataLen(maxTxOctets, maxTxTime);
+                }
+                break;
+
+            case HCI_OPCODE_LE_WRITE_DEF_DATA_LEN:
+                if (hciCoreCb.extResetSeq)
+                {
+                    /* send first extended command */
+                    (*hciCoreCb.extResetSeq)(pMsg, opcode);
+                }
+                else
+                {
+                    /* initialize extended parameters */
+                    hciCoreCb.maxAdvDataLen = 0;
+                    hciCoreCb.numSupAdvSets = 0;
+                    hciCoreCb.perAdvListSize = 0;
+
+                    /* send next command in sequence */
+                    HciLeRandCmd();
+                }
+                break;
+
+            case HCI_OPCODE_LE_READ_MAX_ADV_DATA_LEN:
+            case HCI_OPCODE_LE_READ_NUM_SUP_ADV_SETS:
+            case HCI_OPCODE_LE_READ_PER_ADV_LIST_SIZE:
+                if (hciCoreCb.extResetSeq)
+                {
+                    /* send next extended command in sequence */
+                    (*hciCoreCb.extResetSeq)(pMsg, opcode);
+                }
+                break;
+
+            case HCI_OPCODE_LE_RAND:
+                /* check if need to send second rand command */
+                if (randCnt < (HCI_RESET_RAND_CNT-1))
+                {
+                    randCnt++;
+                    HciLeRandCmd();
+                }
+                else
+                {
+                    signal_reset_sequence_done();
+                }
+                break;
+
+            default:
+                break;
+            }
+        } else {
+            /**
+             * vendor specific event
+             */
+            if (pMsg[0] == VENDOR_SPECIFIC_EVENT) {
+                /* parse parameters */
+                pMsg += HCI_EVT_HDR_LEN;
+                BSTREAM_TO_UINT16(opcode, pMsg);
+
+                if (opcode == EVT_BLUE_INITIALIZED) {
+                    if (bluenrg_initialized) {
+                        return;
+                    }
+                    bluenrg_initialized = true;
+                    if (reset_received) {
+                        aciEnableLinkLayerModeOnly();
+                    }
+                }
+
+            }
+        }
+    }
+
+private:
+    void aciEnableLinkLayerModeOnly() {
+        uint8_t data[1] = { 0x01 };
+        enable_link_layer_mode_ongoing = true;
+        aciWriteConfigData(LL_WITHOUT_HOST_OFFSET, data);
+    }
+
+    void aciSetRole() {
+        // master and slave, simultaneous advertising and scanning
+        // (up to 4 connections)
+        uint8_t data[1] = { 0x04 };
+        aciWriteConfigData(ROLE_OFFSET, data);
+    }
+
+    void aciGattInit() {
+        uint8_t *pBuf = hciCmdAlloc(ACI_GATT_INIT_OPCODE, 0);
+        if (!pBuf) {
+            return;
+        }
+        hciCmdSend(pBuf);
+    }
+
+    void aciGapInit() {
+        uint8_t *pBuf = hciCmdAlloc(ACI_GAP_INIT_OPCODE, 3);
+        if (!pBuf) {
+            return;
+        }
+        pBuf[3] = 0xF;
+        pBuf[4] = 0;
+        pBuf[5] = 0;
+        hciCmdSend(pBuf);
+    }
+
+    void aciReadConfigParameter(uint8_t offset) {
+        uint8_t *pBuf = hciCmdAlloc(ACI_READ_CONFIG_DATA_OPCODE, 1);
+        if (!pBuf) {
+            return;
+        }
+
+        pBuf[3] = offset;
+        hciCmdSend(pBuf);
+    }
+
+    template<size_t N>
+    void aciWriteConfigData(uint8_t offset, uint8_t (&buf)[N]) {
+        uint8_t *pBuf = hciCmdAlloc(ACI_WRITE_CONFIG_DATA_OPCODE, 2 + N);
+        if (!pBuf) {
+            return;
+        }
+
+        pBuf[3] = offset;
+        pBuf[4] = N;
+        memcpy(pBuf + 5, buf, N);
+        hciCmdSend(pBuf);
+    }
+
+    void hciCoreReadResolvingListSize(void)
+    {
+        /* if LL Privacy is supported by Controller and included */
+        if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_PRIVACY) &&
+            (hciLeSupFeatCfg & HCI_LE_SUP_FEAT_PRIVACY))
+        {
+            /* send next command in sequence */
+            HciLeReadResolvingListSize();
+        }
+        else
+        {
+            hciCoreCb.resListSize = 0;
+
+            /* send next command in sequence */
+            hciCoreReadMaxDataLen();
+        }
+    }
+
+    void hciCoreReadMaxDataLen(void)
+    {
+    /* if LE Data Packet Length Extensions is supported by Controller and included */
+        if ((hciCoreCb.leSupFeat & HCI_LE_SUP_FEAT_DATA_LEN_EXT) &&
+            (hciLeSupFeatCfg & HCI_LE_SUP_FEAT_DATA_LEN_EXT))
+        {
+            /* send next command in sequence */
+            HciLeReadMaxDataLen();
+        }
+        else
+        {
+            /* send next command in sequence */
+            HciLeRandCmd();
+        }
+    }
+
+    void bluenrg_reset() {
+        /* Reset BlueNRG SPI interface. Hold reset line to 0 for 1500ms */
+        rst = 0;
+        wait_us(1500);
+        rst = 1;
+
+        /* Wait for the radio to come back up */
+        wait_us(100000);
+    }
+
+    DigitalOut rst;
+    bool reset_received;
+    bool bluenrg_initialized;
+    bool enable_link_layer_mode_ongoing;
+};
+
+/**
+ * Transport driver of the ST BlueNRG shield.
+ * @important: With that driver, it is assumed that the SPI bus used is not shared
+ * with other SPI peripherals. The reasons behind this choice are simplicity and
+ * performance:
+ *   - Reading from the peripheral SPI can be challenging especially if other
+ *     threads access the same SPI bus. Indeed it is common that the function
+ *     spiRead yield nothings even if the chip has signaled data with the irq
+ *     line. Sharing would make the situation worse and increase the risk of
+ *     timeout of HCI commands / response.
+ *   - This driver can be used even if the RTOS is disabled or not present it may
+ *     may be usefull for some targets.
+ *
+ * If The SPI is shared with other peripherals then the best option would be to
+ * handle SPI read in a real time thread woken up by an event flag.
+ *
+ * Other mechanisms might also be added in the future to handle data read as an
+ * event from the stack. This might not be the best solution for all BLE chip;
+ * especially this one.
+ */
+class TransportDriver : public cordio::CordioHCITransportDriver {
+public:
+    /**
+     * Construct the transport driver required by a BlueNRG module.
+     * @param mosi Pin of the SPI mosi
+     * @param miso Pin of the SPI miso
+     * @param sclk Pin of the SPI clock
+     * @param irq Pin used by the module to signal data are available.
+     */
+    TransportDriver(PinName mosi, PinName miso, PinName sclk, PinName ncs, PinName irq)
+        : spi(mosi, miso, sclk), nCS(ncs), irq(irq), _spi_thread(osPriorityNormal, SPI_STACK_SIZE, _spi_thread_stack) {
+        _spi_thread.start(callback(this, &TransportDriver::spi_read_cb));
+    }
+
+    virtual ~TransportDriver() { }
+
+    /**
+     * @see CordioHCITransportDriver::initialize
+     */
+    virtual void initialize() {
+        // Setup the spi for 8 bit data, low clock polarity,
+        // 1-edge phase, with an 8MHz clock rate
+        spi.format(8, 0);
+        spi.frequency(8000000);
+
+        // Deselect the BlueNRG chip by keeping its nCS signal high
+        nCS = 1;
+
+        wait_us(500);
+
+        // Set the interrupt handler for the device
+        irq.mode(PullDown); // set irq mode
+        irq.rise(callback(this, &TransportDriver::HCI_Isr));
+    }
+
+    /**
+     * @see CordioHCITransportDriver::terminate
+     */
+    virtual void terminate() { }
+
+    /**
+     * @see CordioHCITransportDriver::write
+     */
+    virtual uint16_t write(uint8_t type, uint16_t len, uint8_t *pData) {
+        // repeat write until successfull. A number of attempt or timeout might
+        // be useful
+        while (spiWrite(type, pData, len) == 0) { }
+        return len;
+    }
+
+private:
+    uint16_t spiWrite(uint8_t type, const uint8_t* data, uint16_t data_length) {
+        static const uint8_t header_master[] = {
+            0x0A, 0x00, 0x00, 0x00, 0x00
+        };
+        uint8_t header_slave[]  = { 0xaa, 0x00, 0x00, 0x00, 0x00 };
+        uint16_t data_written = 0;
+        uint16_t write_buffer_size = 0;
+
+        _spi_mutex.lock();
+
+        /* CS reset */
+        nCS = 0;
+
+        /* Exchange header */
+        for (uint8_t i = 0; i < sizeof(header_master); ++i) {
+            header_slave[i] = spi.write(header_master[i]);
+        }
+
+        if (header_slave[0] != 0x02) {
+            goto exit;
+        }
+
+        write_buffer_size = header_slave[2] << 8 | header_slave[1];
+
+        if (write_buffer_size == 0 || write_buffer_size < (data_length + 1)) {
+            goto exit;
+        }
+
+        spi.write(type);
+
+        data_written = data_length;
+        for (uint16_t i = 0; i < data_length; ++i) {
+            spi.write(data[i]);
+        }
+
+    exit:
+        nCS = 1;
+
+        _spi_mutex.unlock();
+
+        return data_written;
+    }
+
+    uint16_t spiRead(uint8_t* data_buffer, const uint16_t buffer_size)
+    {
+        static const uint8_t header_master[] = {0x0b, 0x00, 0x00, 0x00, 0x00};
+        uint8_t header_slave[5] = { 0xaa, 0x00, 0x00, 0x00, 0x00};
+        uint16_t read_length = 0;
+        uint16_t data_available = 0;
+
+        nCS = 0;
+
+        /* Read the header */
+        for (size_t i = 0; i < sizeof(header_master); i++) {
+            header_slave[i] = spi.write(header_master[i]);
+        }
+
+        if (header_slave[0] != 0x02) {
+            goto exit;
+        }
+
+        data_available = (header_slave[4] << 8) | header_slave[3];
+        read_length = data_available > buffer_size ? buffer_size : data_available;
+
+        for (uint16_t i = 0; i < read_length; ++i) {
+            data_buffer[i] = spi.write(0xFF);
+        }
+
+    exit:
+        nCS = 1;
+
+        return read_length;
+    }
+
+    /*
+     * might be split into two parts: the IRQ signaling a real time thread and
+     * the real time thread reading data from the SPI.
+     */
+    void HCI_Isr(void)
+    {
+        _spi_read_sem.release();
+    }
+
+    void spi_read_cb() {
+        uint8_t data_buffer[256];
+        while(true) {
+            _spi_read_sem.wait();
+
+            _spi_mutex.lock();
+            while(irq == 1) {
+                uint16_t data_read = spiRead(data_buffer, sizeof(data_buffer));
+                on_data_received(data_buffer, data_read);
+            }
+            _spi_mutex.unlock();
+        }
+    }
+
+    /**
+     * Unsafe SPI, does not lock when SPI access happens.
+     */
+    ::mbed::SPI spi;
+    DigitalOut nCS;
+    InterruptIn irq;
+    rtos::Thread _spi_thread;
+    uint8_t _spi_thread_stack[SPI_STACK_SIZE];
+    rtos::Semaphore _spi_read_sem;
+    rtos::Mutex _spi_mutex;
+};
+
+} // namespace bluenrg
+} // namespace vendor
+} // namespace ble
+
+/**
+ * Cordio HCI driver factory
+ */
+ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver() {
+    static ble::vendor::bluenrg::TransportDriver transport_driver(
+        BLUENRG_PIN_SPI_MOSI,
+        BLUENRG_PIN_SPI_MISO,
+        BLUENRG_PIN_SPI_SCK,
+        BLUENRG_PIN_SPI_nCS,
+        BLUENRG_PIN_SPI_IRQ
+    );
+    static ble::vendor::bluenrg::HCIDriver hci_driver(
+        transport_driver,
+        BLUENRG_PIN_SPI_RESET
+    );
+    return hci_driver;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shields/TARGET_CORDIO_BLUENRG/README.md	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,52 @@
+# Cordio BLE-X-NUCLEO-IDB0XA1
+
+BLE_API wrapper Library for X-NUCLEO-IDB05A1 BlueNRG (Bluetooth Low Energy) Expansion Board. It uses ARM Cordio stack instead of the ST BlueNRG stack. 
+
+## Introduction
+
+This firmware package implements the port of BLE_API to STMicroelectronics' [X-NUCLEO-IDB05A1](https://developer.mbed.org/components/X-NUCLEO-IDB05A1-Bluetooth-Low-Energy/) Bluetooth Low Energy Nucleo Expansion Board.
+
+### Arduino Connector Compatibility Warning
+
+X-NUCLEO-IDB05A1 is Arduino compatible with an exception: instead of using pin **D13** for the SPI clock, pin **D3** is used.
+The default configuration for this library is having the SPI clock on pin **D3**.
+
+To be fully Arduino compatible, X-NUCLEO-IDB05A1 needs a small HW patch.
+
+For X-NUCLEO-IDB05A1 this patch consists in removing zero resistor **R4** and instead soldering zero resistor **R6**.
+
+In case you patch your board, then you also have to configure this library to use pin **D13** to drive the SPI clock. To this aim you need to compile this driver with macro `BLUENRG_PIN_SPI_SCK=D13` defined.
+
+If you use pin **D13** for the SPI clock, please be aware that on STM32 Nucleo boards you may **not** drive the LED, otherwise you will get a conflict: the LED on STM32 Nucleo boards is connected to pin **D13**.
+
+Referring to the current list of tested platforms (see [X-NUCLEO-IDB05A1](https://developer.mbed.org/components/X-NUCLEO-IDB05A1-Bluetooth-Low-Energy/) page), the patch is required by [ST-Nucleo-F103RB](https://developer.mbed.org/platforms/ST-Nucleo-F103RB/); [ST-Nucleo-F302R8](https://developer.mbed.org/platforms/ST-Nucleo-F302R8/); [ST-Nucleo-F411RE](https://developer.mbed.org/platforms/ST-Nucleo-F411RE/); [ST-Nucleo-F446RE](https://developer.mbed.org/platforms/ST-Nucleo-F446RE/); and [FRDM-K64F](https://developer.mbed.org/platforms/FRDM-K64F/).
+
+
+### Driver configuration
+
+In order to use the BlueNRG-MS module together with other targets, you need to set the macros defined in file [bluenrg_targets.h](https://github.com/ARMmbed/ble-x-nucleo-idb0xa1/blob/master/bluenrg/bluenrg_targets.h). Please, update the [mbed_lib.json](https://github.com/ARMmbed/ble-x-nucleo-idb0xa1/blob/master/mbed_lib.json) to include the list of extra macros that configure the driver for your target.
+
+## Target Configuration
+
+To use that library, the target requires some extra configuration in the application `mbed_app.json`. In the `target_overides` section:   
+
+* BLE feature has to be enabled for the target using the BlueNRG module
+
+```json
+"target.features_add": ["BLE"]
+```
+
+* Extra labels have to be defined to include the cordio stack and this library: 
+
+```json
+"target.extra_labels_add": ["CORDIO", "CORDIO_BLUENRG"]
+```
+
+As an example, the target overide section for the `NUCLEO_F401RE` would be: 
+
+```json
+        "NUCLEO_F401RE": {
+            "target.features_add": ["BLE"],
+            "target.extra_labels_add": ["CORDIO", "CORDIO_BLUENRG"]
+        }
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shields/TARGET_CORDIO_BLUENRG/bluenrg_targets.h	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,65 @@
+/**
+  ******************************************************************************
+  * @file    bluenrg_targets.h
+  * @author  AST / EST
+  * @version V0.0.1
+  * @date    24-July-2015
+  * @brief   This header file is intended to manage the differences between
+  *          the different supported base-boards which might mount the
+  *          X_NUCLEO_IDB0XA1 BlueNRG BLE Expansion Board.
+  ******************************************************************************
+  * @attention
+  *
+  * <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>
+  *
+  * Redistribution and use in source and binary forms, with or without modification,
+  * are permitted provided that the following conditions are met:
+  *   1. Redistributions of source code must retain the above copyright notice,
+  *      this list of conditions and the following disclaimer.
+  *   2. Redistributions in binary form must reproduce the above copyright notice,
+  *      this list of conditions and the following disclaimer in the documentation
+  *      and/or other materials provided with the distribution.
+  *   3. Neither the name of STMicroelectronics nor the names of its contributors
+  *      may be used to endorse or promote products derived from this software
+  *      without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  *
+  ******************************************************************************
+  */
+
+/* Define to prevent from recursive inclusion --------------------------------*/
+#ifndef _BLUENRG_TARGETS_H_
+#define _BLUENRG_TARGETS_H_
+
+#if !defined(BLUENRG_PIN_SPI_MOSI)
+#define BLUENRG_PIN_SPI_MOSI   (D11)
+#endif
+#if !defined(BLUENRG_PIN_SPI_MISO)
+#define BLUENRG_PIN_SPI_MISO   (D12)
+#endif
+#if !defined(BLUENRG_PIN_SPI_nCS)
+#define BLUENRG_PIN_SPI_nCS    (A1)
+#endif
+#if !defined(BLUENRG_PIN_SPI_RESET)
+#define BLUENRG_PIN_SPI_RESET  (D7)
+#endif
+#if !defined(BLUENRG_PIN_SPI_IRQ)
+#define BLUENRG_PIN_SPI_IRQ    (A0)
+#endif
+
+/* NOTE: Refer to README for further details regarding BLUENRG_PIN_SPI_SCK */
+#if !defined(BLUENRG_PIN_SPI_SCK)
+#define BLUENRG_PIN_SPI_SCK    (D3)
+#endif
+
+#endif // _BLUENRG_TARGTES_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shields/TARGET_CORDIO_BLUENRG/mbed_lib.json	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,24 @@
+{
+    "name": "cordio_bluenrg",
+    "config": {
+        "valid-public-bd-address": {
+            "help": "Read the BD public address at startup",
+            "value": false
+        }
+    },
+    "target_overrides": {
+        "K64F": {
+            "target.macros_add": ["BLUENRG_PIN_SPI_SCK=D13"]
+        },
+        "DISCO_L475VG_IOT01A": {
+            "target.macros_add": [
+                "BLUENRG_PIN_SPI_MOSI=PC_12",
+                "BLUENRG_PIN_SPI_MISO=PC_11",
+                "BLUENRG_PIN_SPI_nCS=PD_13",
+                "BLUENRG_PIN_SPI_RESET=PA_8",
+                "BLUENRG_PIN_SPI_IRQ=PE_6",
+                "BLUENRG_PIN_SPI_SCK=PC_10"
+            ]
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/main.cpp	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,173 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * 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 <events/mbed_events.h>
+#include <mbed.h>
+#include "ble/BLE.h"
+#include "ble/gap/Gap.h"
+#include "ble/services/HeartRateService.h"
+#include "pretty_printer.h"
+
+const static char DEVICE_NAME[] = "Heartrate";
+
+static events::EventQueue event_queue(/* event count */ 16 * EVENTS_EVENT_SIZE);
+
+class HeartrateDemo : ble::Gap::EventHandler {
+public:
+    HeartrateDemo(BLE &ble, events::EventQueue &event_queue) :
+        _ble(ble),
+        _event_queue(event_queue),
+        _led1(LED1, 1),
+        _connected(false),
+        _hr_uuid(GattService::UUID_HEART_RATE_SERVICE),
+        _hr_counter(100),
+        _hr_service(ble, _hr_counter, HeartRateService::LOCATION_FINGER),
+        _adv_data_builder(_adv_buffer) { }
+
+    void start() {
+        _ble.gap().setEventHandler(this);
+
+        _ble.init(this, &HeartrateDemo::on_init_complete);
+
+        _event_queue.call_every(500, this, &HeartrateDemo::blink);
+        _event_queue.call_every(1000, this, &HeartrateDemo::update_sensor_value);
+
+        _event_queue.dispatch_forever();
+    }
+
+private:
+    /** Callback triggered when the ble initialization process has finished */
+    void on_init_complete(BLE::InitializationCompleteCallbackContext *params) {
+        if (params->error != BLE_ERROR_NONE) {
+            printf("Ble initialization failed.");
+            return;
+        }
+
+        print_mac_address();
+
+        start_advertising();
+    }
+
+    void start_advertising() {
+        /* Create advertising parameters and payload */
+
+        ble::AdvertisingParameters adv_parameters(
+            ble::advertising_type_t::CONNECTABLE_UNDIRECTED,
+            ble::adv_interval_t(ble::millisecond_t(1000))
+        );
+
+        _adv_data_builder.setFlags();
+        _adv_data_builder.setAppearance(ble::adv_data_appearance_t::GENERIC_HEART_RATE_SENSOR);
+        _adv_data_builder.setLocalServiceList(mbed::make_Span(&_hr_uuid, 1));
+        _adv_data_builder.setName(DEVICE_NAME);
+
+        /* Setup advertising */
+
+        ble_error_t error = _ble.gap().setAdvertisingParameters(
+            ble::LEGACY_ADVERTISING_HANDLE,
+            adv_parameters
+        );
+
+        if (error) {
+            printf("_ble.gap().setAdvertisingParameters() failed\r\n");
+            return;
+        }
+
+        error = _ble.gap().setAdvertisingPayload(
+            ble::LEGACY_ADVERTISING_HANDLE,
+            _adv_data_builder.getAdvertisingData()
+        );
+
+        if (error) {
+            printf("_ble.gap().setAdvertisingPayload() failed\r\n");
+            return;
+        }
+
+        /* Start advertising */
+
+        error = _ble.gap().startAdvertising(ble::LEGACY_ADVERTISING_HANDLE);
+
+        if (error) {
+            printf("_ble.gap().startAdvertising() failed\r\n");
+            return;
+        }
+    }
+
+    void update_sensor_value() {
+        if (_connected) {
+            // Do blocking calls or whatever is necessary for sensor polling.
+            // In our case, we simply update the HRM measurement.
+            _hr_counter++;
+
+            //  100 <= HRM bps <=175
+            if (_hr_counter == 175) {
+                _hr_counter = 100;
+            }
+
+            _hr_service.updateHeartRate(_hr_counter);
+        }
+    }
+
+    void blink(void) {
+        _led1 = !_led1;
+    }
+
+private:
+    /* Event handler */
+
+    void onDisconnectionComplete(const ble::DisconnectionCompleteEvent&) {
+        _ble.gap().startAdvertising(ble::LEGACY_ADVERTISING_HANDLE);
+        _connected = false;
+    }
+
+    virtual void onConnectionComplete(const ble::ConnectionCompleteEvent &event) {
+        if (event.getStatus() == BLE_ERROR_NONE) {
+            _connected = true;
+        }
+    }
+
+private:
+    BLE &_ble;
+    events::EventQueue &_event_queue;
+    DigitalOut _led1;
+
+    bool _connected;
+
+    UUID _hr_uuid;
+
+    uint8_t _hr_counter;
+    HeartRateService _hr_service;
+
+    uint8_t _adv_buffer[ble::LEGACY_ADVERTISING_MAX_SIZE];
+    ble::AdvertisingDataBuilder _adv_data_builder;
+};
+
+/** Schedule processing of events from the BLE middleware in the event queue. */
+void schedule_ble_events(BLE::OnEventsToProcessCallbackContext *context) {
+    event_queue.call(Callback<void()>(&context->ble, &BLE::processEvents));
+}
+
+int main()
+{
+    BLE &ble = BLE::Instance();
+    ble.onEventsToProcess(schedule_ble_events);
+
+    HeartrateDemo demo(ble, event_queue);
+    demo.start();
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/pretty_printer.h	Tue Feb 12 14:03:29 2019 +0000
@@ -0,0 +1,95 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2018 ARM Limited
+ *
+ * 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/BLE.h"
+
+inline void print_error(ble_error_t error, const char* msg)
+{
+    printf("%s: ", msg);
+    switch(error) {
+        case BLE_ERROR_NONE:
+            printf("BLE_ERROR_NONE: No error");
+            break;
+        case BLE_ERROR_BUFFER_OVERFLOW:
+            printf("BLE_ERROR_BUFFER_OVERFLOW: The requested action would cause a buffer overflow and has been aborted");
+            break;
+        case BLE_ERROR_NOT_IMPLEMENTED:
+            printf("BLE_ERROR_NOT_IMPLEMENTED: Requested a feature that isn't yet implement or isn't supported by the target HW");
+            break;
+        case BLE_ERROR_PARAM_OUT_OF_RANGE:
+            printf("BLE_ERROR_PARAM_OUT_OF_RANGE: One of the supplied parameters is outside the valid range");
+            break;
+        case BLE_ERROR_INVALID_PARAM:
+            printf("BLE_ERROR_INVALID_PARAM: One of the supplied parameters is invalid");
+            break;
+        case BLE_STACK_BUSY:
+            printf("BLE_STACK_BUSY: The stack is busy");
+            break;
+        case BLE_ERROR_INVALID_STATE:
+            printf("BLE_ERROR_INVALID_STATE: Invalid state");
+            break;
+        case BLE_ERROR_NO_MEM:
+            printf("BLE_ERROR_NO_MEM: Out of Memory");
+            break;
+        case BLE_ERROR_OPERATION_NOT_PERMITTED:
+            printf("BLE_ERROR_OPERATION_NOT_PERMITTED");
+            break;
+        case BLE_ERROR_INITIALIZATION_INCOMPLETE:
+            printf("BLE_ERROR_INITIALIZATION_INCOMPLETE");
+            break;
+        case BLE_ERROR_ALREADY_INITIALIZED:
+            printf("BLE_ERROR_ALREADY_INITIALIZED");
+            break;
+        case BLE_ERROR_UNSPECIFIED:
+            printf("BLE_ERROR_UNSPECIFIED: Unknown error");
+            break;
+        case BLE_ERROR_INTERNAL_STACK_FAILURE:
+            printf("BLE_ERROR_INTERNAL_STACK_FAILURE: internal stack faillure");
+            break;
+    }
+    printf("\r\n");
+}
+
+/** print device address to the terminal */
+inline void print_address(const Gap::Address_t &addr)
+{
+    printf("%02x:%02x:%02x:%02x:%02x:%02x\r\n",
+           addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
+}
+
+inline void print_mac_address()
+{
+    /* Print out device MAC address to the console*/
+    Gap::AddressType_t addr_type;
+    Gap::Address_t address;
+    BLE::Instance().gap().getAddress(&addr_type, address);
+    printf("DEVICE MAC ADDRESS: ");
+    print_address(address);
+}
+
+inline const char* phy_to_string(Gap::Phy_t phy) {
+    switch(phy.value()) {
+        case Gap::Phy_t::LE_1M:
+            return "LE 1M";
+        case Gap::Phy_t::LE_2M:
+            return "LE 2M";
+        case Gap::Phy_t::LE_CODED:
+            return "LE coded";
+        default:
+            return "invalid PHY";
+    }
+}