Bill Siever
/
nRF5-DK-HeartRateDemo_C
Fork of nRF5-DK-HeartRateDemo by
source/main.cpp@3:f593ad98fe21, 2016-11-11 (annotated)
- Committer:
- bsiever
- Date:
- Fri Nov 11 20:33:07 2016 +0000
- Revision:
- 3:f593ad98fe21
- Parent:
- 2:b850666f3c8f
- Child:
- 4:6b6018da25f6
nRF52-DK Heart Rate Service developed with C-based approach to the service (rather than an object for the service). It still uses objects for the UUIDs and Characteristics. Initial commit. This version supports only the 8-bit heart rate and energy
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
bsiever | 0:3a7b472313d7 | 1 | #include <events/mbed_events.h> |
bsiever | 0:3a7b472313d7 | 2 | #include <mbed.h> |
bsiever | 0:3a7b472313d7 | 3 | #include "ble/BLE.h" |
bsiever | 0:3a7b472313d7 | 4 | #include "ble/Gap.h" |
bsiever | 0:3a7b472313d7 | 5 | |
bsiever | 3:f593ad98fe21 | 6 | // See the official heart rate service definitions: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml |
bsiever | 3:f593ad98fe21 | 7 | // This version shows a working heart rate service created with a "C style" (non object-oriented) approach. |
bsiever | 3:f593ad98fe21 | 8 | // It uses 8-bit heart rate values and includes "energy expended" values. |
bsiever | 3:f593ad98fe21 | 9 | |
bsiever | 0:3a7b472313d7 | 10 | |
bsiever | 2:b850666f3c8f | 11 | // If debug is "true", include code to print debugging messages to the "console" |
bsiever | 0:3a7b472313d7 | 12 | #define DEBUG 1 |
bsiever | 0:3a7b472313d7 | 13 | #if DEBUG |
bsiever | 2:b850666f3c8f | 14 | #define LOG_PRINTF(...) eventQueue.call(printf, __VA_ARGS__); |
bsiever | 0:3a7b472313d7 | 15 | #else |
bsiever | 2:b850666f3c8f | 16 | #define LOG_PRINTF(...) ; |
bsiever | 0:3a7b472313d7 | 17 | #endif |
bsiever | 0:3a7b472313d7 | 18 | |
bsiever | 0:3a7b472313d7 | 19 | /*********************************************************************** |
bsiever | 0:3a7b472313d7 | 20 | I. Global Variables: |
bsiever | 0:3a7b472313d7 | 21 | This section declares "global variable" (variables that are used |
bsiever | 3:f593ad98fe21 | 22 | in multiple functions in this file and need to exist the entire time |
bsiever | 3:f593ad98fe21 | 23 | code is running (i.e., the concept of global is about both their scope |
bsiever | 3:f593ad98fe21 | 24 | and their lifetime) |
bsiever | 0:3a7b472313d7 | 25 | ************************************************************************/ |
bsiever | 3:f593ad98fe21 | 26 | // A. UUIDs for the service and its characteristics (they won't change, so declared const) |
bsiever | 3:f593ad98fe21 | 27 | const UUID HRMS_SERV(0x180D); |
bsiever | 3:f593ad98fe21 | 28 | const UUID HRMS_HRM_CHAR(0x2A37); |
bsiever | 3:f593ad98fe21 | 29 | const UUID HRMS_BODYSENSELOC_CHAR(0x2A38); |
bsiever | 3:f593ad98fe21 | 30 | const UUID HRMS_CONTROLPOINT_CHAR(0x2A39); |
bsiever | 3:f593ad98fe21 | 31 | |
bsiever | 3:f593ad98fe21 | 32 | // B. Create variables for any actual data |
bsiever | 3:f593ad98fe21 | 33 | uint8_t hrmHeartRateData[4] = {0x08,0,0,0}; // 1st byte: flags, 2nd byte heart date, 3-4th bytes: energy expended |
bsiever | 3:f593ad98fe21 | 34 | // Flags 0x8 indicates energy expended is included and heart rate is an 8-bit value. |
bsiever | 3:f593ad98fe21 | 35 | uint8_t hrmBodySensorLocation = 0; // 0 is code for "Other". See https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml |
bsiever | 3:f593ad98fe21 | 36 | uint8_t hrmControlPoint = 0; |
bsiever | 3:f593ad98fe21 | 37 | |
bsiever | 0:3a7b472313d7 | 38 | |
bsiever | 3:f593ad98fe21 | 39 | // C. Create "pointers" (references) to the characteristics |
bsiever | 3:f593ad98fe21 | 40 | // (The actual objects are constructed at run-time) |
bsiever | 3:f593ad98fe21 | 41 | GattCharacteristic hrmRateChar(HRMS_HRM_CHAR, // UUID to use |
bsiever | 3:f593ad98fe21 | 42 | hrmHeartRateData, // Pointer to data to use (arrays ARE pointers) |
bsiever | 3:f593ad98fe21 | 43 | 4, // Number of bytes (current data) |
bsiever | 3:f593ad98fe21 | 44 | 4, // Number of bytes (max) |
bsiever | 3:f593ad98fe21 | 45 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); // Permissions |
bsiever | 0:3a7b472313d7 | 46 | |
bsiever | 3:f593ad98fe21 | 47 | // The versions below use templated classes (a congenience for a few "common" characteristic permissions) |
bsiever | 3:f593ad98fe21 | 48 | ReadOnlyGattCharacteristic<uint8_t> hrmLocationChar(HRMS_BODYSENSELOC_CHAR, // UUID to use |
bsiever | 3:f593ad98fe21 | 49 | &hrmBodySensorLocation); // Data to use |
bsiever | 3:f593ad98fe21 | 50 | |
bsiever | 3:f593ad98fe21 | 51 | WriteOnlyGattCharacteristic<uint8_t> hrmControlPointChar(HRMS_CONTROLPOINT_CHAR, // UUID |
bsiever | 3:f593ad98fe21 | 52 | &hrmControlPoint); // Data to use |
bsiever | 3:f593ad98fe21 | 53 | |
bsiever | 3:f593ad98fe21 | 54 | |
bsiever | 3:f593ad98fe21 | 55 | |
bsiever | 3:f593ad98fe21 | 56 | |
bsiever | 3:f593ad98fe21 | 57 | // D. A way to create new "events" |
bsiever | 0:3a7b472313d7 | 58 | /* Total size of space for eventQueue = event count * event size */ |
bsiever | 0:3a7b472313d7 | 59 | EventQueue eventQueue( 16 * 32); |
bsiever | 0:3a7b472313d7 | 60 | |
bsiever | 3:f593ad98fe21 | 61 | // D. Setup the interrupt pins for any buttons |
bsiever | 0:3a7b472313d7 | 62 | InterruptIn buttons[4] = { InterruptIn(P0_13), InterruptIn(P0_14), InterruptIn(P0_15), InterruptIn(P0_16) }; |
bsiever | 0:3a7b472313d7 | 63 | |
bsiever | 0:3a7b472313d7 | 64 | |
bsiever | 0:3a7b472313d7 | 65 | |
bsiever | 0:3a7b472313d7 | 66 | /*********************************************************************** |
bsiever | 0:3a7b472313d7 | 67 | II. Event "Call backs": |
bsiever | 0:3a7b472313d7 | 68 | This section contains functions that are called to respond to events. |
bsiever | 0:3a7b472313d7 | 69 | (Each of these is "set" as a callback somewhere in the following code too) |
bsiever | 0:3a7b472313d7 | 70 | ************************************************************************/ |
bsiever | 0:3a7b472313d7 | 71 | |
bsiever | 0:3a7b472313d7 | 72 | // A. Callback for things to do when a device disconnects |
bsiever | 0:3a7b472313d7 | 73 | void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) { |
bsiever | 2:b850666f3c8f | 74 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 0:3a7b472313d7 | 75 | |
bsiever | 0:3a7b472313d7 | 76 | // Start advertising for a new connection |
bsiever | 0:3a7b472313d7 | 77 | BLE::Instance().gap().startAdvertising(); |
bsiever | 0:3a7b472313d7 | 78 | } |
bsiever | 0:3a7b472313d7 | 79 | |
bsiever | 0:3a7b472313d7 | 80 | |
bsiever | 3:f593ad98fe21 | 81 | |
bsiever | 3:f593ad98fe21 | 82 | void onDataWritten(const GattWriteCallbackParams *params) { |
bsiever | 3:f593ad98fe21 | 83 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 3:f593ad98fe21 | 84 | // See: https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/65474dc93927/GattCallbackParamTypes_8h_source.html for structure |
bsiever | 3:f593ad98fe21 | 85 | // A write of 1 to the "control point" should reset the energy expended |
bsiever | 3:f593ad98fe21 | 86 | if (params->handle == hrmControlPointChar.getValueAttribute().getHandle()) { |
bsiever | 3:f593ad98fe21 | 87 | LOG_PRINTF("Writing to HRM Control Point"); |
bsiever | 3:f593ad98fe21 | 88 | if(params->len == 1 && params->data[0]==1) { |
bsiever | 3:f593ad98fe21 | 89 | LOG_PRINTF("Clearing HRM Control Point"); |
bsiever | 3:f593ad98fe21 | 90 | // If it has the correct length and data, reset |
bsiever | 3:f593ad98fe21 | 91 | hrmHeartRateData[2] = 0; |
bsiever | 3:f593ad98fe21 | 92 | hrmHeartRateData[3] = 0; |
bsiever | 3:f593ad98fe21 | 93 | // Get the BLE object |
bsiever | 3:f593ad98fe21 | 94 | BLE& ble = BLE::Instance(); |
bsiever | 3:f593ad98fe21 | 95 | ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); |
bsiever | 3:f593ad98fe21 | 96 | } |
bsiever | 3:f593ad98fe21 | 97 | } |
bsiever | 3:f593ad98fe21 | 98 | } |
bsiever | 3:f593ad98fe21 | 99 | |
bsiever | 3:f593ad98fe21 | 100 | void createHeartRateService() { |
bsiever | 3:f593ad98fe21 | 101 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 3:f593ad98fe21 | 102 | |
bsiever | 3:f593ad98fe21 | 103 | // Use the default BLE object: |
bsiever | 3:f593ad98fe21 | 104 | BLE &ble = BLE::Instance(); |
bsiever | 3:f593ad98fe21 | 105 | |
bsiever | 3:f593ad98fe21 | 106 | GattCharacteristic *charTable[] = {&hrmRateChar, &hrmLocationChar, &hrmControlPointChar}; |
bsiever | 3:f593ad98fe21 | 107 | GattService hrmService(HRMS_SERV, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); |
bsiever | 3:f593ad98fe21 | 108 | |
bsiever | 3:f593ad98fe21 | 109 | ble.addService(hrmService); |
bsiever | 3:f593ad98fe21 | 110 | // Add an call back when data is written. |
bsiever | 3:f593ad98fe21 | 111 | ble.onDataWritten(onDataWritten); |
bsiever | 3:f593ad98fe21 | 112 | } |
bsiever | 3:f593ad98fe21 | 113 | |
bsiever | 3:f593ad98fe21 | 114 | |
bsiever | 0:3a7b472313d7 | 115 | // B. Callback for things to do when the BLE object is ready |
bsiever | 2:b850666f3c8f | 116 | void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) { |
bsiever | 2:b850666f3c8f | 117 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 0:3a7b472313d7 | 118 | |
bsiever | 0:3a7b472313d7 | 119 | // Get the BLE object |
bsiever | 0:3a7b472313d7 | 120 | BLE& ble = BLE::Instance(); |
bsiever | 0:3a7b472313d7 | 121 | |
bsiever | 3:f593ad98fe21 | 122 | // Create the HeartRateService |
bsiever | 3:f593ad98fe21 | 123 | createHeartRateService(); |
bsiever | 0:3a7b472313d7 | 124 | |
bsiever | 0:3a7b472313d7 | 125 | // Setup the "onDisconnection()" callback |
bsiever | 0:3a7b472313d7 | 126 | // Connection info is handeled by GAP (Generic ACCESS Protocol), hence the ble.gap() part |
bsiever | 0:3a7b472313d7 | 127 | ble.gap().onDisconnection(bleDisconnectionCallback); |
bsiever | 0:3a7b472313d7 | 128 | |
bsiever | 0:3a7b472313d7 | 129 | // Setup advertising: Build the "payload" |
bsiever | 0:3a7b472313d7 | 130 | // Add in the mode (discoverable / low energy only) |
bsiever | 0:3a7b472313d7 | 131 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); |
bsiever | 0:3a7b472313d7 | 132 | // Include a list of services in the advertising packet |
bsiever | 0:3a7b472313d7 | 133 | uint16_t uuid16_list[] = {GattService::UUID_HEART_RATE_SERVICE}; |
bsiever | 0:3a7b472313d7 | 134 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *) uuid16_list, sizeof(uuid16_list)); |
bsiever | 0:3a7b472313d7 | 135 | |
bsiever | 0:3a7b472313d7 | 136 | // Include the device name in the advertising packet |
bsiever | 0:3a7b472313d7 | 137 | char DEVICE_NAME[] = "HRM"; |
bsiever | 0:3a7b472313d7 | 138 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *) DEVICE_NAME, sizeof(DEVICE_NAME)); |
bsiever | 0:3a7b472313d7 | 139 | // Add the fact that it's "connectable" to the advertising packet (and that its not directed to a specific device) |
bsiever | 0:3a7b472313d7 | 140 | ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); |
bsiever | 0:3a7b472313d7 | 141 | // Wait 1S between advertising packets. Shorter will lead to faster connections but more power consumption |
bsiever | 0:3a7b472313d7 | 142 | // Longer will usually lead to slower times-to-connect, but lower power consumption |
bsiever | 0:3a7b472313d7 | 143 | ble.gap().setAdvertisingInterval(1000); /* 1000ms */ |
bsiever | 0:3a7b472313d7 | 144 | |
bsiever | 0:3a7b472313d7 | 145 | // Also set the device name in the GAP level |
bsiever | 0:3a7b472313d7 | 146 | ble.setDeviceName((uint8_t*)DEVICE_NAME); // Set the "GAP Name" too. |
bsiever | 0:3a7b472313d7 | 147 | |
bsiever | 0:3a7b472313d7 | 148 | // Start advertising |
bsiever | 0:3a7b472313d7 | 149 | ble.gap().startAdvertising(); |
bsiever | 0:3a7b472313d7 | 150 | } |
bsiever | 0:3a7b472313d7 | 151 | |
bsiever | 0:3a7b472313d7 | 152 | void buttonsPress() { |
bsiever | 2:b850666f3c8f | 153 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 3:f593ad98fe21 | 154 | |
bsiever | 3:f593ad98fe21 | 155 | // Get access to the BLE object |
bsiever | 3:f593ad98fe21 | 156 | BLE &ble = BLE::Instance(); |
bsiever | 3:f593ad98fe21 | 157 | |
bsiever | 0:3a7b472313d7 | 158 | // Display the buttons that are pressed |
bsiever | 0:3a7b472313d7 | 159 | |
bsiever | 0:3a7b472313d7 | 160 | // Do NOT use printf() in callbacks. Put all printing on tasks handled by the event queue. |
bsiever | 2:b850666f3c8f | 161 | // (This uses the debug macro provided to call printf() with arguments) |
bsiever | 0:3a7b472313d7 | 162 | for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn);i++) { |
bsiever | 0:3a7b472313d7 | 163 | if(buttons[i].read()==0) |
bsiever | 2:b850666f3c8f | 164 | LOG_PRINTF("\t%d down\r\n",i+1); |
bsiever | 0:3a7b472313d7 | 165 | } |
bsiever | 0:3a7b472313d7 | 166 | if(buttons[0].read()==0) { |
bsiever | 3:f593ad98fe21 | 167 | // Increase the heart rate and update |
bsiever | 3:f593ad98fe21 | 168 | hrmHeartRateData[1]++; |
bsiever | 3:f593ad98fe21 | 169 | ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); |
bsiever | 0:3a7b472313d7 | 170 | } else |
bsiever | 0:3a7b472313d7 | 171 | if(buttons[1].read()==0) { |
bsiever | 3:f593ad98fe21 | 172 | hrmHeartRateData[1]--; |
bsiever | 3:f593ad98fe21 | 173 | ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); |
bsiever | 0:3a7b472313d7 | 174 | } |
bsiever | 3:f593ad98fe21 | 175 | if(buttons[2].read()==0) { |
bsiever | 3:f593ad98fe21 | 176 | unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8; |
bsiever | 3:f593ad98fe21 | 177 | energy++; |
bsiever | 3:f593ad98fe21 | 178 | hrmHeartRateData[2] = energy & 0xFF; |
bsiever | 3:f593ad98fe21 | 179 | hrmHeartRateData[3] = energy >> 8; |
bsiever | 3:f593ad98fe21 | 180 | ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); |
bsiever | 3:f593ad98fe21 | 181 | } |
bsiever | 3:f593ad98fe21 | 182 | if(buttons[3].read()==0) { |
bsiever | 3:f593ad98fe21 | 183 | unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8; |
bsiever | 3:f593ad98fe21 | 184 | energy--; |
bsiever | 3:f593ad98fe21 | 185 | hrmHeartRateData[2] = energy & 0xFF; |
bsiever | 3:f593ad98fe21 | 186 | hrmHeartRateData[3] = energy >> 8; |
bsiever | 3:f593ad98fe21 | 187 | ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); |
bsiever | 3:f593ad98fe21 | 188 | } |
bsiever | 0:3a7b472313d7 | 189 | } |
bsiever | 0:3a7b472313d7 | 190 | |
bsiever | 0:3a7b472313d7 | 191 | // C. Callback to respond to BLE events. This will add a function call to the |
bsiever | 0:3a7b472313d7 | 192 | // Event Queue to process the event. |
bsiever | 0:3a7b472313d7 | 193 | void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) { |
bsiever | 0:3a7b472313d7 | 194 | BLE &ble = BLE::Instance(); |
bsiever | 0:3a7b472313d7 | 195 | eventQueue.call(Callback<void()>(&ble, &BLE::processEvents)); |
bsiever | 0:3a7b472313d7 | 196 | } |
bsiever | 0:3a7b472313d7 | 197 | |
bsiever | 0:3a7b472313d7 | 198 | /*********************************************************************** |
bsiever | 0:3a7b472313d7 | 199 | III. main() thread |
bsiever | 0:3a7b472313d7 | 200 | This section will run initially and configure/start everything else. |
bsiever | 0:3a7b472313d7 | 201 | ************************************************************************/ |
bsiever | 0:3a7b472313d7 | 202 | |
bsiever | 0:3a7b472313d7 | 203 | |
bsiever | 0:3a7b472313d7 | 204 | int main() { |
bsiever | 2:b850666f3c8f | 205 | LOG_PRINTF(__func__); // Print the function's name |
bsiever | 0:3a7b472313d7 | 206 | // Configure the buttons. |
bsiever | 0:3a7b472313d7 | 207 | for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn); i++) { |
bsiever | 0:3a7b472313d7 | 208 | // Pull the button voltages "up" to 3v by default |
bsiever | 0:3a7b472313d7 | 209 | buttons[i].mode(PullUp); |
bsiever | 0:3a7b472313d7 | 210 | // Callback for when the button falls to a 0 (when the button is pressed) |
bsiever | 0:3a7b472313d7 | 211 | buttons[i].fall(&buttonsPress); |
bsiever | 0:3a7b472313d7 | 212 | } |
bsiever | 0:3a7b472313d7 | 213 | |
bsiever | 0:3a7b472313d7 | 214 | // Get access to the BLE object |
bsiever | 0:3a7b472313d7 | 215 | BLE &ble = BLE::Instance(); |
bsiever | 0:3a7b472313d7 | 216 | // Set the BLE object to use the event queue to process any BLE events |
bsiever | 0:3a7b472313d7 | 217 | // (So when a BLE event occurs, a function to respond to it will be placed in the event queue) |
bsiever | 0:3a7b472313d7 | 218 | ble.onEventsToProcess(scheduleBleEventsProcessing); |
bsiever | 0:3a7b472313d7 | 219 | |
bsiever | 0:3a7b472313d7 | 220 | // Initialize the BLE object |
bsiever | 0:3a7b472313d7 | 221 | ble.init(bleInitComplete); |
bsiever | 0:3a7b472313d7 | 222 | |
bsiever | 0:3a7b472313d7 | 223 | // Let the "event queue" take over |
bsiever | 0:3a7b472313d7 | 224 | eventQueue.dispatch_forever(); |
bsiever | 0:3a7b472313d7 | 225 | |
bsiever | 0:3a7b472313d7 | 226 | return 0; |
bsiever | 0:3a7b472313d7 | 227 | } |