BLE Heart Rate Feather Beta Test Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************* | |
This is code modified by Iain from Adafruit for Bluefruit LE heart rate modules. | |
Added: battery reporting code (bug: only reports on boot) | |
Added: heart rate summing and reporting code | |
modified by @iainnash | |
original example written by adafruit | |
Adafruit invests time and resources providing this open source code, | |
please support Adafruit and open-source hardware by purchasing | |
products from Adafruit! | |
MIT license, check LICENSE for more information | |
All text above, and the splash screen below must be included in | |
any redistribution | |
*********************************************************************/ | |
#include <bluefruit.h> | |
#define VBAT_PIN (A7) | |
// 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 | |
#define VBAT_MV_PER_LSB (0.73242188F) | |
// 2M + 0.806M voltage divider on VBAT = (2M / (0.806M + 2M)) | |
#define VBAT_DIVIDER (0.71275837F) | |
// Compensation factor for the VBAT divider | |
#define VBAT_DIVIDER_COMP (1.403F) | |
/* HRM Service Definitions | |
Heart Rate Monitor Service: 0x180D | |
Heart Rate Measurement Char: 0x2A37 | |
Body Sensor Location Char: 0x2A38 | |
*/ | |
BLEService hrms = BLEService(UUID16_SVC_HEART_RATE); | |
BLECharacteristic hrmc = BLECharacteristic(UUID16_CHR_HEART_RATE_MEASUREMENT); | |
BLECharacteristic bslc = BLECharacteristic(UUID16_CHR_BODY_SENSOR_LOCATION); | |
BLEDis bledis; // DIS (Device Information Service) helper class instance | |
BLEBas blebas; // BAS (Battery Service) helper class instance | |
uint8_t bps = 0; | |
uint8_t mvToPercent(float mvolts) { | |
uint8_t battery_level; | |
if (mvolts >= 3000) { | |
battery_level = 100; | |
} else if (mvolts > 2900) { | |
battery_level = 100 - ((3000 - mvolts) * 58) / 100; | |
} else if (mvolts > 2740) { | |
battery_level = 42 - ((2900 - mvolts) * 24) / 160; | |
} else if (mvolts > 2440) { | |
battery_level = 18 - ((2740 - mvolts) * 12) / 300; | |
} else if (mvolts > 2100) { | |
battery_level = 6 - ((2440 - mvolts) * 6) / 340; | |
} else { | |
battery_level = 0; | |
} | |
return battery_level; | |
} | |
// copy + pasted from seeeed example code | |
unsigned char counter; | |
unsigned long temp[21]; | |
unsigned long sub; | |
bool data_effect = true; | |
int heart_rate = 0; // the measurement result of heart rate | |
const int max_heartpluse_duty = 2000; | |
void arrayInit() { | |
// init result array | |
for (unsigned char i = 0; i < 20; i ++) | |
{ | |
temp[i] = 0; | |
} | |
temp[20] = millis(); | |
// end init array | |
} | |
void sum() | |
{ | |
if (data_effect) | |
{ | |
heart_rate = 1200000 / (temp[20] - temp[0]); //60*20*1000/20_total_time | |
Serial.print("r:1:"); | |
Serial.println(heart_rate); | |
} | |
data_effect = 1; //sign bit | |
} | |
void interrupt() | |
{ | |
temp[counter] = millis(); | |
switch (counter) | |
{ | |
case 0: | |
sub = temp[counter] - temp[20]; | |
Serial.println(sub); | |
break; | |
default: | |
sub = temp[counter] - temp[counter - 1]; | |
Serial.println(sub); | |
break; | |
} | |
if (sub > max_heartpluse_duty) //set 2 seconds as max heart pluse duty | |
{ | |
data_effect = 0; //sign bit | |
counter = 0; | |
Serial.println("Heart rate measure error,test will restart!" ); | |
arrayInit(); | |
} | |
if (counter == 20 && data_effect) | |
{ | |
counter = 0; | |
sum(); | |
} | |
else if (counter != 20 && data_effect) | |
counter++; | |
else | |
{ | |
counter = 0; | |
data_effect = 1; | |
} | |
} | |
void setup() | |
{ | |
arrayInit(); | |
Serial.begin(115200); | |
while ( !Serial ) delay(10); // for nrf52840 with native usb | |
pinMode(16, INPUT); | |
// Initialise the Bluefruit module | |
Bluefruit.begin(); | |
// Set the advertised device name (keep it short!) | |
Bluefruit.setName("HeartRate No1"); | |
// Set the connect/disconnect callback handlers | |
Bluefruit.Periph.setConnectCallback(connect_callback); | |
Bluefruit.Periph.setDisconnectCallback(disconnect_callback); | |
// Configure and Start the Device Information Service | |
Serial.println("Configuring the Device Information Service"); | |
bledis.setManufacturer("Adafruit Industries"); | |
bledis.setModel("Heartbeat Update"); | |
bledis.begin(); | |
// Start the BLE Battery Service and set it to 100% | |
Serial.println("Configuring the Battery Service"); | |
blebas.begin(); | |
analogReference(AR_INTERNAL_3_0); | |
analogReadResolution(12); // Can be 8, 10, 12 or 14 | |
delay(1); | |
int vbat_raw = analogRead(VBAT_PIN); | |
uint8_t vbat_per = mvToPercent(vbat_raw * VBAT_MV_PER_LSB); | |
blebas.write(vbat_per); | |
// Setup the Heart Rate Monitor service using | |
// BLEService and BLECharacteristic classes | |
Serial.println("Configuring the Heart Rate Monitor Service"); | |
setupHRM(); | |
// Setup the advertising packet(s) | |
Serial.println("Setting up the advertising payload(s)"); | |
startAdv(); | |
Serial.println("Ready Player One!!!"); | |
Serial.println("\nAdvertising"); | |
} | |
void startAdv(void) | |
{ | |
// Advertising packet | |
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); | |
Bluefruit.Advertising.addTxPower(); | |
// Include HRM Service UUID | |
Bluefruit.Advertising.addService(hrms); | |
// Include Name | |
Bluefruit.Advertising.addName(); | |
/* Start Advertising | |
- Enable auto advertising if disconnected | |
- Interval: fast mode = 20 ms, slow mode = 152.5 ms | |
- Timeout for fast mode is 30 seconds | |
- Start(timeout) with timeout = 0 will advertise forever (until connected) | |
For recommended advertising interval | |
https://developer.apple.com/library/content/qa/qa1931/_index.html | |
*/ | |
Bluefruit.Advertising.restartOnDisconnect(true); | |
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms | |
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode | |
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds | |
} | |
void setupHRM(void) | |
{ | |
// Configure the Heart Rate Monitor service | |
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml | |
// Supported Characteristics: | |
// Name UUID Requirement Properties | |
// ---------------------------- ------ ----------- ---------- | |
// Heart Rate Measurement 0x2A37 Mandatory Notify | |
// Body Sensor Location 0x2A38 Optional Read | |
// Heart Rate Control Point 0x2A39 Conditional Write <-- Not used here | |
hrms.begin(); | |
// Note: You must call .begin() on the BLEService before calling .begin() on | |
// any characteristic(s) within that service definition.. Calling .begin() on | |
// a BLECharacteristic will cause it to be added to the last BLEService that | |
// was 'begin()'ed! | |
// Configure the Heart Rate Measurement characteristic | |
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml | |
// Properties = Notify | |
// Min Len = 1 | |
// Max Len = 8 | |
// B0 = UINT8 - Flag (MANDATORY) | |
// b5:7 = Reserved | |
// b4 = RR-Internal (0 = Not present, 1 = Present) | |
// b3 = Energy expended status (0 = Not present, 1 = Present) | |
// b1:2 = Sensor contact status (0+1 = Not supported, 2 = Supported but contact not detected, 3 = Supported and detected) | |
// b0 = Value format (0 = UINT8, 1 = UINT16) | |
// B1 = UINT8 - 8-bit heart rate measurement value in BPM | |
// B2:3 = UINT16 - 16-bit heart rate measurement value in BPM | |
// B4:5 = UINT16 - Energy expended in joules | |
// B6:7 = UINT16 - RR Internal (1/1024 second resolution) | |
hrmc.setProperties(CHR_PROPS_NOTIFY); | |
hrmc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); | |
hrmc.setFixedLen(2); | |
hrmc.setCccdWriteCallback(cccd_callback); // Optionally capture CCCD updates | |
hrmc.begin(); | |
uint8_t hrmdata[2] = { 0b00000110, 0x40 }; // Set the characteristic to use 8-bit values, with the sensor connected and detected | |
hrmc.write(hrmdata, 2); | |
// Configure the Body Sensor Location characteristic | |
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml | |
// Properties = Read | |
// Min Len = 1 | |
// Max Len = 1 | |
// B0 = UINT8 - Body Sensor Location | |
// 0 = Other | |
// 1 = Chest | |
// 2 = Wrist | |
// 3 = Finger | |
// 4 = Hand | |
// 5 = Ear Lobe | |
// 6 = Foot | |
// 7:255 = Reserved | |
bslc.setProperties(CHR_PROPS_READ); | |
bslc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); | |
bslc.setFixedLen(1); | |
bslc.begin(); | |
bslc.write8(5); // Set the characteristic to 'Ear lobe' (2) | |
} | |
void connect_callback(uint16_t conn_handle) | |
{ | |
// Get the reference to current connection | |
BLEConnection* connection = Bluefruit.Connection(conn_handle); | |
char central_name[32] = { 0 }; | |
connection->getPeerName(central_name, sizeof(central_name)); | |
Serial.print("Connected to "); | |
Serial.println(central_name); | |
} | |
/** | |
Callback invoked when a connection is dropped | |
@param conn_handle connection where this event happens | |
@param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h | |
*/ | |
void disconnect_callback(uint16_t conn_handle, uint8_t reason) | |
{ | |
(void) conn_handle; | |
(void) reason; | |
Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX); | |
Serial.println("Advertising!"); | |
} | |
void cccd_callback(uint16_t conn_hdl, BLECharacteristic* chr, uint16_t cccd_value) | |
{ | |
// Display the raw request packet | |
Serial.print("CCCD Updated: "); | |
//Serial.printBuffer(request->data, request->len); | |
Serial.print(cccd_value); | |
Serial.println(""); | |
// Check the characteristic this CCCD update is associated with in case | |
// this handler is used for multiple CCCD records. | |
if (chr->uuid == hrmc.uuid) { | |
if (chr->notifyEnabled(conn_hdl)) { | |
Serial.println("Heart Rate Measurement 'Notify' enabled"); | |
} else { | |
Serial.println("Heart Rate Measurement 'Notify' disabled"); | |
} | |
} | |
} | |
bool lastHeart = false; | |
void notifyBle() { | |
uint8_t hrmdata[2] = { 0b00000110, heart_rate }; // Sensor connected, increment BPS value | |
// Note: We use .notify instead of .write! | |
// If it is connected but CCCD is not enabled | |
// The characteristic's value is still updated although notification is not sent | |
if ( hrmc.notify(hrmdata, sizeof(hrmdata)) ) { | |
Serial.print("Heart Rate Measurement updated to: "); Serial.println(heart_rate); | |
} else { | |
Serial.println("ERROR: Notify not set in the CCCD or not connected!"); | |
} | |
} | |
void loop() | |
{ | |
bool thisHeart = digitalRead(16); | |
digitalWrite(LED_RED, thisHeart); | |
if (thisHeart != lastHeart && thisHeart == true) { | |
interrupt(); | |
} | |
digitalToggle(LED_RED); | |
if ( Bluefruit.connected() ) { | |
if (thisHeart != lastHeart && thisHeart == true) { | |
notifyBle(); | |
} | |
} | |
lastHeart = thisHeart; | |
// Only send update once per second | |
delay(10); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment