I am writing a program to read a sensor at a fairly rapid rate (100 Hz for example) and I want to send the results over bluetooth to my laptop.
The sensor reading part work fine on it's own. The Bluetooth transfer of data works fine on it's own. When I put the two together the consistent timing of my sensor reading goes out the window! Even if I just add a BLE.begin() the consistent timing is gone.
There are many articles about this problem:
Increase the ADC sample rate - Nano Family / Nano 33 BLE - Arduino Forum
So my question is, what is the "correct" design pattern I should use to have consistent sensor reads with transmission of the data over bluetooth? I am more than happy to do "batch mode" processing: Read required number of samples from sensor, stop reading and send over bluetooth, then reinitiate the next sensor reading.
I'm at a loss for what to try next to fix the timing consistency problem.
What I'm doing now is shown below. The important parts: Timer Interrupt routine (TimerHandler0 routine) just sets a flag, that the main loop (Section labelled: "4.2 BURST Mode") checks and then uses the flag to take a reading.
*/
#include <Arduino.h>
#include <ArduinoBLE.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
//#include <Adafruit_BNO055.h>
//#include <utility/quaternion.h>
#include <Arduino_LSM9DS1.h>
#define TIMER_INTERRUPT_DEBUG 0
#define _TIMERINTERRUPT_LOGLEVEL_ 3
//#include "NRF52_MBED_TimerInterrupt.h"
#include <mbed.h>
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
#define BLE_UUID_ENVIRONMENT "181C" //Service
#define BLE_UUID_RUNMODE "2A56" //Characteristic for string writing and reading.
#define BLE_UUID_CONTMODE_STRUCTDATA "2A57" //Characteristic for calibration string writing.
// As a struct. Most efficient method vs. a string.
#define BLE_UUID_BURSTMODESTRUCTDATA "2A58" //Characteristic for data as as struct in burst mode
// which will return multiple structs in an array
#define BLE_UUID_NUMPTS "2A21" // Number of points to record when in burst mode. TBD
#define BLE_UUID_BURST_OPMODE "2A4E" // Mode of operation.
// To be defined: False=Continuous, True=burst mode.
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino Nano 33 BLE"
#define BLE_LOCAL_NAME "SensorMonitor" //The name that Python uses to find device
//----------------------------------------------------------------------------------------------------------------------
// CONSTANTS
//----------------------------------------------------------------------------------------------------------------------
#define LED_BLUE_PIN 24
#define SAMPLE_RATE 20 // ms Delay. How often do we read and update BLE data.
#define TIMER0_DURATION_MS 5 //How often the Timer loop will actually go in and check if the TimerFlag
// is set meaning time to take a reading.
#define NUMPTS_BURSTMODE 100 //Numer of points to record when in burst mode
#define NUMPTS_BURST_PERSEND 10 //Number of points to send per write to characteristic.
// * because you can't send them all at once with 244 bytes limit per send.
// * normally don't need to change this unless the struct size changes
//----------------------------------------------------------------------------------------------------------------------
// STRUCTURE DEFINITION
// Note: The unpacking in Python needs to match the byte configuration of the struct here.
//----------------------------------------------------------------------------------------------------------------------
// uint_8 = 1 byte
// int = 4 bytes
//unsigned long = 4 bytes
//unisgned short =u_int16_t = 2 bytes
typedef struct __attribute__ ((packed)) {
unsigned long timeread;
int ax;
int ay;
int az;
}sensordata ;
sensordata d;
//----------------------------------------------------------------------------------------------------------------------
// TIMER SETUP AND BURST MODE CONFIG
//----------------------------------------------------------------------------------------------------------------------
//Define array of structs for BURST MODE
//sensordata burstdata[NUMPTS_BURSTMODE];
sensordata burstdata[NUMPTS_BURSTMODE];
sensordata *burstdataptr=&burstdata[0] ;
bool timerFired=false; //Flag that the timer has fired and you should read the sensor
bool NRF52Run=false;
// Init NRF52 timer NRF_TIMER3
// NRF52_MBED_Timer ITimer0(NRF_TIMER_3);
// Init MBED Timer
mbed::Ticker mytimer;
volatile uint32_t preMillisTimer0 = 0;
volatile u_int16_t ptsread=0; // Initialize pt counter
bool toggle0 = false;
unsigned long lastTimer=0;
unsigned long previousMillis = 0; // last time the reading was done, in ms
//----------------------------------------------------------------------------------------------------------------------
// ######################### FUNCTION DEFINITIONS. NEED FOR PLATFORMIO (not Arduino) #########################
//
// https://docs.platformio.org/en/latest/faq.html#convert-arduino-file-to-c-manually
void writeHandler(BLEDevice central, BLECharacteristic characteristic);
void updateSensorStructData();
void updateSensorData();
sensordata readSensorDataAsStruct();
sensordata dummyread();
void TimerHandler0();
void setupBluetooth();
//----------------------------------------------------------------------------------------------------------------------
// ********************** INITIALIZATION **************************************
//Setup the BNO055 sensor
//Adafruit_BNO055 myIMU=Adafruit_BNO055();
BLEService mainService(BLE_UUID_ENVIRONMENT);
// A FLAG to stop and stop reading and updating data.
BLEBoolCharacteristic runmodeChar(BLE_UUID_RUNMODE,BLERead | BLEWrite | BLENotify);
BLECharacteristic contmodestructDataChar (BLE_UUID_CONTMODE_STRUCTDATA,BLERead | BLENotify, sizeof(d) );
//New Additions to support burst mode
BLEBoolCharacteristic burstoperationmodeChar (BLE_UUID_BURST_OPMODE,BLERead|BLEWrite );
BLEUnsignedShortCharacteristic burstmodenumptsChar (BLE_UUID_NUMPTS,BLERead|BLEWrite );
// Revise to send a subset of points, not total point count.
BLECharacteristic burstmodestructDataChar (BLE_UUID_BURSTMODESTRUCTDATA,BLERead | BLENotify, sizeof(d)*NUMPTS_BURST_PERSEND );
// *************** DEFINITION OF TIMER HANDLER ****************
void TimerHandler0(){
//preMillisTimer0 = millis();
if (ptsread<NUMPTS_BURSTMODE) {
//Set flag to take a reading
timerFired=true;
} //end if
}
// *************** DEFINITION OF BLUETOOTH SETUP ROUTINE ****************
void setupBluetooth(){
// begin initialization
if (!BLE.begin()) {
Serial.println("starting BLE failed!");
while (1);
}
/* Set a local name for the BLE device
This name will appear in advertising packets
and can be used by remote devices to identify this BLE device
The name can be changed but maybe be truncated based on space left in advertisement packet
*/
BLE.setDeviceName( BLE_DEVICE_NAME );
BLE.setLocalName( BLE_LOCAL_NAME );
BLE.setAdvertisedService( mainService ); // add the service UUID
// ********************** EVENT HANDLERS **************************************
runmodeChar.setEventHandler(BLEWritten, writeHandler);
//Should expand to more handles and less inline code. TBD
// ********************** ADD CHARACTERISTICS **********************************
mainService.addCharacteristic(runmodeChar); //Add characteristic to test writing
mainService.addCharacteristic(contmodestructDataChar); //Characteristic that will send struct.
mainService.addCharacteristic(burstoperationmodeChar); //Characteristic that will allow user to set operating mode.
mainService.addCharacteristic(burstmodenumptsChar); //Characteristic that will allow user to set operating mode.
mainService.addCharacteristic(burstmodestructDataChar); //Characteristic that will allow user to set operating mode.
// ********************** DESCRIPTORS TO IMPROVE READABILITY*******************
BLEDescriptor runmodeDescriptor("2901", "Run Flag: True=Run, False=Stop. Needs to be true to read sensor");
runmodeChar.addDescriptor(runmodeDescriptor);
BLEDescriptor structdataDescriptor("2901", "CONTINUOUS MODE: Data as a C Struct");
contmodestructDataChar.addDescriptor(structdataDescriptor);
BLEDescriptor modeDescriptor("2901", "BURST MODE FLAG: True=Burst Mode, False=Continuous");
burstoperationmodeChar.addDescriptor(modeDescriptor);
BLEDescriptor burstnumptsDescriptor("2901", "BURST MODE: Set Number of Pts to collect in burst mode");
burstmodenumptsChar.addDescriptor(burstnumptsDescriptor);
BLEDescriptor burstmodestructdataDescriptor("2901", "BURST MODE: Data as an array of C Structs, numpts long");
burstmodestructDataChar.addDescriptor(burstmodestructdataDescriptor);
// *********************** SET DEFAULT VALUES *********************************
burstmodenumptsChar.setValue(NUMPTS_BURSTMODE); //Set default number of points for burst mode.
burstoperationmodeChar.setValue(false); //default to continuous mode
runmodeChar.setValue(false); //Don't start running immediately...alow for config
//Add the Service
BLE.addService(mainService);
/* Start advertising BLE. It will start continuously transmitting BLE
advertising packets and will be visible to remote BLE central devices
until it receives a new connection */
BLE.advertise();
Serial.println("Bluetooth device advertising, waiting for connections...");
} //End Setup Bluetooth
// ********************** SETUP **************************************
void setup() {
Serial.begin(115200); // initialize serial communication
while (!Serial );
delay(1000);
setupBluetooth();
//Setup Onboard IMU
if (!IMU.begin()) {
Serial.println("Failed to initialize onboard IMU!");
while (1);
}
//IMU Continuous reading mode..Always get the latest sample
IMU.setContinuousMode();
pinMode(LED_BUILTIN, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected
pinMode(LED_BLUE_PIN, OUTPUT);
} //End Setup
// ********************** MAIN LOOP **************************************
void loop() {
// wait for a BLE central
BLEDevice central = BLE.central();
// *** 1. ARE YOU CONNECTED TO BLUETOOTH: if a central is connected to the peripheral:
if (central) {
Serial.print("Connected to central: ");
// print the central's BT address:
Serial.println(central.address());
// turn on the LED to indicate the connection:
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("Waiting for run mode to be set TRUE.");
// *** 2. WAIT FOR THE RUN FLAG TO START
while(!runmodeChar.value())
{
//Do nothing until the run mode is set.
//Serial.println("Waiting on run mode flag...");
BLE.poll(); //TODOL Not sure if this is needed??
}
// Serial.print("Current run mode: ");
// Serial.println(runmodeChar.value());
// *** 3. READ THE OPERATING MODE Characteristic so we know which mode to operate in.
bool currentopmode = burstoperationmodeChar.value();
Serial.print("Current operating mode: ");
if(currentopmode==false){
Serial.println("Continuous");
} else{
Serial.println("Burst");
}
//BLE.poll(); //TODOL Not sure if this is needed??
// *** 4. MAIN LOOP OF RUNNING AND COLLECTING READINGS.
// ********************************************************************
// ** 4.1 Continuous Mode **
// ********************************************************************
if(currentopmode==false){
// ** Continuous Mode
Serial.println("Entered Continuous Run Mode.");
// while the central is connected:
while (central.connected() && runmodeChar.value() ) {
unsigned long currentMillis = millis();
// ####################################################
// if time interval have passed, check the value:
// ####################################################
if (currentMillis - previousMillis >= SAMPLE_RATE) {
previousMillis = currentMillis;
//Read the datafrom sensor
sensordata data = readSensorDataAsStruct();
//Update characteristic
contmodestructDataChar.writeValue((uint8_t *)&data, sizeof(data));
} //end if
}//end while
}//end Continuous Mode
// ********************************************************************
// ** 4.2 BURST Mode **
// ********************************************************************
if (currentopmode==true) {
// ** Burst Mode **
Serial.println("Entered Burst Run Mode.");
//BLE.poll();
// Start the timer. Try MBED Ticker
//Now attach interrupt to MBED so it can start
Serial.println("Burst Mode: Timer Interrupt attached. looping.");
mytimer.attach(&TimerHandler0, SAMPLE_RATE/1000.);
while (true ){
// if (millis()-lastTimer > TIMER0_DURATION_MS){
lastTimer=millis();
// Step 1: If Flag is true, take a reading
if(timerFired==true){
//take a reading
*burstdataptr=readSensorDataAsStruct();
//Increment pointer
++burstdataptr;
//Increment points read
++ptsread;
//ReSet flag to false so we can detect next timer firing event
timerFired=false;
} //End TimerFlag
//Step 2: If we have the number of samples required.
if(ptsread==NUMPTS_BURSTMODE){
// Step 2.1: ** Stop Timer
Serial.println("Burst Mode: Stopping Timer and processing points collected.");
//ITimer0.stopTimer();
mytimer.detach();
//BLE.poll();
//Step 2.2: ** Write array to bluetooth
// Break Array into chunks to send that are under the max limit of 244 bytes.
int packetbytes=NUMPTS_BURST_PERSEND*sizeof(sensordata);
//Initialize pointer
sensordata *loopptr=&burstdata[0];
sensordata *loopend=loopptr+NUMPTS_BURSTMODE;
//Send update to Bluetooth
//Original command tried to send all data at once.
// * Exceeds the max allowable of 244 bytes per send. Look in github
// * or in source code for MTU and maxlength.
// burstmodestructDataChar.writeValue( (uint8_t *) &burstdata, sizeof(burstdata) );
Serial.println("Burst Mode: Writing data to bluetooth.");
while (loopptr!=loopend){
burstmodestructDataChar.writeValue( (uint8_t *) loopptr, packetbytes );
//Increment pointer
loopptr+=NUMPTS_BURST_PERSEND;
}//end while loop writing to bluetooth
//BLE.poll(); //TODO: Not sure if this is needed??
//Step 2.3: ** Reset pointers and counter flags
ptsread=0;
//loopptr=&burstdata[0];
burstdataptr=&burstdata[0];
//** restart timer
//ITimer0.restartTimer();
//mytimer.attach(&TimerHandler0, SAMPLE_RATE/1000.);
//Step 2.4: Break out of while loop.
break;
}//End Burst mode point number check
// } //end Time duration check
}//end while
Serial.println("Exiting Burst Mode loop processing");
} // end Burst mode
} //end if Central connected
}// End Loop() function
// ********************** SUPPORT FUNCTIONS **************************************
sensordata readSensorDataAsStruct(){
/* Read the current data values.
Returns a Struct with the data. */
// Initialize
static sensordata datareading;
static float ax,ay,az;
unsigned long tnow=millis();
IMU.readAcceleration(ax,ay,az);
// Serial.print("Sensor read: x,y,z=");
// Serial.print(ax);
// Serial.print("|");
// Serial.print(ay);
// Serial.print("|");
// Serial.println(az);
//Declare struct and populate
//The scaler 10000 converts a float so it can be sent as an int.
//It will be decoded back to float on receiving side.
datareading.timeread=tnow;
datareading.ax=(int) (ax*10000);
datareading.ay=(int) (ay*10000);
datareading.az=(int) (az*10000);
return datareading;
}
void writeHandler(BLEDevice central, BLECharacteristic characteristic){
//Event Handler. Fires when something is written to the string characteristic
// Serial.print("WriteHandler UUID: ");
// Serial.println(characteristic.uuid());
u_int8_t setval=0;
int valuelen = characteristic.valueLength(); //Get Size
Serial.print("writeHandler: Size Written=");
Serial.print(valuelen);
Serial.print("| Data= ");
for (int i=0;i<valuelen ;i++) {
setval=(u_int8_t)characteristic.value()[i];
Serial.print(setval);
}
Serial.println("");
if (setval==false)
{
//If false -> in stop Mode
Serial.println("runmode set to FALSE");
} else
{
Serial.println("runmode set to TRUE");
}
} //End Writehandler
sensordata dummyread(){
unsigned long tnow=millis();
//The scaler 10000 converts a float so it can be sent as an int.
//It will be decoded back to float on receiving side.
sensordata datareading;
datareading.timeread=tnow;
datareading.ax=20000;
datareading.ay=30000;
datareading.az=40000;
return datareading;
}
A sample of the timing inconsistencies when trying to read the onboard LSM9DS1 accelerometer at 100 Hz (10ms) is shown below. These don't happen when Arduino BLE is NOT present in the program.
Confused and hoping for some good troubleshooting / program design tips.
Greatly appreciated.