If you're running off of a battery, chances are you wanna know what the voltage is at!
That way you can tell when the battery needs recharging. Lipoly batteries are 'maxed
out' at 4.2V and stick around 3.7V for much of the battery life, then slowly sink down
to 3.2V or so before the protection circuitry cuts it off. By measuring the voltage you
can quickly tell when you're heading below 3.7V
To make this easy we stuck a double-100K resistor divider on the BAT pin, and
connected it to A6 which is not exposed on the feather breakout
In Arduino, you can read this pin's voltage, then double it, to get the battery voltage.
// Arduino Example Code snippet
#define VBATPIN A6
float measuredvbat = analogRead(VBATPIN);
measuredvbat *= 2; // we divided by 2, so multiply back
measuredvbat *= 3.6; // Multiply by 3.6V, our reference voltage
measuredvbat /= 1024; // convert to voltage
Serial.print("VBat: " ); Serial.println(measuredvbat);
For CircuitPython, we've written a get_voltage() helper function to do the math for
you. All you have to do is call the function, provide the pin and print the results.
To keep up with Nordic's SoftDevice advances, you will likely need to update your
bootloader
Follow this link for instructions on how to do that
This step is only necessary on the nRF52832-based devices, NOT on the newer
nRF52840 Feather Express.
Update the Bootloader
https://adafru.it/Dsx
4. Run a Test Sketch
At this point, you should be able to run a test sketch from theExamples folder, or just
flash the following blinky code from the Arduino IDE:
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
This will blink the red LED beside the USB port on the Feather, or the red LED labeled
"LED" by the corner of the USB connector on the CLUE.
If Arduino failed to upload sketch to the Feather
If you get this error:
Timed out waiting for acknowledgement from device.
Failed to upgrade target. Error is: No data received on serial
To help explain some common use cases for the nRF52 BLE API, feel free to consult
the example documentation in this section of the learning guide:
Advertising: Beacon - Shows how to use the BLEBeacon helper class to
•
configure your Bleufruit nRF52 Feather as a beacon
BLE UART: Controller - Shows how to use the Controllerutility in our Bluefruit LE
•
Connect apps to send basic data between your peripheral and your phone or
tablet.
Custom: HRM - Shows how to defined and work with a custom GATT Service
•
and Characteristic, using the officially adopted Heart Rate Monitor (HRM) service
as an example.
BLE Pin I/O (StandardFirmataBLE) Shows how to control Pin I/O of nRF52 with
•
Firmata protocol
For more information on the Arduino nRF52 API, check out this page(https://adafru.it
/IIa).
Advertising: Beacon
This example shows how you can use the BLEBeacon helper class and advertising
API to configure your Bluefruit nRF52 board as a 'Beacon'.
Complete Code
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
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>
// Beacon uses the Manufacturer Specific Data field in the advertising
// packet, which means you must provide a valid Manufacturer ID. Update
// the field below to an appropriate value. For a list of valid IDs see:
// https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers
// 0x004C is Apple
// off Blue LED for lowest power consumption
Bluefruit.autoConnLed(false);
Bluefruit.setTxPower(0); // Check bluefruit.h for supported values
// Manufacturer ID is required for Manufacturer Specific Data
beacon.setManufacturer(MANUFACTURER_ID);
// Setup the advertising packet
startAdv();
Serial.println("Broadcasting beacon, open your beacon app to test");
// Suspend Loop() to save power, since we didn't have any code there
suspendLoop();
}
void startAdv(void)
{
// Advertising packet// Set the beacon payload using the BLEBeacon class populated// earlier in this example
Bluefruit.Advertising.setBeacon(beacon);
// Secondary Scan Response packet (optional)// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.addName();
/* Start Advertising
* - Enable auto advertising if disconnected
* - Timeout for fast mode is 30 seconds
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
*
* Apple Beacon specs
* - Type: Non connectable, undirected
* - Fixed interval: 100 ms -> fast = slow = 100 ms
*/
//Bluefruit.Advertising.setType(BLE_GAP_ADV_TYPE_ADV_NONCONN_IND);
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(160, 160); // 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
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
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 the BLE UART (AKA 'NUS') 128-bit UUID
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.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
}
/**************************************************************************/
/*!
@brief Constantly poll for new command or response data
*/
/**************************************************************************/
void loop(void)
{
// Wait for new data to arriveuint8_t len = readPacket(&bleuart, 500);
if (len ==0) return;
// Got a packet!// printHex(packetbuffer, len);
// Colorif (packetbuffer[1] =='C') {
uint8_t red = packetbuffer[2];
uint8_t green = packetbuffer[3];
uint8_t blue = packetbuffer[4];
Serial.print ("RGB #");
if (red <0x10) Serial.print("0");
Serial.print(red, HEX);
if (green <0x10) Serial.print("0");
Serial.print(green, HEX);
if (blue <0x10) Serial.print("0");
Serial.println(blue, HEX);
}
// READ_BUFSIZE Size of the read buffer for incoming packets
#define READ_BUFSIZE (20)
/* Buffer to hold incoming characters */
uint8_t packetbuffer[READ_BUFSIZE+1];
/**************************************************************************/
/*!
@brief Casts the four bytes at the specified address to a float
*/
/**************************************************************************/
float parsefloat(uint8_t *buffer)
{
float f;
memcpy(&f, buffer, 4);
return f;
}
/**************************************************************************/
/*!
@brief Prints a hexadecimal value in plain characters
@param data Pointer to the byte data
@param numBytes Data length in bytes
*/
/**************************************************************************/
{
uint32_t szPos;
for (szPos=0; szPos < numBytes; szPos++)
{
Serial.print(F("0x"));
// Append leading 0 for small valuesif (data[szPos] <=0xF)
{
Serial.print(F("0"));
Serial.print(data[szPos] &0xf, HEX);
}
else
{
Serial.print(data[szPos] &0xff, HEX);
}
// Add a trailing space if appropriateif ((numBytes >1) && (szPos != numBytes - 1))
{
Serial.print(F(" "));
}
}
Serial.println();
}
/**************************************************************************/
/*!
@brief Waits for incoming data and parses it
*/
/**************************************************************************/
Then you need to 'populate' those variables with appropriate values. For simplicity
sake, you can define a custom function for your service where all of the code is
placed, and then just call this function once in the 'setup' function:
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
// Permission = 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.notify(hrmdata, 2); // Use .notify instead of .write!
// Configure the Body Sensor Location characteristic
// See: https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
// Permission = 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);
// Check the characteristic this CCCD update is associated with in case
// this handler is used for multiple CCCD records.
if (chr->uuid == htmc.uuid) {
if (chr->indicateEnabled(conn_hdl)) {
Serial.println("Temperature Measurement 'Indicate' enabled");
} else {
Serial.println("Temperature Measurement 'Indicate' disabled");
}
}
}
4. Repeat the same procedure for any other BLECharacteristics in your service.
Full Sample Code
The full sample code for this example can be seen below:
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
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
*********************************************************************/
// Initialise the Bluefruit module
Serial.println("Initialise the Bluefruit nRF52 module");
Bluefruit.begin();
// 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("Bluefruit Feather52");
bledis.begin();
// Start the BLE Battery Service and set it to 100%
Serial.println("Configuring the Battery Service");
blebas.begin();
blebas.write(100);
// 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");
}
// 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?
// 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?
// 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
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
*/
{
// 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");
}
}
}
// 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 sentif ( hrmc.notify(hrmdata, sizeof(hrmdata)) ){
Serial.print("Heart Rate Measurement updated to: "); Serial.println(bps);
}else{
Serial.println("ERROR: Notify not set in the CCCD or not connected!");
}
}
// Only send update once per second
delay(1000);
}
BLE Pin I/O
Firmata is a generic protocol for communicating with microcontrollers and controlling
the board's pins such as setting the GPIO outputs and inputs, PWM output, analog
reads, etc....
Setup
In order to run this demo, you will need to open Bluefruit LE Connect on your mobile
deviceusing our free iOS(https://adafru.it/f4H), Android(https://adafru.it/f4G) or OS X
(https://adafru.it/o9F) applications.
Load the StandardFirmataBLE example sketch(https://adafru.it/Bl4) in the
•
Arduino IDE
Compile the sketch and flash it to your nRF52 based Feather
•
Once you are done uploading, open theSerial Monitor (Tools > Serial Monitor)
•
Open the Bluefruit LE Connect application on your mobile device
•
Connect to the appropriate target (probably 'Bluefruit52')
•
Once connected switch to thePin I/Oapplication inside the app
•
For more information using Pin I/O module, you could check out this tutorial
The latest version of this code is always available on Github(https://adafru.it/vaN),
and in theExamples folder of the nRF52 BSP.
The code below is provided for convenience sake, but may be out of date! See
the link above for the latest code.
/*
Firmata is a generic protocol for communicating with microcontrollers
from software on a host computer. It is intended to work with
any host computer software package.
To download a host software package, please click on the following link
to open the list of Firmata client libraries in your default browser.
Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved.
Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved.
Copyright (C) 2009 Shigeru Kobayashi. All rights reserved.
Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See file LICENSE.txt for further informations on licensing terms.
/*==============================================================================
* GLOBAL VARIABLES
*============================================================================*/
#ifdef FIRMATA_SERIAL_FEATURE
SerialFirmata serialFeature;
#endif
BLEUart bleuart;
/* analog inputs */
int analogInputsToReport = 0; // bitwise array to store pin reporting
/* digital input ports */
byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence
byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent
/* pins configuration */
byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else
/* timer variables */
unsigned long currentMillis; // store the current value from millis()
unsigned long previousMillis; // for comparison with currentMillis
unsigned int samplingInterval = 19; // how often to run the main loop (in ms)
{
servos[servoPinMap[pin]].detach();
// if we're detaching the last servo, decrement the count// otherwise store the index of the detached servo
if (servoPinMap[pin] == servoCount && servoCount > 0) {
servoCount--;
} else if (servoCount > 0) {
// keep track of detached servos because we want to reuse their indexes// before incrementing the count of attached servos
detachedServoCount++;
detachedServos[detachedServoCount - 1] = servoPinMap[pin];
}
servoPinMap[pin] =255;
}
void enableI2CPins()
{
byte i;
// is there a faster way to do this? would probaby require importing// Arduino.h to get SCL and SDA pinsfor (i =0; i < TOTAL_PINS; i++) {
if (IS_PIN_I2C(i)) {
// mark pins as i2c so they are ignore in non i2c data requests
setPinModeCallback(i, PIN_MODE_I2C);
}
}
isI2CEnabled =true;
Wire.begin();
}
/* disable the i2c pins so they can be used for other functions */
void disableI2CPins() {
isI2CEnabled =false;
// disable read continuous mode for all devices
queryIndex = -1;
}
void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) {
// allow I2C requests that don't require a register read// for example, some devices using an interrupt pin to signify new data available// do not always require the register read so upon interrupt you call
Wire.requestFrom()
if (theRegister != I2C_REGISTER_NOT_SPECIFIED) {
Wire.beginTransmission(address);
wireWrite((byte)theRegister);
Wire.endTransmission(stopTX); // default = true// do not set a value of 0if (i2cReadDelayTime >0) {
// delay is necessary for some devices such as WiiNunchuck
delayMicroseconds(i2cReadDelayTime);
}
} else {
theRegister =0; // fill the register with a dummy value
}
Wire.requestFrom(address, numBytes); // all bytes are returned in requestFrom
// check to be sure correct number of bytes were returned by slaveif (numBytes < Wire.available()) {
Firmata.sendString("I2C: Too many bytes received");
} elseif (numBytes > Wire.available()) {
{
// pins not configured as INPUT are cleared to zeros
portValue = portValue & portConfigInputs[portNumber];
// only send if the value is different than previously sentif (forceSend || previousPINs[portNumber] != portValue) {
Firmata.sendDigitalPort(portNumber, portValue);
previousPINs[portNumber] = portValue;
}
}
/* ---------------------------------------------------------------------------- * check all the active digital inputs for change of state, then add any events
* to the Serial output queue using Serial.print() */
void checkDigitalInputs(void)
{
/* Using non-looping code allows constants to be given to readPort().
* The compiler will apply substantial optimizations if the inputs
* to readPort() are compile-time constants. */
if (TOTAL_PORTS >0&& reportPINs[0]) outputPort(0, readPort(0,
portConfigInputs[0]), false);
if (TOTAL_PORTS >1&& reportPINs[1]) outputPort(1, readPort(1,
portConfigInputs[1]), false);
if (TOTAL_PORTS >2&& reportPINs[2]) outputPort(2, readPort(2,
portConfigInputs[2]), false);
if (TOTAL_PORTS >3&& reportPINs[3]) outputPort(3, readPort(3,
portConfigInputs[3]), false);
if (TOTAL_PORTS >4&& reportPINs[4]) outputPort(4, readPort(4,
portConfigInputs[4]), false);
if (TOTAL_PORTS >5&& reportPINs[5]) outputPort(5, readPort(5,
portConfigInputs[5]), false);
if (TOTAL_PORTS >6&& reportPINs[6]) outputPort(6, readPort(6,
portConfigInputs[6]), false);
if (TOTAL_PORTS >7&& reportPINs[7]) outputPort(7, readPort(7,
portConfigInputs[7]), false);
if (TOTAL_PORTS >8&& reportPINs[8]) outputPort(8, readPort(8,
portConfigInputs[8]), false);
if (TOTAL_PORTS >9&& reportPINs[9]) outputPort(9, readPort(9,
portConfigInputs[9]), false);
if (TOTAL_PORTS >10&& reportPINs[10]) outputPort(10, readPort(10,
portConfigInputs[10]), false);
if (TOTAL_PORTS >11&& reportPINs[11]) outputPort(11, readPort(11,
portConfigInputs[11]), false);
if (TOTAL_PORTS >12&& reportPINs[12]) outputPort(12, readPort(12,
portConfigInputs[12]), false);
if (TOTAL_PORTS >13&& reportPINs[13]) outputPort(13, readPort(13,
portConfigInputs[13]), false);
if (TOTAL_PORTS >14&& reportPINs[14]) outputPort(14, readPort(14,
portConfigInputs[14]), false);
if (TOTAL_PORTS >15&& reportPINs[15]) outputPort(15, readPort(15,
portConfigInputs[15]), false);
}
// ----------------------------------------------------------------------------/* sets the pin mode to the correct state and sets the relevant bits in the
* two bit-arrays that track Digital I/O and PWM status
*/
void setPinModeCallback(byte pin, int mode)
{
if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE)
return;
if (Firmata.getPinMode(pin) == PIN_MODE_I2C && isI2CEnabled && mode !=
PIN_MODE_I2C) {
// disable i2c so pins can be used for other functions// the following if statements should reconfigure the pins properly
disableI2CPins();
}
if (IS_PIN_DIGITAL(pin) && mode != PIN_MODE_SERVO) {
if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
detachServo(pin);
}
}
if (IS_PIN_ANALOG(pin)) {
reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ?1 : 0); //
turn on/off reporting
}
if (IS_PIN_DIGITAL(pin)) {
if (mode == INPUT || mode == PIN_MODE_PULLUP) {
portConfigInputs[pin /8] |= (1<< (pin &7));
} else {
portConfigInputs[pin /8] &=~(1<< (pin &7));
}
}
Firmata.setPinState(pin, 0);
switch (mode) {
case PIN_MODE_ANALOG:
if (IS_PIN_ANALOG(pin)) {
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver
#if ARDUINO <= 100
// deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
}
Firmata.setPinMode(pin, PIN_MODE_ANALOG);
}
break;
case INPUT:
// Adafruit: Input without pull up cause pin state changes randomly --> lots of
transmission data
// if (IS_PIN_DIGITAL(pin)) {
// pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver
//#if ARDUINO <= 100
// // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
// digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
//#endif
// Firmata.setPinMode(pin, INPUT);
// }
// break;
case PIN_MODE_PULLUP:
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP);
Firmata.setPinMode(pin, PIN_MODE_PULLUP);
Firmata.setPinState(pin, 1);
}
break;
case OUTPUT:
if (IS_PIN_DIGITAL(pin)) {
if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
// Disable PWM if pin mode was previously set to PWM.
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
}
pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
Firmata.setPinMode(pin, OUTPUT);
}
break;
case PIN_MODE_PWM:
if (IS_PIN_PWM(pin)) {
pinMode(PIN_TO_PWM(pin), OUTPUT);
analogWrite(PIN_TO_PWM(pin), 0);
Firmata.setPinMode(pin, PIN_MODE_PWM);
}
break;
case PIN_MODE_SERVO:
if (IS_PIN_DIGITAL(pin)) {
Firmata.setPinMode(pin, PIN_MODE_SERVO);
if (servoPinMap[pin] == 255 || !servos[servoPinMap[pin]].attached()) {
// pass -1 for min and max pulse values to use default values set// by Servo library
attachServo(pin, -1, -1);
}
}
break;
case PIN_MODE_I2C:
if (IS_PIN_I2C(pin)) {
// mark the pin as i2c// the user must call I2C_CONFIG to enable I2C for a device
Firmata.setPinMode(pin, PIN_MODE_I2C);
}
break;
case PIN_MODE_SERIAL:
break;
default:
Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
}
// TODO: save status to EEPROM here, if changed
}
/*
* Sets the value of an individual pin. Useful if you want to set a pin value but
* are not tracking the digital port state.
* Can only be used on pins configured as OUTPUT.
* Cannot be used to enable pull-ups on Digital INPUT pins.
*/
void setPinValueCallback(byte pin, int value)
{
if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
if (Firmata.getPinMode(pin) == OUTPUT) {
Firmata.setPinState(pin, value);
digitalWrite(PIN_TO_DIGITAL(pin), value);
}
}
}
void analogWriteCallback(byte pin, int value)
{
if (pin < TOTAL_PINS) {
switch (Firmata.getPinMode(pin)) {
case PIN_MODE_SERVO:
if (IS_PIN_DIGITAL(pin))
servos[servoPinMap[pin]].write(value);
Firmata.setPinState(pin, value);
break;
case PIN_MODE_PWM:
if (IS_PIN_PWM(pin))
analogWrite(PIN_TO_PWM(pin), value);
Firmata.setPinState(pin, value);
break;
}
if (port < TOTAL_PORTS) {
// create a mask of the pins on this port that are writable.
lastPin = port *8+8;
if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
for (pin = port *8; pin < lastPin; pin++) {
// do not disturb non-digital pins (eg, Rx & Tx)if (IS_PIN_DIGITAL(pin)) {
// do not touch pins in PWM, ANALOG, SERVO or other modesif (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
pinValue = ((byte)value & mask) ?1 : 0;
if (Firmata.getPinMode(pin) == OUTPUT) {
pinWriteMask |= mask;
} elseif (Firmata.getPinMode(pin) == INPUT && pinValue ==1&&
Firmata.getPinState(pin) !=1) {
// only handle INPUT here for backwards compatibility
#if ARDUINO > 100
pinMode(pin, INPUT_PULLUP);
#else
// only write to the INPUT pin to enable pullups if Arduino v1.0.0 or
// ----------------------------------------------------------------------------/* sets bits in a bit array (int) to toggle the reporting of the analogIns
*/
//void FirmataClass::setAnalogPinReporting(byte pin, byte state) {
//}
void reportAnalogCallback(byte analogPin, int value)
{
if (analogPin < TOTAL_ANALOG_PINS) {
if (value ==0) {
analogInputsToReport = analogInputsToReport &~ (1<< analogPin);
} else {
analogInputsToReport = analogInputsToReport | (1<< analogPin);
// prevent during system reset or all analog pin values will be reported// which may report noise for unconnected analog pinsif (!isResetting) {
// Send pin value immediately. This is helpful when connected via// ethernet, wi-fi or bluetooth so pin states can be known upon// reconnecting.
Firmata.sendAnalog(analogPin, analogRead( ANALOG_TO_PIN(analogPin) ) );
}
}
}
// TODO: save status to EEPROM here, if changed
}
void reportDigitalCallback(byte port, int value)
{
if (port < TOTAL_PORTS) {
reportPINs[port] = (byte)value;
// Send port value immediately. This is helpful when connected via// ethernet, wi-fi or bluetooth so pin states can be known upon// reconnecting.if (value) outputPort(port, readPort(port, portConfigInputs[port]), true);
}
// do not disable analog reporting on these 8 pins, to allow some// pins used for digital, others analog. Instead, allow both types// of reporting to be enabled, but check if the pin is configured// as analog when sampling the analog inputs. Likewise, while// scanning digital pins, portConfigInputs will mask off values from any// pins configured as analog
}
switch (command) {
case I2C_REQUEST:
mode = argv[1] & I2C_READ_WRITE_MODE_MASK;
if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) {
Firmata.sendString("10-bit addressing not supported");
return;
}
else {
slaveAddress = argv[0];
}
// need to invert the logic here since 0 will be default for client// libraries that have not updated to add support for restart txif (argv[1] & I2C_END_TX_MASK) {
stopTX = I2C_RESTART_TX;
}
else {
stopTX = I2C_STOP_TX; // default
}
switch (mode) {
case I2C_WRITE:
Wire.beginTransmission(slaveAddress);
for (byte i =2; i < argc; i +=2) {
data = argv[i] + (argv[i +1] <<7);
wireWrite(data);
}
Wire.endTransmission();
delayMicroseconds(70);
break;
case I2C_READ:
if (argc ==6) {
// a slave register is specified
slaveRegister = argv[2] + (argv[3] <<7);
data = argv[4] + (argv[5] <<7); // bytes to read
}
else {
// a slave register is NOT specified
slaveRegister = I2C_REGISTER_NOT_SPECIFIED;
data = argv[2] + (argv[3] <<7); // bytes to read
}
readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX);
break;
case I2C_READ_CONTINUOUSLY:
if ((queryIndex +1) >= I2C_MAX_QUERIES) {
// too many queries, just ignore
Firmata.sendString("too many queries");
break;
}
if (argc ==6) {
// a slave register is specified
slaveRegister = argv[2] + (argv[3] <<7);
data = argv[4] + (argv[5] <<7); // bytes to read
}
else {
// a slave register is NOT specified
slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED;
data = argv[2] + (argv[3] <<7); // bytes to read
}
queryIndex++;
query[queryIndex].addr = slaveAddress;
query[queryIndex].reg = slaveRegister;
query[queryIndex].bytes = data;
query[queryIndex].stopTX = stopTX;
break;
case I2C_STOP_READING:
byte queryIndexToSkip;
// if read continuous mode is enabled for only 1 i2c device, disable// read continuous reporting for that deviceif (queryIndex <=0) {
queryIndex = -1;
} else {
queryIndexToSkip =0;
// if read continuous mode is enabled for multiple devices,// determine which device to stop reading and remove it's data from// the array, shifiting other array data to fill the spacefor (byte i =0; i < queryIndex +1; i++) {
if (query[i].addr == slaveAddress) {
queryIndexToSkip = i;
break;
}
}
for (byte i = queryIndexToSkip; i < queryIndex +1; i++) {
if (i < I2C_MAX_QUERIES) {
query[i].addr = query[i +1].addr;
query[i].reg = query[i +1].reg;
query[i].bytes = query[i +1].bytes;
query[i].stopTX = query[i +1].stopTX;
}
}
queryIndex--;
}
break;
default:break;
}
break;
case I2C_CONFIG:
delayTime = (argv[0] + (argv[1] <<7));
if (delayTime >0) {
i2cReadDelayTime = delayTime;
}
// these vars are here for clarity, they'll optimized away by the compiler
byte pin = argv[0];
int minPulse = argv[1] + (argv[2] <<7);
int maxPulse = argv[3] + (argv[4] <<7);
if (IS_PIN_DIGITAL(pin)) {
if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached())
{
detachServo(pin);
}
attachServo(pin, minPulse, maxPulse);
setPinModeCallback(pin, PIN_MODE_SERVO);
}
}
break;
case SAMPLING_INTERVAL:
if (argc >1) {
samplingInterval = argv[0] + (argv[1] <<7);
if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) {
samplingInterval = MINIMUM_SAMPLING_INTERVAL;
}
} else {
//Firmata.sendString("Not enough data");
}
break;
case EXTENDED_ANALOG:
if (argc >1) {
int val = argv[1];
if (argc >2) val |= (argv[2] <<7);
if (argc >3) val |= (argv[3] <<14);
analogWriteCallback(argv[0], val);
}
break;
case CAPABILITY_QUERY:
Firmata.write(START_SYSEX);
Firmata.write(CAPABILITY_RESPONSE);
for (byte pin =0; pin < TOTAL_PINS; pin++) {
if (IS_PIN_DIGITAL(pin)) {
Firmata.write((byte)INPUT);
Firmata.write(1);
Firmata.write((byte)PIN_MODE_PULLUP);
Firmata.write(1);
Firmata.write((byte)OUTPUT);
Firmata.write(1);
}
if (IS_PIN_ANALOG(pin)) {
Firmata.write(PIN_MODE_ANALOG);
Firmata.write(10); // 10 = 10-bit resolution
}
if (IS_PIN_PWM(pin)) {
Firmata.write(PIN_MODE_PWM);
Firmata.write(DEFAULT_PWM_RESOLUTION);
}
if (IS_PIN_DIGITAL(pin)) {
Firmata.write(PIN_MODE_SERVO);
Firmata.write(14);
}
if (IS_PIN_I2C(pin)) {
Firmata.write(PIN_MODE_I2C);
Firmata.write(1); // TODO: could assign a number to map to SCL or SDA
}
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.handleCapability(pin);
#endif
Firmata.write(127);
}
Firmata.write(END_SYSEX);
break;
case PIN_STATE_QUERY:
// initialize a defalt state// TODO: option to load config from EEPROM instead of default
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.reset();
#endif
if (isI2CEnabled) {
disableI2CPins();
}
for (byte i =0; i < TOTAL_PORTS; i++) {
reportPINs[i] =false; // by default, reporting off
portConfigInputs[i] =0; // until activated
previousPINs[i] =0;
}
for (byte i =0; i < TOTAL_PINS; i++) {
// pins with analog capability default to analog input// otherwise, pins default to digital outputif (IS_PIN_ANALOG(i)) {
// turns off pullup, configures everything
setPinModeCallback(i, PIN_MODE_ANALOG);
} elseif (IS_PIN_DIGITAL(i)) {
// sets the output to 0, configures portConfigInputs
setPinModeCallback(i, OUTPUT);
}
servoPinMap[i] =255;
}
// by default, do not report any analog inputs
analogInputsToReport =0;
detachedServoCount =0;
servoCount =0;
/* send digital inputs to set the initial state on the host computer,
* since once in the loop(), this firmware will only send on change */
/*
TODO: this can never execute, since no pins default to digital input
but it will be needed when/if we support EEPROM stored config
for (byte i=0; i < TOTAL_PORTS; i++) {
outputPort(i, readPort(i, portConfigInputs[i]), true);
}
*/
isResetting =false;
}
void setup()
{
Serial.begin(115200);
while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println("Bluefruit52 Standard Firmata via BLEUART Example" );
Serial.println("------------------------------------------------\n");
// Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin();
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
// try to go as fast as possible, could be rejected by some central, increase it
if needed
// iOS won't negotitate and will mostly use 30ms
Bluefruit.Periph.setConnInterval(9, 24); // min = 9*1.25=11.25 ms, max =
23*1.25=30ms
// Configure and Start BLE Uart Service// Firmata use several small write(1) --> buffering TXD is required to run
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.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
{
// Skip if not connected and bleuart notification is not enabledif ( !(Bluefruit.connected() && bleuart.notifyEnabled()) ) return;
byte pin, analogPin;
/* DIGITALREAD - as fast as possible, check for changes and output them to the
* FTDI buffer using Serial.print() */
checkDigitalInputs();
/* STREAMREAD - processing incoming messagse as soon as possible, while still
* checking digital inputs. */
while (Firmata.available())
Firmata.processInput();
// TODO - ensure that Stream buffer doesn't go over 60 bytes
currentMillis = millis();
if (currentMillis - previousMillis > samplingInterval) {
previousMillis += samplingInterval;
/* ANALOGREAD - do all analogReads() at the configured sampling interval */
// Init BLE Central Uart Serivce
clientUart.begin();
clientUart.setRxCallback(bleuart_rx_callback);
Scanner
Let's start the advertising scanner to find a peripheral.
We'll hook up the scan result callback with setRxCallback().
Whenever advertising data is found by the scanner, it will be passed to this callback
handler, and we can examine the advertising data there, and only connect to
peripheral(s) that advertise the bleuart service.
Note: If the peripheral has multiple services and bleuart is not included in the UUID
list in the advertising packet, you could optionally use another check such as
matching the MAC address, name checking, using "another service", etc.
Once we find a peripheral that we wish to communicate with, call Bluefruit.Centr
al.connect() to establish connection with it:
void setup()
{
// Other set up .....
/* Start Central Scanning
* - Enable auto scan if disconnected
* - Interval = 100 ms, window = 80 ms
* - Don't use active scan
* - Start(timeout) with timeout = 0 will scan forever (until connected)
*/
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
Bluefruit.Scanner.useActiveScan(false);
Bluefruit.Scanner.start(0); // // 0 = Don't stop scanning after
n seconds
}
/**
* Callback invoked when scanner pick up an advertising data
* @param report Structural advertising data
*/
void scan_callback(ble_gap_evt_adv_report_t* report)
{
// Check if advertising contain BleUart service
if ( Bluefruit.Scanner.checkReportForService(report, clientUart) )
{
Serial.print("BLE UART service detected. Connecting ... ");
// Connect to device with bleuart service in advertising
// read and print out Manufacturer
memset(buffer, 0, sizeof(buffer));
if ( clientDis.getManufacturer(buffer, sizeof(buffer)) )
{
Serial.print("Manufacturer: ");
Serial.println(buffer);
}
// read and print out Model Number
memset(buffer, 0, sizeof(buffer));
if ( clientDis.getModel(buffer, sizeof(buffer)) )
{
Serial.print("Model: ");
Serial.println(buffer);
}
Serial.println();
}
Serial.print("Discovering BLE Uart Service ... ");
Serial.println("Ready to receive from peripheral");
}else
{
Serial.println("Found NONE");
// disconect since we couldn't find bleuart service
Bluefruit.Central.disconnect(conn_handle);
}
}
Full Sample Code
The full sample code for this example can be seen below:
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
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
*********************************************************************/
/*
* This sketch demonstrate the central API(). A additional bluefruit
* that has bleuart as peripheral is required for the demo.
*/
// while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println("Bluefruit52 Central BLEUART Example");
Serial.println("-----------------------------------\n");
// Initialize Bluefruit with maximum connections as Peripheral = 0, Central = 1// SRAM usage required by SoftDevice will increase dramatically with number of
// Init BLE Central Uart Serivce
clientUart.begin();
clientUart.setRxCallback(bleuart_rx_callback);
// Increase Blink rate to different from PrPh advertising mode
Bluefruit.setConnLedInterval(250);
// Callbacks for Central
Bluefruit.Central.setConnectCallback(connect_callback);
Bluefruit.Central.setDisconnectCallback(disconnect_callback);
/* Start Central Scanning
* - Enable auto scan if disconnected
* - Interval = 100 ms, window = 80 ms
* - Don't use active scan
* - Start(timeout) with timeout = 0 will scan forever (until connected)
*/
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
Bluefruit.Scanner.useActiveScan(false);
Bluefruit.Scanner.start(0); // // 0 = Don't stop scanning after
n seconds
}
/**
* Callback invoked when scanner pick up an advertising data
* @param report Structural advertising data
*/
{
// Check if advertising contain BleUart serviceif ( Bluefruit.Scanner.checkReportForService(report, clientUart) )
{
Serial.print("BLE UART service detected. Connecting ... ");
// Connect to device with bleuart service in advertising
Bluefruit.Central.connect(report);
}else
{
// For Softdevice v6: after received a report, scanner will be paused// We need to call Scanner resume() to continue scanning
Bluefruit.Scanner.resume();
}
}
/**
* Callback invoked when an connection is established
* @param conn_handle
*/
void connect_callback(uint16_t conn_handle)
{
Serial.println("Connected");
Serial.print("Dicovering Device Information ... ");
if ( clientDis.discover(conn_handle) )
{
Serial.println("Found it");
char buffer[32+1];
// read and print out Manufacturer
memset(buffer, 0, sizeof(buffer));
if ( clientDis.getManufacturer(buffer, sizeof(buffer)) )
{
Serial.print("Manufacturer: ");
Serial.println(buffer);
}
// read and print out Model Number
memset(buffer, 0, sizeof(buffer));
if ( clientDis.getModel(buffer, sizeof(buffer)) )
{
Serial.print("Model: ");
Serial.println(buffer);
}
/**
* Callback invoked when uart received data
* @param uart_svc Reference object to the service where the data
* arrived. In this example it is clientUart
*/
void bleuart_rx_callback(BLEClientUart& uart_svc)
{
Serial.print("[RX]: ");
while ( uart_svc.available() )
{
Serial.print( (char) uart_svc.read() );
{
if ( Bluefruit.Central.connected() )
{
// Not discovered yetif ( clientUart.discovered() )
{
// Discovered means in working state// Get Serial input and send to Peripheralif ( Serial.available() )
{
delay(2); // delay a bit for all characters to arrive
if ( bleuart.notifyEnabled() )
{
// Forward data from our peripheral to Mobile
bleuart.print( str );
}else
{
// response with no prph message
clientUart.println("[Cent] Peripheral role not connected");
}
}
// Forward data from Mobile to our peripheral
char str[20+1] = { 0 };
bleuart.read(str, 20);
Serial.print("[Prph] RX: ");
Serial.println(str);
if ( clientUart.discovered() )
{
clientUart.print(str);
}else
{
bleuart.println("[Prph] Central role not connected");
}
}
Peripheral Role
The first thing to do for the peripheral part of our code is to setup the connect
callback, which fires when a connection is established/disconnected with the central.
Alternatively you could poll the connection status with connected(), but callbacks
helps to simplify the code significantly:
// Callbacks for Peripheral
Bluefruit.setConnectCallback(prph_connect_callback);
Bluefruit.setDisconnectCallback(prph_disconnect_callback);
Central Role
Next we setup the Central mode connect callback, which fires when a connection is
established/disconnected with a peripheral device:
// Callbacks for Central
Bluefruit.Central.setConnectCallback(cent_connect_callback);
Bluefruit.Central.setDisconnectCallback(cent_disconnect_callback);
Advertising and Scanner
It is possible to start both the scanner and advertising at the same time so that we can
discover and be discovered by other BLE devices. For the scanner, we use a filter that
only fires the callback if a specific UUID is found in the advertising data of the peer
device:
/* Start Central Scanning
* - Enable auto scan if disconnected
* - Interval = 100 ms, window = 80 ms
* - Filter only accept bleuart service
* - Don't use active scan
* - Start(timeout) with timeout = 0 will scan forever (until connected)
*/
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
Bluefruit.Scanner.filterUuid(bleuart.uuid);
Bluefruit.Scanner.useActiveScan(false);
Bluefruit.Scanner.start(0); // 0 = Don't stop scanning after n
seconds
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.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
Full Sample Code
The full sample code for this example can be seen below:
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
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
*********************************************************************/
/*
* This sketch demonstrate how to run both Central and Peripheral roles
* at the same time. It will act as a relay between an central (mobile)
* to another peripheral using bleuart service.
*
* Mobile <--> DualRole <--> peripheral Ble Uart
*/
{
Serial.begin(115200);
while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println("Bluefruit52 Dual Role BLEUART Example");
Serial.println("-------------------------------------\n");
// Initialize Bluefruit with max concurrent connections as Peripheral = 1,
Central = 1
// SRAM usage required by SoftDevice will increase with number of connections
Bluefruit.begin(1, 1);
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
// Callbacks for Peripheral
Bluefruit.Periph.setConnectCallback(prph_connect_callback);
Bluefruit.Periph.setDisconnectCallback(prph_disconnect_callback);
// Callbacks for Central
Bluefruit.Central.setConnectCallback(cent_connect_callback);
Bluefruit.Central.setDisconnectCallback(cent_disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Configure and Start BLE Uart Service
bleuart.begin();
bleuart.setRxCallback(prph_bleuart_rx_callback);
// Init BLE Central Uart Serivce
clientUart.begin();
clientUart.setRxCallback(cent_bleuart_rx_callback);
/* Start Central Scanning
* - Enable auto scan if disconnected
* - Interval = 100 ms, window = 80 ms
* - Filter only accept bleuart service
* - Don't use active scan
* - Start(timeout) with timeout = 0 will scan forever (until connected)
*/
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
Bluefruit.Scanner.filterUuid(bleuart.uuid);
Bluefruit.Scanner.useActiveScan(false);
Bluefruit.Scanner.start(0); // 0 = Don't stop scanning after n
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.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 loop()
{
// do nothing, all the work is done in callback
}
/*------------------------------------------------------------------*/
/* Central
*------------------------------------------------------------------*/
{
// Since we configure the scanner with filterUuid()// Scan callback only invoked for device with bleuart service advertised // Connect to the device with bleuart service in advertising packet
Bluefruit.Central.connect(report);
}
void cent_connect_callback(uint16_t conn_handle)
{
// Get the reference to current connection
BLEConnection* connection = Bluefruit.Connection(conn_handle);
/**
* Callback invoked when uart received data
* @param cent_uart Reference object to the service where the data
* arrived. In this example it is clientUart
*/
if ( bleuart.notifyEnabled() )
{
// Forward data from our peripheral to Mobile
bleuart.print( str );
}else
{
// response with no prph message
clientUart.println("[Cent] Peripheral role not connected");
}
}
if ( !hrmc.discover() )
{
// Measurement chr is mandatory, if it is not found (valid), then disconnect
Serial.println("not found !!!");
Serial.println("Measurement characteristic is mandatory but not found");
Bluefruit.Central.disconnect(conn_handle);
return;
}
Serial.println("Found it");
// Measurement is found, continue to look for option Body Sensor Location
// https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
// Body Sensor Location is optional, print out the location in text if present
Serial.print("Discovering Body Sensor Location characteristic ... ");
if ( bslc.discover() )
{
Serial.println("Found it");
// Body sensor location value is 8 bit
const char* body_str[] = { "Other", "Chest", "Wrist", "Finger", "Hand", "Ear
Lobe", "Foot" };
// Read 8-bit BSLC value from peripheral
uint8_t loc_value = bslc.read8();
5. Once hrmc is discovered, you should enable its notification by calling hrmc.enabl
eNotify() . If this succeeded (return true), peripheral can now send data to us using
notify message. Which will trigger the callback that we setup earlier to handle
incoming data.
// Connect Callback Part 3
void connect_callback(uint16_t conn_handle)
{
.......
// Reaching here means we are ready to go, let's enable notification on
measurement chr
if ( hrmc.enableNotify() )
{
Serial.println("Ready to receive HRM Measurement value");
}else
{
Serial.println("Couldn't enable notify for HRM Measurement. Increase DEBUG
LEVEL for troubleshooting");
}
}
/**
* Hooked callback that triggered when a measurement value is sent from peripheral
* @param chr Pointer to client characteristic that even occurred,
* in this example it should be hrmc
* @param data Pointer to received data
* @param len Length of received data
*/
void hrm_notify_callback(BLEClientCharacteristic* chr, uint8_t* data, uint16_t len)
{
// https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml
// Measurement contains of control byte0 and measurement (8 or 16 bit) + optional
field
// if byte0's bit0 is 0 --> measurement is 8 bit, otherwise 16 bit.
The full sample code for this example can be seen below:
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
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
*********************************************************************/
// Initialise the Bluefruit module
Serial.println("Initialise the Bluefruit nRF52 module");
Bluefruit.begin();
// 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("Bluefruit Feather52");
bledis.begin();
// Start the BLE Battery Service and set it to 100%
Serial.println("Configuring the Battery Service");
blebas.begin();
blebas.write(100);
// 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");
}
// 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?
// 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?
// 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?
/**
* 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
*/
{
// 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");
}
}
}
// 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 sentif ( hrmc.notify(hrmdata, sizeof(hrmdata)) ){
Serial.print("Heart Rate Measurement updated to: "); Serial.println(bps);
}else{
Serial.println("ERROR: Notify not set in the CCCD or not connected!");
}
}
// Only send update once per second
delay(1000);
}
Arduino Bluefruit nRF52 API
The Adafruit nRF52 core defines a number of custom classes that aim to make it easy
/*------------- Client Service -------------*/
bool addService(BLEClientService& service);
// Functions to work with the raw advertising packet
uint8_t count(void);
uint8_t* getData(void);
bool setData(const uint8_t* data, uint8_t count);
void clearData(void);
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.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
}
The Bluefruit nRF52 BSP codebase is undergoing active development based on
customer feedback and testing. As such, the class documentation here is
incomplete, and you should consult the Github repo for the latest code and API
developments: https://goo.gl/LdEx62
This documentation is based on BSP 0.7.0 and higher. Please make sure you
have an up to date version before using the code below.
The BLEScanner class is used inCentral Mode, and facilitates scanning for BLE
peripherals in range and parsing the advertising data that is being sent out by the
peripherals.
The BLEScanner class is normally accessed via the Bluefruit class (instantiated at
startup), as shown below:
/* Start Central Scanning
* - Enable auto scan if disconnected
* - Filter for devices with a min RSSI of -80 dBm
* - Interval = 100 ms, window = 50 ms
* - Use active scan (requests the optional scan response packet)
* - Start(0) = will scan forever since no timeout is given
*/
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.filterRssi(-80); // Only invoke callback when RSSI
>= -80 dBm
Bluefruit.Scanner.setInterval(160, 80); // in units of 0.625 ms
Bluefruit.Scanner.useActiveScan(true); // Request scan response data
Bluefruit.Scanner.start(0); // 0 = Don't stop scanning after n
seconds
Whenever a valid advertising packet is detected (based on any optional filters that are
applied in the BLEScanner class), a dedicated callback function (see
rx_callback_t ) will be called.
The callback function has the following signature:
NOTE: ble_gap_evt_adv_report_t is part of the Nordic nRF52 SD and is defined in
ble_gap.h
void scan_callback(ble_gap_evt_adv_report_t* report)
{
/* Display the timestamp and device address */
if (report->scan_rsp)
{
/* This is a Scan Response packet */
Serial.printf("[SR%10d] Packet received from ", millis());
}
else
{
/* This is a normal advertising packet */
Serial.printf("[ADV%9d] Packet received from ", millis());
}
Serial.printBuffer(report->peer_addr.addr, 6, ':');
Serial.print("\n");