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

Revision:
0:b283842072f8
--- /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;
+}