Adafruit Feather nRF52840 Sense User Manual

Adafruit Feather nRF52840 Sense

Created by Kattni Rembor
https://learn.adafruit.com/adafruit-feather-sense
Last updated on 2021-12-12 04:37:16 PM EST
©Adafruit Industries Page 1 of 186

Table of Contents

Overview

Pinouts

Microcontroller and QSPI
Power Pins
Analog Pins
PWM Outputs and I2C Pins
Special Note
Sensors
USB and Battery
Buttons
NeoPixel and Status LEDs

Power Management

Battery + USB Power
Power supplies
Measuring Battery
ENable pin
Alternative Power Options

Arduino Support Setup

1. BSP Installation
2. LINUX ONLY: adafruit-nrfutil Tool Installation
3. Update the bootloader (nRF52832 ONLY)
Advanced Option: Manually Install the BSP via 'git'
7
11
12
13
14
15
16
20
21
22
23
23
24
25
26
26
27
27
29
30
30

Arduino Board Testing

1. Select the Board Target
2. Select the USB CDC Serial Port
Download & Install CP2104 Driver (nRF52832)
Download & Install Adafruit Driver (nRF52840 Windows)
3. Update the bootloader (nRF52832 Feather Only)
4. Run a Test Sketch

Arduino Sensor Example

Sensors
Code

Arduino BLE Examples

Example Source Code
Documented Examples

Advertising: Beacon

Complete Code
Output

BLE UART: Controller

Setup
Complete Code
31
31
31
32
32
33
33
35
35
35
38
38
39
39
39
41
42
42
44
©Adafruit Industries Page 2 of 186

Custom: HRM

HRM Service Definition
Implementing the HRM Service and Characteristics
Service + Characteristic Setup Code Analysis
Full Sample Code
49
49
49
51
52

BLE Pin I/O

Setup
Complete Code

Central BLEUART

Client Services
Scanner
Central Role
Full Sample Code

Dual Roles BLEUART

Server & Client Service Setup
Peripheral Role
Central Role
Advertising and Scanner
Full Sample Code

Custom: Central HRM

HRM Service Definition
Implementing the HRM Service and Characteristics
Client Service + Characteristic Code Analysis
Full Sample Code
56
56
57
71
71
72
73
74
77
78
79
79
79
80
84
84
84
85
88

Arduino Bluefruit nRF52 API

AdafruitBluefruit

API
Examples

BLEGap

BLEAdvertising

API
Related Information
Example

BLEScanner

API
setRxCallback(rx_callback_t fp)
void useActiveScan(bool enable);
void filterRssi(int8_t min_rssi);void filterMSD(uint16_t manuf_id);void filterUuid(BLEUuid ble_uuid);void
filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2);void filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2,
BLEUuid ble_uuid3);void filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2, BLEUuid ble_uuid3, BLEUuid
ble_uuid4);void filterUuid(BLEUuid ble_uuid[], uint8_t count);
void clearFilters(void);
bool start(uint16_t timeout = 0); bool stop(void);
void restartOnDisconnect(bool enable);
Examples
91
92
92
94
94
95
95
96
97
98
98
99
100
101
101
102
102
102
©Adafruit Industries Page 3 of 186

BLEService

Basic Usage
Order of Operations (Important!)
API
Example
102
103
103
103
104

BLECharacteristic

Basic Usage
Order of Operations (Important!)
API
Example

BLEClientService

Basic Usage
API
Example

BLEClientCharacteristic

Basic Usage
API
Example

BLEDiscovery

API

BLEDis

API
Example
Output
106
106
107
107
109
111
111
111
112
115
116
116
117
121
121
121
123
123
124

BLEUart

API
Example

BLEClientUart

API
Examples

BLEBeacon

API
Example
Testing

BLEMidi

API
Installing the Arduino MIDI Library
Example
Usage

BLEHidAdafruit

API
Example Sketches
Bonding HID Devices
Setting up your Bluefruit device for bonding
Bonding on iOS
124
124
125
127
127
127
130
131
131
132
133
133
134
134
137
138
138
139
139
140
141
©Adafruit Industries Page 4 of 186
Testing the HID Keyboard and Bonding
142

BLEAncs

API
ANCS OLED Example
Sketch Requirements
Loading the Sketch
Pairingto your Mobile Device
Wait for Alerts

BLEClientCts

API
Client CTS OLED Example
Sketch Requirements
Loading the Sketch
Pairingto your Mobile Device
Wait for Time Data

BLECentral

nRF52 ADC

Analog Reference Voltage
Analog Resolution
Default ADC Example (10-bit, 3.6V Reference)
Advanced Example (12-bit, 3.0V Reference)
142
142
143
143
143
144
146
147
147
148
148
148
149
150
151
152
152
152
153
153

CircuitPython on Feather Sense

Set up CircuitPython Quick Start!

Feather Sense CircuitPython Libraries

Installing CircuitPython Libraries on your Feather Sense

CircuitPython Pins and Modules

CircuitPython Pins
import board
I2C, SPI, and UART
What Are All the Available Names?
Microcontroller Pin Names
CircuitPython Built-In Modules

CircuitPython Sense Demo

Feather Sense Sensor Demo

Welcome to CircuitPython

Getting Started with BLE and CircuitPython

Guides
155
155
157
157
159
159
160
161
162
163
164
164
165
167
167
167

CircuitPython Essentials

Software Resources

Bluefruit LE Client Apps and Libraries
Bluefruit LE Connect(Android/Java)
Bluefruit LE Connect (iOS/Swift)
Bluefruit LE Connect for OS X(Swift)
©Adafruit Industries Page 5 of 186
169
169
169
169
170
170
Bluefruit LE Command Line Updater for OS X (Swift)
Deprecated:Bluefruit Buddy(OS X)
ABLE(Cross Platform/Node+Electron)
Bluefruit LE Python Wrapper
Debug Tools
AdaLink(Python)
Adafruit nRF51822 Flasher(Python)
171
172
172
173
173
174
174

Downloads

Files
Demo code
Schematic
Fab Print

FAQs

175
175
176
178
178
178
©Adafruit Industries Page 6 of 186

Overview

The Adafruit Feather Bluefruit Sense takes our popular Feather nRF52840 Express(h
ttps://adafru.it/DLQ) and adds a smorgasbord of sensors to make a great wireless
sensor platform. This Feather microcontroller comes with Bluetooth Low Energy and n
ative USB support featuring the nRF52840! This Feather is an 'all-in-one' Arduino-
compatible + Bluetooth Low Energy with built in USB plus battery charging. With
native USB it works great with CircuitPython, too.
©Adafruit Industries Page 7 of 186
Like the Feather nRF52840, this chip comes with Arduino IDE support - you can
program the nRF52840 chip directly to take full advantage of the Cortex-M4
processor, and then calling into the Nordic SoftDevice radio stack when you need to
communicate over BLE. Since the underlying API and peripherals are the same for the
'832 and '840, you can supercharge your older nRF52832 projects with the same
exact code, with a single recompile!
This Feather is also a BLE-friendly CircuitPython board! CircuitPython works best with
disk drive access, and this is the only BLE-plus-USB-native chip that has the memory
to handle running a little Python interpreter. The massive RAM and speedy Cortex
M4F chip make this a good match. Make BLE central or peripheral devices with the
ease of CircuitPython.
©Adafruit Industries Page 8 of 186
A chorus of supporting sensors surround the module so you can do all sorts of enviro
nmental and motion sensing:
ST Micro series 9-DoFmotion - LSM6DS33 Accel/Gyro(https://adafru.it/IfN) + LI
S3MDL magnetometer(https://adafru.it/IHC)
APDS9960 Proximity, Light, Color, and Gesture Sensor(https://adafru.it/IHD)
PDM Microphone sound sensor(https://adafru.it/FB0)
SHT Humidity(https://adafru.it/IHE)
BMP280 temperature and barometric pressure/altitude(https://adafru.it/ufr)
©Adafruit Industries Page 9 of 186
Features:
ARM Cortex M4F (with HW floating point acceleration) running at 64MHz
1MB flash and 256KB SRAM
Native Open Source USB stack - pre-programmed with UF2 bootloader
Bluetooth Low Energy compatible 2.4GHz radio (Details available in thenRF528
40(https://adafru.it/Dvy) product specification)
FCC / IC / TELEC certified module
Up to +8dBm output power
21 GPIO, 6 x 12-bit ADC pins, up to 12 PWM outputs (3 PWM modules with 4
outputs each)
Pin #13 red LED for general purpose blinking, Blue LED for general purpose
connection status, NeoPixel for colorful feedback
Power/enable pin
Measures 2.0" x 0.9" x 0.28" (51mm x 23mm x 7.2mm) without headers soldered
in
Light as a (large?) feather - 6 grams
4 mounting holes
Reset button
SWD debug pads on bottom of PCB
Works out of the box with all of our Adafruit FeatherWings!(https://adafru.it/vby)
(Even the UART-using ones like the GPS FeatherWing)
©Adafruit Industries Page 10 of 186

Pinouts

The Feather nRF52840 Sense is full of all kinds of goodies. Let's take a look!

Microcontroller and QSPI

Nordic nRF52840 Bluetooth LE processor - 1 MB of Flash, 256KB RAM, 64 MHz
Cortex M4 processor.
©Adafruit Industries Page 11 of 186
QSPI flash - 2MB of internal flash storage for datalogging or CircuitPython code.
QSPI requires 6 pins, which are not broken out on the 0.1" pin headers to avoid
conflicts. QSPI is neat because it allows you to have 4 data in/out lines instead
of just SPI's single line in and single line out. This means that QSPI is at least 4
times faster. But in reality is at least 10x faster because you can clock the QSPI
peripheral much faster than a plain SPI peripheral.

Power Pins

3V: This pin is connected to the output of the on board 3.3V regulator. It can be
used to supply 3.3V power to external sensors, breakouts or FeatherWings.
LIPO Input (Bat): This is the voltage supply off the optional LIPO cell that can be
connected via the JST PH connector. It is nominally ~3.5-4.2V.
VREG Enable (En): This pin can be set to GND to disable the 3.3V output from
the on board voltage regulator. By default it is set high via a pullup resistor.
USB Power(USB): This is the voltage supply off USB connector, nominally
4.5-5.2V.
©Adafruit Industries Page 12 of 186

Analog Pins

The 6 available analog inputs (A0 .. A5) can be configured to generate 8, 10 or 12-bit
data (or 14-bits with over-sampling), at speeds up to 200kHz (depending on the bit-
width of the values generated), based on either an internal 0.6V reference or the
external supply.
The following default values are used for Arduino. See this guide's nRF52 ADC page(
https://adafru.it/REi) for details about changing these settings.
Default voltage range: 0-3.6V (uses the internal 0.6V reference with 1/6 gain)
Default resolution: 12-bit (0..4096)
Default mV per lsb (assuming 3.6V and 12-bit resolution): 1 LSB = 0.87890625
mV
CircuitPython uses 1/4 gain with a VDD/4 reference voltage.
An additional two ADC pins are available but pre-connected to provide specific
functionality:
AREF (A7 / P0.31), which can be used as an optional external analog reference
for the internal comparator (COMP) peripheral. AREF is not available for use with
the ADC. This pin can be accessed in code via PIN_AREF orA7. If using an
external AREF, thismust be less than or equal to VDD, which is usually 3.3V!
©Adafruit Industries Page 13 of 186
VOLTAGE_MONITOR (A6 / P0.29): This pin is hard wired to a voltage-divider on
the LIPO battery input, allowing you to safely measure the LIPO battery level on
your device. If possible, you should avoid using this pin as an input because you
will lose the ability to read the battery voltage. You can use it as an output just
make sure to switch the pin to analog input when you want to do the battery
read, then back to output when toggling pins
In order to avoid damaging the sensitive analog circuitry, AREF must be equal to
or lower than VDD (3.3V)!
Unlike digital functions, which can be remapped to any GPIO/digital pin, the ADC
functionality is tied to specified pins, labelled as A* in the image above (A0, A1,
etc.).

PWM Outputs and I2C Pins

PWM Outputs
Any GPIO pin can be configured as a PWM output, using the dedicated PWM block.
Three PWM modules can provide up to 12 PWM channels with individual frequency
control in groups of up to four channels.
©Adafruit Industries Page 14 of 186
I2C Pins
I2C pins on the nRF52840 have 4.7K pullup resistors installed and are connected to
all-but-the-microphone sensors. You can connect any other sensors as long as there
is not an I2C address collision

Special Note

©Adafruit Industries Page 15 of 186
The following pins have some restrictions that need to be taken into account when
using them:
D2/NFC2: The D2 pin is uses the same pad as one-half of the NFC antenna pins.
By default, the nRF52840 Feather ships with these pins configured for GPIO
mode, which is done by writing a value to the UICR flash config memory. If you
wish to use NFC, you will need to erase the UICR memory which requires
erasing the entire chip, and you will need a Segger J-Link(https://adafru.it/yDp)
to reflash the bootloader and firmware.

Sensors

Gyro + Accel: LSM6DS33- This sensor is a 6-DoF IMU accelerometer + gyroscope.
The 3-axis accelerometer, can tell you which direction is down towards the Earth (by
measuring gravity) or how fast the Feather Sense is accelerating in 3D space. The 3-
axis gyroscope that can measure spin and twist. Pair with a triple-axis magnetometer
to create a 9-DoF inertial measurement unit that can detect its orientation in real-
space thanks to Earth's stable magnetic field. Sensor is I2C on standard pins, address
0x6A and IRQ pin on digital pin 3
©Adafruit Industries Page 16 of 186
Magnetometer: LIS3MDL - Sense the magnetic fields that surround us with this handy
triple-axis magnetometer (compass) module. Magnetometers can sense where the
strongest magnetic force is coming from, generally used to detect magnetic north, but
can also be used for measuring magnetic fields. This sensor tends to be paired with a
6-DoF (degree of freedom) accelerometer/gyroscope to create a 9-DoF inertial
measurement unit that can detect its orientation in real-space thanks to Earth's stable
magnetic field. Sensor is I2C on standard pins, address 0x1C
Light + Gesture + Proximity: APDS9960 - Detect simple gestures (left to right, right to
left, up to down, down to up are currently supported), return the amount of red, blue,
green, and clear light, or return how close an object is to the front of the sensor. This
©Adafruit Industries Page 17 of 186
sensor has an integrated IR LED and driver, along with four directional photodiodes
that sense reflected IR energy from the LED. Since there are four IR sensors, you can
measure the changes in light reflectance at each of the cardinal locations over time
and turn those changes into gestures. Sensor is I2C on standard pins, address 0x39
and IRQ pin on digital pin 36
Humidity: SHT30 - This sensor has an excellent ±2% relative humidity and ±0.5°C
accuracy for most uses. Sensor is I2C on standard pins, address 0x44
Temp + Pressure: BMP280 - This sensor is a precision sensing solution for measuring
barometric pressure with ±1 hPa absolute accuracy, and temperature with ±1.0°C
©Adafruit Industries Page 18 of 186
accuracy. Because pressure changes with altitude, and the pressure measurements
are so good, you can also use it as an altimeter with ±1 meter accuracy. It has a low
altitude noise of 0.25m and a fast conversion time. Sensor is I2C on standard pins,
address 0x77
PDM Microphone sound sensor: MP34DT01-M - PDM sound sensor. In CircuitPython,
board.MICROPHONE_DATA is PDM data, and board.MICROPHONE_CLOCK is PDM
clock. In Arduino, D34 is PDM data, and D35 is PDM clock.
©Adafruit Industries Page 19 of 186

USB and Battery

USB Micro - This USB port is used for programming and/or powering the Feather
Sense. It is a standard USB Micro connector.
Battery - 2-pin JST PH connector for a battery. Power the Feather Sense from
any 3V-6V power source, as it has internal regulator and protection diodes. You
can also charge LiPoly batteries plugged into this connector using USB power.
Check that the batteries you are using are the the same as Adafruit polarity.
Wrong polarity batteries will destroy the charging circuitry
©Adafruit Industries Page 20 of 186

Buttons

Reset button - The button on the left next to the USB connector resets the
board. Press once to reset. Quickly press twice to enter the bootloader.
User button - The button on the right is both usable by the bootloader and user-
controllable. Address it in code using board.SWITCH in CircuitPython and D7
in Arduino.
©Adafruit Industries Page 21 of 186

NeoPixel and Status LEDs

NeoPixel - The addressable RGB NeoPixel LED is used as a status LED by the
bootloader and CircuitPython, but is also controllable using code. Control it
using board.NEOPIXEL in CircuitPython and D8 in Arduino.
Red status LED - This little red LED, labeled D13, works as a status LED in the
bootloader. Otherwise, it is controllable using code by addressing board.RED_
LED in CircuitPython, and D13 in Arduino.
Blue status LED - This little blue LED, labeled Conn, works as a connectivity
status LED in Arduino, and is user-controllable in both Arduino and
CircuitPython. Control it in code by addressing board.BLUE_LED in
CircuitPython and D4 in Arduino.
Charge status LED - The little LED, labeled CHG, below the USB connector is the
charge status LED. When no battery is connected, it flashes. When a battery is
connected and charging, the LED is steady amber.
©Adafruit Industries Page 22 of 186

Power Management

Battery + USB Power

We wanted to make the Feather easy to power both when connected to a computer
as well as via battery. There's two ways to power a Feather. You can connect with a
MicroUSB cable (just plug into the jack) and the Feather will regulate the 5V USB
down to 3.3V. You can also connect a 4.2/3.7V Lithium Polymer (Lipo/Lipoly) or Lithium
Ion (LiIon) battery to the JST jack. This will let the Feather run on a rechargable
battery. When the USB power is powered, it will automatically switch over to USB for
power, as well as start charging the battery (if attached) at 200mA. This happens
'hotswap' style so you can always keep the Lipoly connected as a 'backup' power that
will only get used when USB power is lost.
The JST connector polarity is matched to Adafruit LiPoly batteries. Using wrong
polarity batteries can destroy your Feather. See image above for the right
polaruty
©Adafruit Industries Page 23 of 186
The above shows the Micro USB jack (left), Lipoly JST jack (top left), as well as the
changeover diode ( just to the right of the JST jack) and the Lipoly charging circuitry
(to the right of the JST jack). There's also a CHG LED, which will light up while the
battery is charging. This LED might also flicker if the battery is not connected.

Power supplies

You have a lot of power supply options here! We bring out the BAT pin, which is tied
to the lipoly JST connector, as well as USB which is the +5V from USB if connected.
We also have the 3V pin which has the output from the 3.3V regulator. We use a
500mA peak regulator. While you can get 500mA from it, you can't do it continuously
from 5V as it will overheat the regulator. It's fine for, say, powering an ESP8266 WiFi
chip or XBee radio though, since the current draw is 'spikey' & sporadic.
©Adafruit Industries Page 24 of 186

Measuring Battery

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.
import board from analogio import AnalogIn
vbat_voltage = AnalogIn(board.VOLTAGE_MONITOR)
©Adafruit Industries Page 25 of 186
def get_voltage(pin): return (pin.value * 3.6) / 65536 * 2
battery_voltage = get_voltage(vbat_voltage) print("VBat voltage: {:.2f}".format(battery_voltage))

ENable pin

If you'd like to turn off the 3.3V regulator, you can do that with the EN(able) pin. Simply
tie this pin to Ground and it will disable the 3V regulator. The BAT and USB pins will
still be powered

Alternative Power Options

The two primary ways for powering a feather are a 3.7/4.2V LiPo battery plugged into
the JST port or a USB power cable.
If you need other ways to power the Feather, here's what we recommend:
For permanent installations, a 5V 1A USB wall adapter(https://adafru.it/duP) will
let you plug in a USB cable for reliable power
For mobile use, where you don't want a LiPoly, use a USB battery pack!(https://
adafru.it/e2q)
If you have a higher voltage power supply, use a 5V buck converter(https://
adafru.it/DHs) and wire it to a USB cable's 5V and GND input(https://adafru.it/
DHu)
©Adafruit Industries Page 26 of 186
Here's what you cannot do:
Do not use alkaline or NiMH batteries and connect to the battery port - this will
destroy the LiPoly charger and there's no way to disable the charger
Do not use 7.4V RC batteries on the battery port - this will destroy the board
The Feather is not designed for external power supplies - this is a design decision to
make the board compact and low cost. It is not recommended, but technically
possible:
Connect an external 3.3V power supply to the 3V and GND pins. Not
recommended, this may cause unexpected behavior and the EN pin will no
longer. Also this doesn't provide power on BAT or USB and some Feathers/
Wings use those pins for high current usages. You may end up damaging your
Feather.
Connect an external 5V power supply to the USB and GND pins. Not
recommended, this may cause unexpected behavior when plugging in the USB
port because you will be back-powering the USB port, which could confuse or
damage your computer.

Arduino Support Setup

You caninstall the Adafruit Bluefruit nRF52 BSP (Board Support Package) in two
steps:
nRF52 support requires at least Arduino IDE version 1.8.6! Please make sure you
have an up to date version before proceeding with this guide!
Please consult the FAQ section at the bottom of this page if you run into any
problems installing or using this BSP!

1. BSP Installation

Recommended: Installing the BSPvia the Board Manager
Download and install the Arduino IDE(https://adafru.it/fvm) (At least v1.8)
Start the Arduino IDE
Go into Preferences
©Adafruit Industries Page 27 of 186
Add https://www.adafruit.com/package_adafruit_index.json as an 'Ad
ditional Board Manager URL' (see image below)
Restart the Arduino IDE
Open the Boards Manageroption from the Tools -> Board menu and install 'Ada
fruit nRF52 by Adafruit' (see image below)
It will take up to a few minutes to finish installing the cross-compiling toolchain and
tools associated with this BSP.
The delay during the installation stage shown in the image below is normal, please be
patient and let the installation terminate normally:
Once the BSP is installed, select
Adafruit Bluefruit nRF52832 Feather (for the nRF52 Feather)
Adafruit Bluefruit nRF52840 Feather Express (for the nRF52840 Feather)
©Adafruit Industries Page 28 of 186
Adafruit ItsyBitsy nRF52840 (for the Itsy '850)
Adafruit Circuit Playground Bluefruit (for the CPB)
etc...
from the Tools -> Board menu, which will update your system config to use the right
compiler and settings for the nRF52:

2. LINUX ONLY: adafruit-nrfutil Tool Installation

adafruit-nrfutil(https://adafru.it/Cau)is amodified versionof Nordic's nrfutil(https://
adafru.it/vaG), which is used to flash boards using the built in serial bootloader. It is
originally written for python2, but have been migrated to python3 and renamed toad
afruit-nrfutilsince BSP version 0.8.5.
This step is only required on Linux, pre-built binaries of adafruit-nrfutil for
Windows and MacOS are already included in the BSP. That should work out of
the box for most setups.
Install python3 if it is not installed in your system already
$ sudo apt-get install python3
Then run the following command to install the tool from PyPi
$ pip3 install --user adafruit-nrfutil
Add pip3 installation dir to your PATH if it is not added already. Make sure adafruit-
nrfutil can be executed in terminal by running
©Adafruit Industries Page 29 of 186
$ adafruit-nrfutil version adafruit-nrfutil version 0.5.3.post12

3. Update the bootloader (nRF52832 ONLY)

To keep up with Nordic's SoftDevice advances, you will likely need to update your
bootloader if you are using the original nRF52832 basedBluefruit nRF52 Feather
boards.
Follow this link for instructions on how to do that
This step ISN'T required for the newer nRF52840 Feather Express, which has a
different bootloader entirely!
Update the nRF52832 Bootloader
https://adafru.it/Dsx

Advanced Option: Manually Install the BSP via 'git'

If you wish to do any development against the core codebase (generate pull requests,
etc.), you can also optionally install the Adafruit nRF52 BSP manually using 'git', as
decribed below:

Adafruit nRF52 BSP via git (for core development and PRs only)

Install BSP via Board Manager as above to install compiler & tools.
1.
Delete the core folder nrf52 installed by Board Manager in Adruino15,
2.
depending on your OS. It could be
macOS: ~/Library/Arduino15/packages/adafruit/hardware/nrf52
Linux: ~/.arduino15/packages/adafruit/hardware/nrf52
Windows: %APPDATA%
\Local\Arduino15\packages\adafruit\hardware\nrf52
Go tothe sketchbook folder on your command line, which should be one of the
3.
following:
macOS: ~/Documents/Arduino
Linux: ~/Arduino
Windows: ~/Documents/Arduino
©Adafruit Industries Page 30 of 186
Create a folder named hardware/Adafruit , if it does not exist, and change
4.
directories into it.
Clone the Adafruit_nRF52_Arduino(https://adafru.it/vaF) repo in the folder
5.
described in step 2:
git clone --recurse-submodules git@github.com:adafruit/
Adafruit_nRF52_Arduino.git
6. This should result in a final folder name like ~/Documents/Arduino/hardware
/Adafruit/Adafruit_nRF52_Arduino (macOS).
Restart the Arduino IDE
7.

Arduino Board Testing

Once you have the Bluefruit nRF52 BSP setup on your system, you need to select the
appropriate board, which will determine the compiler and expose some new menus
options:

1. Select the Board Target

Go to theTools menu
SelectTools > Board > Adafruit Bluefruit nRF52 Feather fornRF52832-based
boards
SelectTools > Board > Adafruit Bluefruit nRF52840 Feather Express fornRF528
40-based boards
Select Tools > Board > Adafruit CLUE for the Adafruit CLUE

2. Select the USB CDC Serial Port

Finally, you need to set the serial port used by Serial Monitor and the serial
bootloader:
Go to Tools > Port and select the appropriate device
©Adafruit Industries Page 31 of 186

Download & Install CP2104 Driver (nRF52832)

For Feather nRF52832 If you don't see the SiLabs device listed, you may need to
install the SiLabs CP2104 driver(https://adafru.it/yfA) on your system.
On MacOSIf you see this dialog message while installing driver
On MacOS If you see this dialog
message while installing driver, System
Extension Blocked
And cannot find the serial port of
CP2104, it is highly possible that driver is
blocked.
To enable it go to System Preferences ->
Security & Privacy and click allow if you
see Silab in the developer name.

Download & Install Adafruit Driver (nRF52840 Windows)

For Feather nRF52840, If you are using Windows, you will need to followsWindows
Driver Installation(https://adafru.it/D0H)to download and install driver.
©Adafruit Industries Page 32 of 186

3. Update the bootloader (nRF52832 Feather Only)

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 theExamples 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
port. Not able to proceed.
Traceback (most recent call last):
©Adafruit Industries Page 33 of 186
 File "nordicsemi\__main__.py", line 294, in serial
 File "nordicsemi\dfu\dfu.py", line 235, in dfu_send_images
 File "nordicsemi\dfu\dfu.py", line 203, in _dfu_send_image
 File "nordicsemi\dfu\dfu_transport_serial.py", line 155, in
send_init_packet
 File "nordicsemi\dfu\dfu_transport_serial.py", line 243, in
send_packet
 File "nordicsemi\dfu\dfu_transport_serial.py", line 282, in
get_ack_nr
nordicsemi.exceptions.NordicSemiException: No data received on
serial port. Not able to proceed.
This is probably caused by the bootloader version mismatched on your feather and
installed BSP. Due to the difference in flash layout (more details(https://adafru.it/
Dsy)) and Softdevice API (which is bundled with bootloader), sketch built with
selected bootloader can only upload to board having the same version. In short,
you need to upgrade/burn bootloader to match on your Feather, follow
aboveUpdate The Bootloader(https://adafru.it/Dsx)guide
It only has to be done once to update your Feather

On Linux I'm getting 'arm-none-eabi-g++: no such file or directory', even though 'arm-none-eabi-g++' exists in the path specified. What should I do?

This is probably caused by a conflict between 32-bit and 64-bit versions of the
compiler, libc and the IDE. The compiler uses 32-bit binaries, so you also need to
have a 32-bit version of libc installed on your system (details(https://adafru.it/vnE)).
Try running the following commands from the command line to resolve this:
1. sudo dpkg --add-architecture i386
2. sudo apt-get update
3. sudo apt-get install libc6:i386
©Adafruit Industries Page 34 of 186

Arduino Sensor Example

This Arduino sketch will loop and print values from all of the Feather Sense's onboard
sensors. This can be handy as a quick check or just use a general reference.
The code listing is below. With this sketch loaded and running, open the Serial
Monitor (make sure baud rate is set correctly) and you should see output like this:

Sensors

To keep things simple, this example uses the sensors in their default configuration.
Typically, there are many more settings available to change a sensors behavior. For
further details, see the libraries and examples specific to each sensor.
APDS9960 Proximity, Light, Color, and Gesture Sensor(https://adafru.it/z0d)
BMP280 Temperature and Barometric Pressure/Altitude(https://adafru.it/fIK)
LIS3MDL Magnetometer(https://adafru.it/IfI)
LSM6DS33 Accel/Gyro(https://adafru.it/Iqd)
SHT31 Humidity(https://adafru.it/k6d)
PDM Microphone(https://adafru.it/L-c)

Code

Here's the complete code listing for the sketch.
// SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries // // SPDX-License-Identifier: MIT
©Adafruit Industries Page 35 of 186
//
#include <Adafruit_APDS9960.h> #include <Adafruit_BMP280.h> #include <Adafruit_LIS3MDL.h> #include <Adafruit_LSM6DS33.h> #include <Adafruit_SHT31.h> #include <Adafruit_Sensor.h> #include <PDM.h>
Adafruit_APDS9960 apds9960; // proximity, light, color, gesture Adafruit_BMP280 bmp280; // temperautre, barometric pressure Adafruit_LIS3MDL lis3mdl; // magnetometer Adafruit_LSM6DS33 lsm6ds33; // accelerometer, gyroscope Adafruit_SHT31 sht30; // humidity
uint8_t proximity; uint16_t r, g, b, c; float temperature, pressure, altitude; float magnetic_x, magnetic_y, magnetic_z; float accel_x, accel_y, accel_z; float gyro_x, gyro_y, gyro_z; float humidity; int32_t mic;
extern PDMClass PDM;
short sampleBuffer[256]; // buffer to read samples into, each sample is 16-bits
volatile int samplesRead; // number of samples read
void setup(void) {
Serial.begin(115200); // while (!Serial) delay(10); Serial.println("Feather Sense Sensor Demo");
// initialize the sensors apds9960.begin(); apds9960.enableProximity(true); apds9960.enableColor(true); bmp280.begin(); lis3mdl.begin_I2C(); lsm6ds33.begin_I2C(); sht30.begin(); PDM.onReceive(onPDMdata); PDM.begin(1, 16000); }
void loop(void) {
proximity = apds9960.readProximity(); while (!apds9960.colorDataReady()) { delay(5); } apds9960.getColorData(&r, &g, &b, &c);
temperature = bmp280.readTemperature(); pressure = bmp280.readPressure(); altitude = bmp280.readAltitude(1013.25);
lis3mdl.read(); magnetic_x = lis3mdl.x; magnetic_y = lis3mdl.y; magnetic_z = lis3mdl.z;
sensors_event_t accel; sensors_event_t gyro; sensors_event_t temp; lsm6ds33.getEvent(&accel, &gyro, &temp); accel_x = accel.acceleration.x; accel_y = accel.acceleration.y; accel_z = accel.acceleration.z; gyro_x = gyro.gyro.x;
©Adafruit Industries Page 36 of 186
gyro_y = gyro.gyro.y; gyro_z = gyro.gyro.z;
humidity = sht30.readHumidity();
samplesRead = 0; mic = getPDMwave(4000);
Serial.println("\nFeather Sense Sensor Demo"); Serial.println("---------------------------------------------"); Serial.print("Proximity: "); Serial.println(apds9960.readProximity()); Serial.print("Red: "); Serial.print(r); Serial.print(" Green: "); Serial.print(g); Serial.print(" Blue :"); Serial.print(b); Serial.print(" Clear: "); Serial.println(c); Serial.print("Temperature: "); Serial.print(temperature); Serial.println(" C"); Serial.print("Barometric pressure: "); Serial.println(pressure); Serial.print("Altitude: "); Serial.print(altitude); Serial.println(" m"); Serial.print("Magnetic: "); Serial.print(magnetic_x); Serial.print(" "); Serial.print(magnetic_y); Serial.print(" "); Serial.print(magnetic_z); Serial.println(" uTesla"); Serial.print("Acceleration: "); Serial.print(accel_x); Serial.print(" "); Serial.print(accel_y); Serial.print(" "); Serial.print(accel_z); Serial.println(" m/s^2"); Serial.print("Gyro: "); Serial.print(gyro_x); Serial.print(" "); Serial.print(gyro_y); Serial.print(" "); Serial.print(gyro_z); Serial.println(" dps"); Serial.print("Humidity: "); Serial.print(humidity); Serial.println(" %"); Serial.print("Mic: "); Serial.println(mic); delay(300); }
/*****************************************************************/
int32_t getPDMwave(int32_t samples) {
short minwave = 30000; short maxwave = -30000;
while (samples > 0) { if (!samplesRead) { yield(); continue; } for (int i = 0; i < samplesRead; i++) { minwave = min(sampleBuffer[i], minwave);
©Adafruit Industries Page 37 of 186
maxwave = max(sampleBuffer[i], maxwave); samples--; } // clear the read count samplesRead = 0; } return maxwave - minwave; }
void onPDMdata() {
// query the number of bytes available int bytesAvailable = PDM.available();
// read into the sample buffer PDM.read(sampleBuffer, bytesAvailable);
// 16-bit, 2 bytes per sample samplesRead = bytesAvailable / 2; }

Arduino BLE Examples

There are numerous examples available for the Bluefruit nRF52/nRF52840 Feathers
in theExamples menu of the nRF52 BSP, and these are always up to date. You're first
stop looking for example code should be there:

Example Source Code

The latest example source code is always available and visible on Github, and the
public git repository should be considered the definitive source of example code for
this board.
Click here to browse the example
source code on Github
https://adafru.it/vaK
©Adafruit Industries Page 38 of 186

Documented Examples

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 Controllerutility 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
©Adafruit Industries Page 39 of 186
// 0x0822 is Adafruit // 0x0059 is Nordic
#define MANUFACTURER_ID 0x0059
// "nRF Connect" app can be used to detect beacon
uint8_t beaconUuid[16] =
{ 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9a, 0xab, 0xbc, 0xcd, 0xde, 0xef, 0xf0 };
// A valid Beacon packet consists of the following information: // UUID, Major, Minor, RSSI @ 1M
BLEBeacon beacon(beaconUuid, 0x0102, 0x0304, -54);
void setup()
{ Serial.begin(115200);
// Uncomment to blocking wait for Serial connection // while ( !Serial ) delay(10);
Serial.println("Bluefruit52 Beacon Example"); Serial.println("--------------------------\n");
Bluefruit.begin();
// 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
after n seconds
©Adafruit Industries Page 40 of 186
}
void loop()
{ // loop is already suspended, CPU will not run loop() at all }

Output

You can use the nRF Beacons application from Nordic Semiconductors to test this
sketch:
nRF Beacons for iOS(https://adafru.it/vaC)
nRF Beacons for Android(https://adafru.it/vaD)
Make sure that you set the UUID, Major and Minor values to match the sketch above,
and then run the sketch at the same time as the nRF Beacons application.
With the default setup you should see a Mona Lisa icon when the beacon is detected.
If you don't see this, double check the UUID, Major and Minor values to be sure they
match exactly.
©Adafruit Industries Page 41 of 186

BLE UART: Controller

This examples shows you you can use the BLEUart helper class and the Bluefruit LE
Connect applications to send based keypad and sensor data to your nRF52.

Setup

In order to use this sketch, you will need to open Bluefruit LE Connect on your mobile
device using our free iOS(https://adafru.it/f4H), Android(https://adafru.it/f4G) or OS X
(https://adafru.it/o9F) applications.
Load theController example sketch(https://adafru.it/vaN) in the Arduino IDE
Compile the sketch and flash it to your nRF52 based Feather
Once you are done uploading, open theSerial 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 theControllerapplication inside the app
Enable an appropriate control surface. TheColor Picker control surface is shown
below, for example (screen shot taken from the iOS application):
©Adafruit Industries Page 42 of 186
As you change the color (or as other data becomes available) you should receive the
data on the nRF52, and see it in the Serial Monitor output:
©Adafruit Industries Page 43 of 186

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>
// OTA DFU service
BLEDfu bledfu;
// Uart over BLE service
BLEUart bleuart;
// Function prototypes for packetparser.cpp
uint8_t readPacket (BLEUart *ble_uart, uint16_t timeout); float parsefloat (uint8_t *buffer); void printHex (const uint8_t * data, const uint32_t numBytes);
// Packet buffer
extern uint8_t packetbuffer[];
void setup(void)
{ Serial.begin(115200); while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println(F("Adafruit Bluefruit52 Controller App Example")); Serial.println(F("-------------------------------------------"));
Bluefruit.begin(); Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
// To be consistent OTA DFU should be added first if it exists bledfu.begin();
// Configure and start the BLE Uart service bleuart.begin();
// Set up and start advertising startAdv();
Serial.println(F("Please use Adafruit Bluefruit LE app to connect in Controller
mode"));
Serial.println(F("Then activate/use the sensors, color picker, game controller,
etc!"));
Serial.println(); }
void startAdv(void)
{ // Advertising packet
©Adafruit Industries Page 44 of 186
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower();
// 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 arrive uint8_t len = readPacket(&bleuart, 500); if (len == 0) return;
// Got a packet! // printHex(packetbuffer, len);
// Color if (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); }
// Buttons if (packetbuffer[1] == 'B') { uint8_t buttnum = packetbuffer[2] - '0'; boolean pressed = packetbuffer[3] - '0'; Serial.print ("Button "); Serial.print(buttnum); if (pressed) { Serial.println(" pressed"); } else { Serial.println(" released"); } }
// GPS Location if (packetbuffer[1] == 'L') { float lat, lon, alt;
©Adafruit Industries Page 45 of 186
lat = parsefloat(packetbuffer+2); lon = parsefloat(packetbuffer+6); alt = parsefloat(packetbuffer+10); Serial.print("GPS Location\t"); Serial.print("Lat: "); Serial.print(lat, 4); // 4 digits of precision! Serial.print('\t'); Serial.print("Lon: "); Serial.print(lon, 4); // 4 digits of precision! Serial.print('\t'); Serial.print(alt, 4); Serial.println(" meters"); }
// Accelerometer if (packetbuffer[1] == 'A') { float x, y, z; x = parsefloat(packetbuffer+2); y = parsefloat(packetbuffer+6); z = parsefloat(packetbuffer+10); Serial.print("Accel\t"); Serial.print(x); Serial.print('\t'); Serial.print(y); Serial.print('\t'); Serial.print(z); Serial.println(); }
// Magnetometer if (packetbuffer[1] == 'M') { float x, y, z; x = parsefloat(packetbuffer+2); y = parsefloat(packetbuffer+6); z = parsefloat(packetbuffer+10); Serial.print("Mag\t"); Serial.print(x); Serial.print('\t'); Serial.print(y); Serial.print('\t'); Serial.print(z); Serial.println(); }
// Gyroscope if (packetbuffer[1] == 'G') { float x, y, z; x = parsefloat(packetbuffer+2); y = parsefloat(packetbuffer+6); z = parsefloat(packetbuffer+10); Serial.print("Gyro\t"); Serial.print(x); Serial.print('\t'); Serial.print(y); Serial.print('\t'); Serial.print(z); Serial.println(); }
// Quaternions if (packetbuffer[1] == 'Q') { float x, y, z, w; x = parsefloat(packetbuffer+2); y = parsefloat(packetbuffer+6); z = parsefloat(packetbuffer+10); w = parsefloat(packetbuffer+14); Serial.print("Quat\t"); Serial.print(x); Serial.print('\t'); Serial.print(y); Serial.print('\t'); Serial.print(z); Serial.print('\t'); Serial.print(w); Serial.println(); } }
You will also need the following helper class in a file calledpacketParser.cpp:
#include <string.h> #include <Arduino.h>
©Adafruit Industries Page 46 of 186
#include <bluefruit.h>
#define PACKET_ACC_LEN (15) #define PACKET_GYRO_LEN (15) #define PACKET_MAG_LEN (15) #define PACKET_QUAT_LEN (19) #define PACKET_BUTTON_LEN (5) #define PACKET_COLOR_LEN (6) #define PACKET_LOCATION_LEN (15)
// 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 */ /**************************************************************************/
void printHex(const uint8_t * data, const uint32_t numBytes)
{ uint32_t szPos; for (szPos=0; szPos < numBytes; szPos++) { Serial.print(F("0x")); // Append leading 0 for small values if (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 appropriate if ((numBytes > 1) && (szPos != numBytes - 1)) { Serial.print(F(" ")); } } Serial.println(); }
/**************************************************************************/ /*! @brief Waits for incoming data and parses it */ /**************************************************************************/
uint8_t readPacket(BLEUart *ble_uart, uint16_t timeout)
{
©Adafruit Industries Page 47 of 186
uint16_t origtimeout = timeout, replyidx = 0;
memset(packetbuffer, 0, READ_BUFSIZE);
while (timeout--) { if (replyidx >= 20) break; if ((packetbuffer[1] == 'A') && (replyidx == PACKET_ACC_LEN)) break; if ((packetbuffer[1] == 'G') && (replyidx == PACKET_GYRO_LEN)) break; if ((packetbuffer[1] == 'M') && (replyidx == PACKET_MAG_LEN)) break; if ((packetbuffer[1] == 'Q') && (replyidx == PACKET_QUAT_LEN)) break; if ((packetbuffer[1] == 'B') && (replyidx == PACKET_BUTTON_LEN)) break; if ((packetbuffer[1] == 'C') && (replyidx == PACKET_COLOR_LEN)) break; if ((packetbuffer[1] == 'L') && (replyidx == PACKET_LOCATION_LEN)) break;
while (ble_uart->available()) { char c = ble_uart->read(); if (c == '!') { replyidx = 0; } packetbuffer[replyidx] = c; replyidx++; timeout = origtimeout; }
if (timeout == 0) break; delay(1); }
packetbuffer[replyidx] = 0; // null term
if (!replyidx) // no data or timeout return 0; if (packetbuffer[0] != '!') // doesn't start with '!' packet beginning return 0;
// check checksum! uint8_t xsum = 0; uint8_t checksum = packetbuffer[replyidx-1];
for (uint8_t i=0; i<replyidx-1; i++) { xsum += packetbuffer[i]; } xsum = ~xsum;
// Throw an error message if the checksum's don't match if (xsum != checksum) { Serial.print("Checksum mismatch in packet : "); printHex(packetbuffer, replyidx+1); return 0; }
// checksum passed! return replyidx; }
©Adafruit Industries Page 48 of 186

Custom: HRM

The BLEService and BLECharacteristic classes can be used to implement any custom
or officially adopted BLE service of characteristic using a set of basic properties and
callback handlers.
The example below shows how to use these classes to implement the Heart Rate
Monitor(https://adafru.it/vaO) service, as defined by the Bluetooth SIG.

HRM Service Definition

UUID: 0x180D(https://adafru.it/vaO)
Characteristic Name
Heart Rate Measurement
Body Sensor Location
Heart Rate Control Point
Only the first characteristic is mandatory, but we will also implement the optionalBody
Sensor Location characteristic. Heart Rate Control Point won't be used in this example
to keep things simple.
UUID
0x2A37
0x2A38
0x2A39
Requirement
Mandatory
Optional
Conditional
Properties
Notify
Read
Write

Implementing the HRM Service and Characteristics

The core service and the first two characteristics can be implemented with the
following code:
First, define the BLEService and BLECharacteristic variables that will be used in your
project:
/* 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);
©Adafruit Industries Page 49 of 186
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);
©Adafruit Industries Page 50 of 186
bslc.setFixedLen(1); bslc.begin(); bslc.write8(2); // Set the characteristic to 'Wrist' (2) }

Service + Characteristic Setup Code Analysis

1. The first thing to do is to call.begin() on the BLEService (hrms above). Since the
UUID is set in the object declaration at the top of the sketch, there is normally nothing
else to do with the BLEService instance.
You MUST call .begin() on the BLEService before adding any BLECharacteristics.
Any BLECharacteristic will automatically be added to the last BLEService that
was `begin()'ed!
2. Next, you can configure theHeart Rate Measurement characteristic (hrmc above).
The values that you set for this will depend on the characteristic definition, but for
convenience sake we've documented the key information in the comments in the
code above.
' hrmc.setProperties(CHR_PROPS_NOTIFY); ' - This sets the PROPERTIES
value for the characteristic, which determines how the characteristic can be
accessed. In this case, the Bluetooth SIG has defined the characteristic
asNotify, which means thatthe peripheral will receive a request ('notification')
from the Central when the Central wants to receive data using this
characteristic.
` hrmc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); ` - This sets
the security for the characteristic, and should normally be set to the values used
in this example.
` hrmc.setFixedLen(2); ` - This tells the Bluetooth stack how many bytes the
characteristic contains (normally a value between 1 and 20). In this case, we will
use a fixed size of two bytes, so we call.setFixedLen. If the characteristic has a
variable length, you would need to set the max size via .setMaxLen.
' hrmc.setCccdWriteCallback(cccd_callback); ' - This optional code sets
the callback that will be fired when the CCCD record is updated by the central.
This is relevant because the characteristic is setup with the NOTIFY property.
When the Central sets to 'Notify' bit, it will write to the CCCD record, and you
can capture this write even in the CCCD callback and turn the sensor on, for
example, allowing you to save power by only turning the sensor on (and back
off) when it is or isn't actually being used. For the implementation of the CCCD
callback handler, see the full sample code at the bottom of this page.
©Adafruit Industries Page 51 of 186
' hrmc.begin(); ' Once all of the properties have been set, you must call.begi
n() which will add the characteristic definition to the last BLEService that was
'.begin()ed'.
3. Optionally set an initial value for the characteristic(s), such as the following code
that populates 'hrmc' with a correct values, indicating that we are providing 8-bit heart
rate monitor values, that the Body Sensor Location characteristic is present, and
setting the first heart rate value to 0x04:
Note that we use .notify() in the example above instead of .write(), since this
characteristic is setup with the NOTIFY property which needs to be handled in a
slightly different manner than other characteristics.
// Set the characteristic to use 8-bit values, with the sensor connected and detected uint8_t hrmdata[2] = { 0b00000110, 0x40 };
// Use .notify instead of .write! hrmc.notify(hrmdata, 2);
The CCCD callback handler has the following signature:
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 == 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 Industries Page 52 of 186
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>
/* 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;
void setup()
{ Serial.begin(115200); while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println("Bluefruit52 HRM Example"); Serial.println("-----------------------\n");
// 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"); }
void startAdv(void)
{
©Adafruit Industries Page 53 of 186
// 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);
©Adafruit Industries Page 54 of 186
// 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(2); // Set the characteristic to 'Wrist' (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"); } } }
void loop()
©Adafruit Industries Page 55 of 186
{ digitalToggle(LED_RED);
if ( Bluefruit.connected() ) { uint8_t hrmdata[2] = { 0b00000110, bps++ }; // 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(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
deviceusing 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 theSerial 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 thePin I/Oapplication inside the app
For more information using Pin I/O module, you could check out this tutorial
herehttps://learn.adafruit.com/bluefruit-le-connect-for-ios/pin-i-o
©Adafruit Industries Page 56 of 186

Complete Code

The latest version of this code is always available on Github(https://adafru.it/vaN),
and in theExamples 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.
https://github.com/firmata/arduino#firmata-client-libraries
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.
Last updated October 16th, 2016 */
©Adafruit Industries Page 57 of 186
// Adafruit nRF52 Boards require Firmata version is at least 2.5.7
#include <bluefruit.h> #include <Servo.h> #include <Wire.h> #include <Firmata.h>
#define I2C_WRITE B00000000 #define I2C_READ B00001000 #define I2C_READ_CONTINUOUSLY B00010000 #define I2C_STOP_READING B00011000 #define I2C_READ_WRITE_MODE_MASK B00011000 #define I2C_10BIT_ADDRESS_MODE_MASK B00100000 #define I2C_END_TX_MASK B01000000 #define I2C_STOP_TX 1 #define I2C_RESTART_TX 0 #define I2C_MAX_QUERIES 8 #define I2C_REGISTER_NOT_SPECIFIED -1
// the minimum interval for sampling analog input
#define MINIMUM_SAMPLING_INTERVAL 1
// Adafruit
uint8_t ANALOG_TO_PIN(uint8_t n)
{ switch (n) { case 0 : return PIN_A0; case 1 : return PIN_A1; case 2 : return PIN_A2; case 3 : return PIN_A3; case 4 : return PIN_A4; case 5 : return PIN_A5; case 6 : return PIN_A6; case 7 : return PIN_A7; }
return 127; }
/*============================================================================== * 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)
/* i2c data */
struct i2c_device_info {
©Adafruit Industries Page 58 of 186
byte addr; int reg; byte bytes; byte stopTX; };
/* for i2c read continuous more */
i2c_device_info query[I2C_MAX_QUERIES];
byte i2cRxData[64]; boolean isI2CEnabled = false;
signed char queryIndex = -1;
// default delay time between i2c read request and Wire.requestFrom()
unsigned int i2cReadDelayTime = 0;
Servo servos[MAX_SERVOS]; byte servoPinMap[TOTAL_PINS]; byte detachedServos[MAX_SERVOS]; byte detachedServoCount = 0; byte servoCount = 0;
boolean isResetting = false;
// Forward declare a few functions to avoid compiler errors with older versions // of the Arduino IDE.
void setPinModeCallback(byte, int); void reportAnalogCallback(byte analogPin, int value); void sysexCallback(byte, byte, byte*);
/* utility functions */
void wireWrite(byte data)
{
#if ARDUINO >= 100
Wire.write((byte)data);
#else
Wire.send(data);
#endif
}
byte wireRead(void) {
#if ARDUINO >= 100
return Wire.read();
#else
return Wire.receive();
#endif
}
/*============================================================================== * FUNCTIONS *============================================================================*/
void attachServo(byte pin, int minPulse, int maxPulse)
{ if (servoCount < MAX_SERVOS) { // reuse indexes of detached servos until all have been reallocated if (detachedServoCount > 0) { servoPinMap[pin] = detachedServos[detachedServoCount - 1]; if (detachedServoCount > 0) detachedServoCount--; } else { servoPinMap[pin] = servoCount; servoCount++; } if (minPulse > 0 && maxPulse > 0) { servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse); } else { servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin)); } } else {
©Adafruit Industries Page 59 of 186
Firmata.sendString("Max servos attached"); } }
void detachServo(byte pin)
{ 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 pins for (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 0 if (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 slave if (numBytes < Wire.available()) { Firmata.sendString("I2C: Too many bytes received"); } else if (numBytes > Wire.available()) {
©Adafruit Industries Page 60 of 186
Firmata.sendString("I2C: Too few bytes received"); }
i2cRxData[0] = address; i2cRxData[1] = theRegister;
for (int i = 0; i < numBytes && Wire.available(); i++) { i2cRxData[2 + i] = wireRead(); }
// send slave address, register and received bytes Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData); }
void outputPort(byte portNumber, byte portValue, byte forceSend)
{ // pins not configured as INPUT are cleared to zeros portValue = portValue & portConfigInputs[portNumber]; // only send if the value is different than previously sent if (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
©Adafruit Industries Page 61 of 186
* 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);
©Adafruit Industries Page 62 of 186
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:
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.handlePinMode(pin, PIN_MODE_SERIAL);
#endif
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; }
©Adafruit Industries Page 63 of 186
} }
void digitalWriteCallback(byte port, int value)
{ byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;
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 modes if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) { pinValue = ((byte)value & mask) ? 1 : 0; if (Firmata.getPinMode(pin) == OUTPUT) { pinWriteMask |= mask; } else if (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
earlier
pinWriteMask |= mask;
#endif
} Firmata.setPinState(pin, pinValue); } } mask = mask << 1; } writePort(port, (byte)value, pinWriteMask); } }
// ----------------------------------------------------------------------------­/* 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 pins if (!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;
©Adafruit Industries Page 64 of 186
// 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 }
/*============================================================================== * SYSEX-BASED commands *============================================================================*/
void sysexCallback(byte command, byte argc, byte *argv)
{ byte mode; byte stopTX; byte slaveAddress; byte data; int slaveRegister; unsigned int delayTime;
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 tx if (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;
©Adafruit Industries Page 65 of 186
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 device if (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 space for (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; }
if (!isI2CEnabled) { enableI2CPins(); }
break; case SERVO_CONFIG: if (argc > 4) {
©Adafruit Industries Page 66 of 186
// 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:
©Adafruit Industries Page 67 of 186
if (argc > 0) { byte pin = argv[0]; Firmata.write(START_SYSEX); Firmata.write(PIN_STATE_RESPONSE); Firmata.write(pin); if (pin < TOTAL_PINS) { Firmata.write(Firmata.getPinMode(pin)); Firmata.write((byte)Firmata.getPinState(pin) & 0x7F); if (Firmata.getPinState(pin) & 0xFF80) Firmata.write((byte) (Firmata.getPinState(pin) >> 7) & 0x7F); if (Firmata.getPinState(pin) & 0xC000) Firmata.write((byte) (Firmata.getPinState(pin) >> 14) & 0x7F); } Firmata.write(END_SYSEX); } break; case ANALOG_MAPPING_QUERY: Firmata.write(START_SYSEX); Firmata.write(ANALOG_MAPPING_RESPONSE); for (byte pin = 0; pin < TOTAL_PINS; pin++) { Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127); } Firmata.write(END_SYSEX); break;
case SERIAL_MESSAGE:
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.handleSysex(command, argc, argv);
#endif
break; } }
/*============================================================================== * SETUP() *============================================================================*/
void systemResetCallback()
{ isResetting = true;
// 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 output if (IS_PIN_ANALOG(i)) { // turns off pullup, configures everything setPinModeCallback(i, PIN_MODE_ANALOG); } else if (IS_PIN_DIGITAL(i)) { // sets the output to 0, configures portConfigInputs setPinModeCallback(i, OUTPUT); }
©Adafruit Industries Page 68 of 186
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
smoothly
// Enable buffering TXD bleuart.begin(); bleuart.bufferTXD(true);
Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);
Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback); Firmata.attach(REPORT_ANALOG, reportAnalogCallback); Firmata.attach(REPORT_DIGITAL, reportDigitalCallback); Firmata.attach(SET_PIN_MODE, setPinModeCallback); Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback); Firmata.attach(START_SYSEX, sysexCallback); Firmata.attach(SYSTEM_RESET, systemResetCallback);
// use bleuart as transportation layer Firmata.begin(bleuart);
// to use a port other than Serial, such as Serial1 on an Arduino Leonardo or
Mega,
// Call begin(baud) on the alternate serial port and pass it to Firmata to begin
like this:
// Serial1.begin(57600);
©Adafruit Industries Page 69 of 186
// Firmata.begin(Serial1); // However do not do this if you are using SERIAL_MESSAGE
//Firmata.begin(57600); //while (!Serial) { // ; // wait for serial port to connect. Needed for ATmega32u4-based boards and
Arduino 101
//}
systemResetCallback(); // reset to default config
// Set up and start advertising startAdv(); }
void startAdv(void)
{ // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower();
// 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
}
/*============================================================================== * LOOP() *============================================================================*/
void loop()
{ // Skip if not connected and bleuart notification is not enabled if ( !(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 */
©Adafruit Industries Page 70 of 186
for (pin = 0; pin < TOTAL_PINS; pin++) { if (IS_PIN_ANALOG(pin) && Firmata.getPinMode(pin) == PIN_MODE_ANALOG) { analogPin = PIN_TO_ANALOG(pin); if (analogInputsToReport & (1 << analogPin)) { Firmata.sendAnalog(analogPin, analogRead( ANALOG_TO_PIN(analogPin) )); } } } // report i2c data for all device with read continuous mode enabled if (queryIndex > -1) { for (byte i = 0; i < queryIndex + 1; i++) { readAndReportData(query[i].addr, query[i].reg, query[i].bytes, query[i].stopTX); } } }
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.update();
#endif
// flush TXD since we use bufferTXD() bleuart.flushTXD(); }

Central BLEUART

This example show you how to use Feather nRF52/nRF52840 as a Central to talk to
other Bluefruit (nRF52 or nRF51) peripherals exposing the bleuart (AKA 'NUS') service.

Client Services

Since the Central role accesses the GATT server on the peripheral, we first need to
declare a client bleuart instance using the BLEClientUarthelper class. We can also
conveniently read Device Information if BLEClientDis is also used.
BLEClientDis clientDis; BLEClientUart clientUart;
Before we can configure client services, Bluefruit.begin() must be called with at
least 1 for the number of concurrent connections supported in central mode. Since we
won't be running the nRF52 as a peripheral in this instance, we will set the peripheral
count to 0:
// Initialize Bluefruit with maximum connections as Peripheral = 0, Central = 1 Bluefruit.begin(0, 1);
Afterward this, the client service(s) must be initialized by calling their begin() functi
on, and you cansetup any callbacks that you wish to use from the helper class:
©Adafruit Industries Page 71 of 186
// Configure DIS client clientDis.begin();
// 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
©Adafruit Industries Page 72 of 186
Bluefruit.Central.connect(report); } }

Central Role

You normally need to setup the Central mode device's connect callback, whichfires
when a connection is established/disconnected with a peripheral device. Alternatively
you could poll the connection status with connected(), but callbacks help to simplify
the code significantly:
// Callbacks for Central Bluefruit.Central.setConnectCallback(connect_callback); Bluefruit.Central.setDisconnectCallback(disconnect_callback);
In the connect callback, we will try to discoverthe bleuart service by browsing the
GATT table of the peripheral. This will help to determine the handle values for
characteristics (e.g TXD, RXD, etc.). This is all done by BLEClientUart's .discover() .
Once the service is found, enable the TXD characteristic's CCCD to allow the
peripheral to send data, and we are ready to send data back and forthbetween the
devices:
void connect_callback(uint16_t conn_handle) { Serial.println("Connected");
Serial.print("Dicovering DIS ... "); 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); }
Serial.println(); }
Serial.print("Discovering BLE Uart Service ... ");
if ( clientUart.discover(conn_handle) ) {
©Adafruit Industries Page 73 of 186
Serial.println("Found it");
Serial.println("Enable TXD's notify"); clientUart.enableTXD();
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. */
#include <bluefruit.h>
BLEClientBas clientBas; // battery client BLEClientDis clientDis; // device information client BLEClientUart clientUart; // bleuart client
void setup()
{ Serial.begin(115200);
// 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
connections
Bluefruit.begin(0, 1);
Bluefruit.setName("Bluefruit52 Central");
// Configure Battyer client clientBas.begin();
// Configure DIS client clientDis.begin();
©Adafruit Industries Page 74 of 186
// 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 */
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 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); }
©Adafruit Industries Page 75 of 186
// 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(); }else { Serial.println("Found NONE"); }
Serial.print("Dicovering Battery ... "); if ( clientBas.discover(conn_handle) ) { Serial.println("Found it"); Serial.print("Battery level: "); Serial.print(clientBas.read()); Serial.println("%"); }else { Serial.println("Found NONE"); }
Serial.print("Discovering BLE Uart Service ... "); if ( clientUart.discover(conn_handle) ) { Serial.println("Found it");
Serial.println("Enable TXD's notify"); clientUart.enableTXD();
Serial.println("Ready to receive from peripheral"); }else { Serial.println("Found NONE");
// disconnect since we couldn't find bleuart service Bluefruit.disconnect(conn_handle); } }
/** * Callback invoked when a connection is dropped * @param conn_handle * @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); }
/** * 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() );
©Adafruit Industries Page 76 of 186
}
Serial.println(); }
void loop()
{ if ( Bluefruit.Central.connected() ) { // Not discovered yet if ( clientUart.discovered() ) { // Discovered means in working state // Get Serial input and send to Peripheral if ( Serial.available() ) { delay(2); // delay a bit for all characters to arrive
char str[20+1] = { 0 }; Serial.readBytes(str, 20);
clientUart.print( str ); } } } }

Dual Roles BLEUART

If you are not familiar with Central Role, it is advised to look at the "Central
BLEUART" example first then continue with this afterwards.
This example demonstrates how you can use a Feather nRF52/nRF52840 to connect
to two other Bluefruit or BLE devices using the bleuart (AKA 'NUS') service
concurrently, with the device running at botha peripheral and a central at the same
time.
This dual role example acts as a BLE bridge that sits between a central and a
peripheral forwarding bleuart messages back and forth, as shown in the image below:
©Adafruit Industries Page 77 of 186

Server & Client Service Setup

Since the Bluefruit device will act as both a central and a peripheral, we will need to
declare both server and client instance of the bleuart helper class:
// Peripheral uart service BLEUart bleuart;
// Central uart client BLEClientUart clientUart;
Before we can configure client services, Bluefruit.begin() must be called with at
least 1 for the number of concurrent connection for both peripheral and central mode:
// Initialize Bluefruit with max concurrent connections as Peripheral = 1, Central = 1 Bluefruit.begin(1, 1);
After this, client services must be initialized by calling their begin() function,
followed by any callbacks that you wish to wire up as well:
// 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);
We are then ready to forward data from central to peripheral and vice versa using
callbacks:
void cent_bleuart_rx_callback(BLEClientUart& cent_uart) { char str[20+1] = { 0 }; cent_uart.read(str, 20);
Serial.print("[Cent] RX: "); Serial.println(str);
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"); } }
void prph_bleuart_rx_callback(void) {
©Adafruit Industries Page 78 of 186
// 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
©Adafruit Industries Page 79 of 186
* - 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
// Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower();
// 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 */
#include <bluefruit.h>
©Adafruit Industries Page 80 of 186
// OTA DFU service
BLEDfu bledfu;
// Peripheral uart service
BLEUart bleuart;
// Central uart client
BLEClientUart clientUart;
void setup()
{ 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
seconds
// Set up and start advertising startAdv(); }
void startAdv(void)
{ // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower();
©Adafruit Industries Page 81 of 186
// 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 }
/*------------------------------------------------------------------*/ /* Peripheral *------------------------------------------------------------------*/
void prph_connect_callback(uint16_t conn_handle)
{ // Get the reference to current connection BLEConnection* connection = Bluefruit.Connection(conn_handle);
char peer_name[32] = { 0 }; connection->getPeerName(peer_name, sizeof(peer_name));
Serial.print("[Prph] Connected to "); Serial.println(peer_name); }
void prph_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{ (void) conn_handle; (void) reason;
Serial.println(); Serial.println("[Prph] Disconnected"); }
void prph_bleuart_rx_callback(uint16_t conn_handle)
{ (void) conn_handle;
// 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");
©Adafruit Industries Page 82 of 186
} }
/*------------------------------------------------------------------*/ /* Central *------------------------------------------------------------------*/
void scan_callback(ble_gap_evt_adv_report_t* report)
{ // 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);
char peer_name[32] = { 0 }; connection->getPeerName(peer_name, sizeof(peer_name));
Serial.print("[Cent] Connected to "); Serial.println(peer_name);;
if ( clientUart.discover(conn_handle) ) { // Enable TXD's notify clientUart.enableTXD(); }else { // disconnect since we couldn't find bleuart service Bluefruit.disconnect(conn_handle); } }
void cent_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{ (void) conn_handle; (void) reason;
Serial.println("[Cent] Disconnected"); }
/** * Callback invoked when uart received data * @param cent_uart Reference object to the service where the data * arrived. In this example it is clientUart */
void cent_bleuart_rx_callback(BLEClientUart& cent_uart)
{ char str[20+1] = { 0 }; cent_uart.read(str, 20);
Serial.print("[Cent] RX: "); Serial.println(str);
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"); } }
©Adafruit Industries Page 83 of 186

Custom: Central HRM

The BLEClientService and BLEClientCharacteristic classes can be used to implement
any custom or officially adopted BLE service of characteristic on the client side (most
often is Central) using a set of basic properties and callback handlers.
The example below shows how to use these classes to implement the Heart Rate
Monitor(https://adafru.it/vaO) service, as defined by the Bluetooth SIG. To run this
example, you will need an extra nRF52 runningperipheral HRM sketch(https://
adafru.it/Cnf)

HRM Service Definition

UUID: 0x180D(https://adafru.it/vaO)
Only the first characteristic is mandatory, but we will also implement the optionalBody
Sensor Location characteristic. Heart Rate Control Point won't be used in this example
to keep things simple.

Implementing the HRM Service and Characteristics

The core service and the first two characteristics can be implemented with the
following code:
First, define the BLEService and BLECharacteristic variables that will be used in your
project:
/* HRM Service Definitions * Heart Rate Monitor Service: 0x180D * Heart Rate Measurement Char: 0x2A37 (Mandatory) * Body Sensor Location Char: 0x2A38 (Optional) */
BLEClientService hrms(UUID16_SVC_HEART_RATE); BLEClientCharacteristic hrmc(UUID16_CHR_HEART_RATE_MEASUREMENT); BLEClientCharacteristic bslc(UUID16_CHR_BODY_SENSOR_LOCATION);
Then you need to initialize those variables by calling their begin().
// Initialize HRM client hrms.begin();
©Adafruit Industries Page 84 of 186
// Initialize client characteristics of HRM. // Note: Client Char will be added to the last service that is begin()ed. bslc.begin();
// set up callback for receiving measurement hrmc.setNotifyCallback(hrm_notify_callback); hrmc.begin();

Client Service + Characteristic Code Analysis

1. The first thing to do is to call.begin() on the BLEClientService (hrms above). Since
the UUID is set in the object declaration at the top of the sketch, there is normally
nothing else to do with the BLEClientService instance.
You MUST call .begin() on the BLEClientService before adding any
BLEClientCharacteristics. Any BLEClientCharacteristic will automatically be
added to the last BLEClientService that was `begin()'ed!
2. SinceHeart Rate Measurement characteristic (clientMeasurementabove) isnotifia
ble.You need to set up callback for it
' hrmc.setNotifyCallback(hrm_notify_callback); ' This sets the callback
that will be fired when we receive a Notify message from peripheral. This is
needed to handle notifiable characteristic since callback allow us to response to
the message in timely manner. For this example is just simply printing out value
to Serial.
' hrmc.begin(); ' Once all of the properties have been set, you must call.begi
n() which will add the characteristic definition to the last BLEClientService that
was '.begin()ed'.
Note for characteristic that does not support notify e.g body sensor location , we
can simply use .read() to retrieve its value.
3. Next, we can start to scan and connect to peripheral that advertises HRM service.
Once connected, we need to go through peripheral GATT table to find out the Gatt
handle for our interest. In this example they are handle for hrms,hrmc andbslc.This
looking up process for interested service/characteristic is called Discovery.
Note: Gatt handle (or just handle) is required to perform any operations at all such as
read, write, enable notify. It is required that a client characteristic must be discovered
before we could doing anything with it.
©Adafruit Industries Page 85 of 186
The service should be discovered before we could discover its characteristic. This
can be done by calling hrms.discover(conn_handle) . Where conn_handle is the
connection ID i.e peripheral that we want to discover since it is possible for Bluefruit
nRF52 to connect to multiple peripherals concurrently. If the service is found, the
function will return true, otherwise false.
// Connect Callback Part 1 void connect_callback(uint16_t conn_handle) { Serial.println("Connected"); Serial.print("Discovering HRM Service ... ");
// If HRM is not found, disconnect and return if ( !hrms.discover(conn_handle) ) { Serial.println("Found NONE");
// disconect since we couldn't find HRM service Bluefruit.Central.disconnect(conn_handle);
return; }
// Once HRM service is found, we continue to discover its characteristic Serial.println("Found it");
.............
}
4. Afterwards, we continue to discover all the interested characteristics within the
service by calling .discover() . The function return true if characteristics is found,
and false otherwise. You could also check with .discovered() function. A service
could contain more characteristics but we don't need to discover them all, only those
that we want to interact with.
Advanced: Alternatively, you could discover all the interested characteristics of a
service within a function call by using Bluefruit.Discovery.discoverCharacteri
stic() (not used in the example). The API can take up to 5 characteristics, if you
need more, the variant with passing array of characteristics is also available. The
function will return the number of characteristic it found.
Note: when a characteristic is discovered by above API, all necessarily meta data
such as handles, properties( read,write, notify etc ...), cccd handle will be updated
automatically. You can then useBLECLientCharacteristic(https://adafru.it/Cng)API
such as read(), write(), enableNotify() on it provided that its properties support such as
operation.
// Connect Callback Part 2 void connect_callback(uint16_t conn_handle) { Serial.print("Discovering Measurement characteristic ... ");
©Adafruit Industries Page 86 of 186
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();
Serial.print("Body Location Sensor: "); Serial.println(body_str[loc_value]); }else { Serial.println("Found NONE"); }
...............
}
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
©Adafruit Industries Page 87 of 186
* @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.
Serial.print("HRM Measurement: ");
if ( data[0] & bit(0) ) { uint16_t value; memcpy(&value, data+1, 2);
Serial.println(value); } else { Serial.println(data[1]); } }

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 *********************************************************************/
#include <bluefruit.h>
/* 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;
void setup()
{ Serial.begin(115200); while ( !Serial ) delay(10); // for nrf52840 with native usb
©Adafruit Industries Page 88 of 186
Serial.println("Bluefruit52 HRM Example"); Serial.println("-----------------------\n");
// 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"); }
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:
©Adafruit Industries Page 89 of 186
// 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(2); // Set the characteristic to 'Wrist' (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 ");
©Adafruit Industries Page 90 of 186
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"); } } }
void loop()
{ digitalToggle(LED_RED);
if ( Bluefruit.connected() ) { uint8_t hrmdata[2] = { 0b00000110, bps++ }; // 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(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
to work with BLE in your projects.
©Adafruit Industries Page 91 of 186
The key classes are listed below, and examined in more detail elsewhere in this
learning guide:
AdafruitBluefruitis the main entry point to the Adafruit Bluefruit nRF52 API. This
class exposes a number of essential functions and classes, such as advertising,
the list of GATT services and characteristics defined on your device, and
connection status.
BLEServiceis a wrapper class for BLE GATT service records, and can be used to
define custom service definitions, or acts as the base class for any service
helper classes.
BLECharacteristic is a wrapper class for a BLE GATT characteristic record, and
can be used to define custom characteristics, or acts as the base class for any
characteristic helper classes.
BLEDis is a helper class for the DIS or 'Device Information Service'.
BLEUart is a helper class for the NUS or 'Nordic UART Service'.
BLEBeacon is a helper class to configure your nRF52 as a beacon using the
advertising packet to send out properly formatted beacon data.
BLEMidi is a helper class to work with MIDI data over BLE.
BLEHidAdafruit is a helper class to emulate an HID mouse or keyboard over
BLE.
Details on each of these helper classes are found further in this learning guide.

AdafruitBluefruit

Adafruit's nRF52 BSP codebase is still undergoing active development based on
customer feedback and testing. As such, the class documentation here may be
incomplete, and you should consult the Github repo for the latest code and API
developments: https://goo.gl/LdEx62
This base class is the main entry point to the Adafruit Bluefruit nRF52 API, and
exposes most of the helper classes and functions that you use to configure your
device.
API
AdafruitBluefruit has the following public API:
©Adafruit Industries Page 92 of 186
// Constructor AdafruitBluefruit(void);
/*------------------------------------------------------------------*/ /* Lower Level Classes (Bluefruit.Advertising.*, etc.) *------------------------------------------------------------------*/ BLEGap Gap; BLEGatt Gatt;
BLEAdvertising Advertising; BLEAdvertisingData ScanResponse; BLEScanner Scanner; BLECentral Central; BLEDiscovery Discovery;
/*------------------------------------------------------------------*/ /* SoftDevice Configure Functions, must call before begin(). * These function affect the SRAM consumed by SoftDevice. *------------------------------------------------------------------*/ void configServiceChanged (bool changed); void configUuid128Count (uint8_t uuid128_max); void configAttrTableSize (uint32_t attr_table_size);
// Config Bandwidth for connections void configPrphConn (uint16_t mtu_max, uint8_t event_len, uint8_t hvn_qsize, uint8_t wrcmd_qsize); void configCentralConn (uint16_t mtu_max, uint8_t event_len, uint8_t hvn_qsize, uint8_t wrcmd_qsize);
// Convenient function to config connection void configPrphBandwidth (uint8_t bw); void configCentralBandwidth(uint8_t bw);
err_t begin(uint8_t prph_count = 1, uint8_t central_count = 0);
/*------------------------------------------------------------------*/ /* General Functions *------------------------------------------------------------------*/ void setName (const char* str); uint8_t getName (char* name, uint16_t bufsize);
bool setTxPower (int8_t power); int8_t getTxPower (void);
bool setApperance (uint16_t appear); uint16_t getApperance (void);
void autoConnLed (bool enabled); void setConnLedInterval (uint32_t ms);
/*------------------------------------------------------------------*/ /* GAP, Connections and Bonding *------------------------------------------------------------------*/ bool connected (void); bool disconnect (void);
bool setConnInterval (uint16_t min, uint16_t max); bool setConnIntervalMS (uint16_t min_ms, uint16_t max_ms);
uint16_t connHandle (void); bool connPaired (void); uint16_t connInterval (void);
bool requestPairing (void); void clearBonds (void);
ble_gap_addr_t getPeerAddr (void); uint8_t getPeerAddr (uint8_t addr[6]);
©Adafruit Industries Page 93 of 186
void printInfo(void);
/*------------------------------------------------------------------*/ /* Callbacks *------------------------------------------------------------------*/ void setConnectCallback ( BLEGap::connect_callback_t fp); void setDisconnectCallback( BLEGap::disconnect_callback_t fp);
These functions are generally available via 'Bluefruit.*'. For example, to check the
connection status in your sketch you could run ' if (Bluefruit.connected()) {
... } '.

Examples

For examples of how to work with the parentBluefruit class, see theExamples section
later in this guide. It's better to examine this class in the context of a real world use
case.
You can also browse the latest example code online via Github:
Browse the latest example code on
Github
https://adafru.it/vaK

BLEGap

This page is a work in progress as the API is changing as we migrate to S132v5
(nRF52832) and S140 (nRF52840) and add better Central mode support.
This GAP API for Bluefruit is accessible via Bluefruit.Gap.*** and has the
following public functions:
typedef void (*connect_callback_t ) (uint16_t conn_handle); typedef void (*disconnect_callback_t ) (uint16_t conn_handle, uint8_t reason);
uint8_t getAddr (uint8_t mac[6]); bool setAddr (uint8_t mac[6], uint8_t type);
bool connected (uint16_t conn_handle);
uint8_t getRole (uint16_t conn_handle);
uint8_t getPeerAddr (uint16_t conn_handle, uint8_t addr[6]); ble_gap_addr_t getPeerAddr (uint16_t conn_handle); uint16_t getPeerName (uint16_t conn_handle, char* buf, uint16_t bufsize);
©Adafruit Industries Page 94 of 186
uint16_t getMTU (uint16_t conn_handle); uint16_t getMaxMtuByConnCfg (uint8_t conn_cfg); uint16_t getMaxMtu (uint8_t conn_handle);

BLEAdvertising

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
'Advertising' is what makes your Bluetooth Low Energy devices visible to other
devices in listening range. The radio sends out specially formatter advertising packets
that contain information like the device name, whether you can connect to the device
(or if it only advertises), etc.
You can also include custom data in the advertising packet, which is essential how
beacons work.
The BLEAdvertisingData andBLEAdvertisingclasses exposes a number of helper
functions to make it easier to create well-formatted advertising packets, as well as to
use theScan Response option, which is an optional secondary advertising packet that
can be requested by a Central device. (This gives you another 27 bytes of advertising
data, but isn't sent out automatically like the main advertising packet.).
This two advertising packets are accessible via theparent AdafruitBluefruit class,
calling ' Bluefruit.Advertising.* ' and ' Bluefruit.ScanResponse.* ' from your
user sketches.
For examples of using these helper classes, see any of theexampleslater on in this
guide, since all devices will advertise as part of the startup process.
API
The BLEAdvertisingData class has the following public API:
/*------------- Adv Data -------------*/ bool addData(uint8_t type, const void* data, uint8_t len); bool addFlags(uint8_t flags); bool addTxPower(void); bool addName(void); bool addAppearance(uint16_t appearance); bool addManufacturerData(const void* data, uint8_t count);
©Adafruit Industries Page 95 of 186
/*------------- UUID -------------*/ bool addUuid(BLEUuid bleuuid); bool addUuid(BLEUuid bleuuid1, BLEUuid bleuuid2); bool addUuid(BLEUuid bleuuid1, BLEUuid bleuuid2, BLEUuid bleuuid3); bool addUuid(BLEUuid bleuuid1, BLEUuid bleuuid2, BLEUuid bleuuid3, BLEUuid bleuuid4);
bool addUuid(BLEUuid bleuuid[], uint8_t count);
/*------------- Service -------------*/ bool addService(BLEService& service); bool addService(BLEService& service1, BLEService& service2); bool addService(BLEService& service1, BLEService& service2, BLEService& service3); bool addService(BLEService& service1, BLEService& service2, BLEService& service3, BLEService& service4);
/*------------- 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);
bool setData(Advertisable& adv_able) { return adv_able.setAdv(*this); }
In addition to API from BLEAdvertisingData, The BLEAdvertising class also has
functions that dictate the behavior of advertising such as slow/fast timeout, adv
intervals, and callbacks etc... 
typedef void (*stop_callback_t) (void); typedef void (*slow_callback_t) (void);
void setType(uint8_t adv_type); void setFastTimeout(uint16_t sec);
void setSlowCallback(slow_callback_t fp); void setStopCallback(stop_callback_t fp);
void setInterval (uint16_t fast, uint16_t slow); void setIntervalMS(uint16_t fast, uint16_t slow);
uint16_t getInterval(void);
bool setBeacon(BLEBeacon& beacon); bool setBeacon(EddyStoneUrl& eddy_url);
bool isRunning(void);
void restartOnDisconnect(bool enable); bool start(uint16_t timeout = 0); bool stop (void);

Related Information

Generic Access Profile(https://adafru.it/vaL): This page contains the official list
of assigned numbers for the'Data' type field. Data is inserted into the
©Adafruit Industries Page 96 of 186
advertising packet by supplying a valid 'data' type, optionally followed by a
properly formatted payload corresponding to the selected value.

Example

For practical example code, see theExamples section later on in this guide. The
snippet below is provided for illustration purposes, but advertising should be
examined in the context of a real use case since it varies from one setup to the next!
void setup(void) { // Other startup code here // ...
// Set up Advertising Packet setupAdv();
// Start Advertising Bluefruit.Advertising.start(); }
void startAdv(void) { // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower();
// 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 }
©Adafruit Industries Page 97 of 186

BLEScanner

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 inCentral 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
API
BLEScanner has the following public API:
typedef void (*rx_callback_t) (ble_gap_evt_adv_report_t*); typedef void (*stop_callback_t) (void);
BLEScanner(void);
ble_gap_scan_params_t* getParams(void); bool isRunning(void);
void useActiveScan(bool enable); void setInterval(uint16_t interval, uint16_t window); void setIntervalMS(uint16_t interval, uint16_t window); void restartOnDisconnect(bool enable);
©Adafruit Industries Page 98 of 186
void filterRssi(int8_t min_rssi); void filterMSD(uint16_t manuf_id);
void filterUuid(BLEUuid ble_uuid); void filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2); void filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2, BLEUuid ble_uuid3); void filterUuid(BLEUuid ble_uuid1, BLEUuid ble_uuid2, BLEUuid ble_uuid3, BLEUuid ble_uuid4); void filterUuid(BLEUuid ble_uuid[], uint8_t count);
void clearFilters(void);
bool start(uint16_t timeout = 0); bool stop(void);
/*------------- Callbacks -------------*/ void setRxCallback(rx_callback_t fp); void setStopCallback(stop_callback_t fp);
/*------------- Data Parser -------------*/ uint8_t parseReportByType(const uint8_t* scandata, uint8_t scanlen, uint8_t type, uint8_t* buf, uint8_t bufsize = 0); uint8_t parseReportByType(const ble_gap_evt_adv_report_t* report, uint8_t type, uint8_t* buf, uint8_t bufsize = 0);
bool checkReportForUuid(const ble_gap_evt_adv_report_t* report, BLEUuid ble_uuid); bool checkReportForService(const ble_gap_evt_adv_report_t* report, BLEClientService svc); bool checkReportForService(const ble_gap_evt_adv_report_t* report, BLEService svc);

setRxCallback(rx_callback_t fp)

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");
©Adafruit Industries Page 99 of 186
/* Raw buffer contents */ Serial.printf("%14s %d bytes\n", "PAYLOAD", report->dlen); if (report->dlen) { Serial.printf("%15s", " "); Serial.printBuffer(report->data, report->dlen, '-'); Serial.println(); }
/* RSSI value */ Serial.printf("%14s %d dBm\n", "RSSI", report->rssi);
/* Adv Type */ Serial.printf("%14s ", "ADV TYPE"); switch (report->type) { case BLE_GAP_ADV_TYPE_ADV_IND: Serial.printf("Connectable undirected\n"); break; case BLE_GAP_ADV_TYPE_ADV_DIRECT_IND: Serial.printf("Connectable directed\n"); break; case BLE_GAP_ADV_TYPE_ADV_SCAN_IND: Serial.printf("Scannable undirected\n"); break; case BLE_GAP_ADV_TYPE_ADV_NONCONN_IND: Serial.printf("Non-connectable undirected\n"); break; }
/* Check for BLE UART UUID */ if ( Bluefruit.Scanner.checkReportForUuid(report, BLEUART_UUID_SERVICE) ) { Serial.printf("%14s %s\n", "BLE UART", "UUID Found!"); }
/* Check for DIS UUID */ if ( Bluefruit.Scanner.checkReportForUuid(report, UUID16_SVC_DEVICE_INFORMATION) ) { Serial.printf("%14s %s\n", "DIS", "UUID Found!"); }
Serial.println(); }

void useActiveScan(bool enable);

Enabling 'Active Scan' by setting the enable parameter to 1 will cause the device to
request the optionalScan Response advertising packet, which is a second 31 byte
advertising packet that can be used to transmit additional information.
By default active scanning is disabled, so no Scan Response packets will be received
by BLEScanner unless this function is called and set to 1 before calling Bluefruit.S
canner.start(0) .
©Adafruit Industries Page 100 of 186
Loading...