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, committed 2019-02-12
- Comitter:
- lru
- Date:
- Tue Feb 12 14:03:29 2019 +0000
- Commit message:
- Initial version.
Changed in this revision
diff -r 000000000000 -r b283842072f8 .mbed --- /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=.
diff -r 000000000000 -r b283842072f8 img/connection.png Binary file img/connection.png has changed
diff -r 000000000000 -r b283842072f8 img/discovery.png Binary file img/discovery.png has changed
diff -r 000000000000 -r b283842072f8 img/notifications.png Binary file img/notifications.png has changed
diff -r 000000000000 -r b283842072f8 img/register_to_notifications.png Binary file img/register_to_notifications.png has changed
diff -r 000000000000 -r b283842072f8 img/scan_result.png Binary file img/scan_result.png has changed
diff -r 000000000000 -r b283842072f8 img/start_scan.png Binary file img/start_scan.png has changed
diff -r 000000000000 -r b283842072f8 mbed-os.lib --- /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
diff -r 000000000000 -r b283842072f8 mbed_app.json --- /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" + } + } +}
diff -r 000000000000 -r b283842072f8 module.json --- /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" +}
diff -r 000000000000 -r b283842072f8 readme.md --- /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
diff -r 000000000000 -r b283842072f8 shields/TARGET_CORDIO_BLUENRG.lib --- /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
diff -r 000000000000 -r b283842072f8 shields/TARGET_CORDIO_BLUENRG/BlueNrgHCIDriver.cpp --- /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; +}
diff -r 000000000000 -r b283842072f8 shields/TARGET_CORDIO_BLUENRG/README.md --- /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"] + } +```
diff -r 000000000000 -r b283842072f8 shields/TARGET_CORDIO_BLUENRG/bluenrg_targets.h --- /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>© 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_
diff -r 000000000000 -r b283842072f8 shields/TARGET_CORDIO_BLUENRG/mbed_lib.json --- /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" + ] + } + } +}
diff -r 000000000000 -r b283842072f8 source/main.cpp --- /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; +} +
diff -r 000000000000 -r b283842072f8 source/pretty_printer.h --- /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"; + } +}