Skip to content

Instantly share code, notes, and snippets.

@iainnash
Created February 7, 2021 19:15
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save iainnash/71e541dbeec8a930bd9aa70af9f7c012 to your computer and use it in GitHub Desktop.
BLE Heart Rate Feather Beta Test Code
/*********************************************************************
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