mbed::Ticker + Arduino 33 BLE + IMU.readaccelerometer() -> BLE disconnect

I have a Arduino Nano 33 BLE and I am trying to read the onboard accelerometer quickly (100Hz) and using a mbed::Ticker to control the read of the sensor. The code works as expected when I have a “dummy” routine simulating the accelerometer reading.

The problem occurs as soon as the Ticker callback (named TimerHandler0 in code below) tries to read the accelerometer. Immediately the BLE disconnects and the program hangs. I am out of ideas as to why BLE would disconnect when I try to read the onboard LSM9DS1 accelerometer.

Code below and if you have any ideas to help troubleshoot it would be GREATLY appreciated.

#include <Arduino.h>
#include <ArduinoBLE.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Arduino_LSM9DS1.h>

#define TIMER_INTERRUPT_DEBUG         0
#define _TIMERINTERRUPT_LOGLEVEL_     3
#include "NRF52_MBED_TimerInterrupt.h"

#include <mbed.h>


//----------------------------------------------------------------------------------------------------------------------
// CONSTANTS
//----------------------------------------------------------------------------------------------------------------------
#define SAMPLE_RATE           100    // ms Delay.  How often do we read and update BLE data.  
#define TIMER0_DURATION_MS    500 //How often the Timer loop will actually go in and check # of pts read and see if it's done

#define NUMPTS_BURSTMODE      20  //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.
//----------------------------------------------------------------------------------------------------------------------

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 *burstdataptr=&burstdata[0] ;

// 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


// *************** DEFINITION OF MBED TIMER HANDLER  ****************

void TimerHandler0(){
  //preMillisTimer0 = millis();
   if (ptsread<NUMPTS_BURSTMODE) {
       // ********************************************************************
        //  ***** PROBLEM:  THE REAL SENSOR READ BELOW FAILS ****
       // ********************************************************************
        //*burstdataptr=readSensorDataAsStruct();
        
        **// The dummy sensor read works.  WHY ??????**
        *burstdataptr=dummyread();
        
        //Increment pointer
        ++burstdataptr;
        //Increment points read
        ++ptsread;
      } //end if
}



// ********************** PORTION OF MAIN LOOP **************************************
void loop() {


    if (currentopmode==true) {
        
          // Start the timer
          // Try MBED Ticker
          //Now attach interrupt to MBED so it can start 

          mytimer.attach(&TimerHandler0, SAMPLE_RATE/1000.);

          
          while (true ){
            if (millis()-lastTimer > TIMER0_DURATION_MS){

              lastTimer=millis();

              if(ptsread==NUMPTS_BURSTMODE){
                  
                   //ITimer0.stopTimer();
                   mytimer.detach();
                   
				   BLE.poll();
                   

                  //** 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) );

                         while (loopptr!=loopend){
                        //Serial.print("pointer val=");
                        //Serial.println((int)loopptr);

                        burstmodestructDataChar.writeValue( (uint8_t *) loopptr, packetbytes );
                        Serial.print("Burst Mode:  Write to: ");
                        Serial.print((unsigned long)loopptr);
                        Serial.println(" successful");
                        //Increment pointer
                        loopptr+=NUMPTS_BURST_PERSEND;
                        
                      }//end while
                  
                  BLE.poll();  //TODO  Not sure if this is needed??
                  
                  //** Reset pointers and counter
                  ptsread=0;
                  //loopptr=&burstdata[0];
                  burstdataptr=&burstdata[0]; 
                  //** restart timer

                  //ITimer0.restartTimer();
                  mytimer.attach(&TimerHandler0, SAMPLE_RATE/1000.);

                 
              }//End Burst mode point check

            } //end Time duration check

          }//end while NRF52 Run

    } // 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.  */

  // Initialzie
  float ax,ay,az;

  unsigned long tnow=millis();

  // **********************************************************
  //  ***** This is the sensor read that appears to cause the issue
  // **********************************************************

  IMU.readAcceleration(ax,ay,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.
  sensordata datareading;

  datareading.timeread=tnow;
  datareading.ax=(int) (ax*10000);
  datareading.ay=(int) (ay*10000);
  datareading.az=(int) (az*10000);
  

  
	return datareading;
}


Hello,

please be so kind edit our code and place these 3 symbols ``` before and after your code.

```
// your code here
```

However, your code is too much long and I think I can say no one have time to analyze whole code for better understanding, especially when it is more Arduino code then Mbed.
Usually is good to simplify your code for isolate error from the rest, and then it is also easier for understand it for others.

I do not know what exactly the readSensorDataAsStruct() function do in the background, but I suppose a I2C transactions. If I am right then this could be reason, because I2C read/write is covered by Mutex and the Mutex is not allowed in interrupt context and that will cause MbedOS crash (error report is printed to default STDIO console).

BR, Jan

Thank you for the reply and sorry, I cleaned up my code. It was very long.

I think you’re probably right that the following line uses I2C to read the sensor.

IMU.readAcceleration(ax,ay,az);

So, no longer a mbed::ticker question but:

  • where can I find good documentation about what you can/can’t do with Ticker?
  • what are the best practices for reading a sensor a precise time intervals?

Thank You

Good question, but I am not sure, probably nowhere.

Basically all methods (of CAN, I2C, SPI, UART and more) like read/write or send/receive or another methods (printf, new, malloc) what somehow manipulate with data/memory are protected by the Mutex because of RTOS multi thread rules.

100Hz is 1000ms/100 = 10ms that is

  1. you can use Bare-metal profile, then there is no RTOS = no Mutex = no crash, but you need to manage to your self to be sure the interrupt is safe to use like that. But also different settings of project is necessary.
  2. you can use EventQeue, this will take out the code from Interrupt context. EQ aslo provide method call_every (1ms is probably best resolution), it is like Ticker, but safe.
  3. you can set just a bool flag in ISR and process it in standart loop.
  4. you can do it without Iterrupts via a basic timer in loop.

Points 3 and 4 are standart, but you need manage your while loop for this method.

BR, Jan

Thank you for the reply. I have reverted back to using points 3 and 4 but it appears I am battling an issue with having Arduino BLE in the same program.
Delays in code execution when using the ArduinoBLE version 1.1.3 and core for the Arduino Nano 33 BLE version 1.1.6 · Issue #113 · arduino-libraries/ArduinoBLE (github.com)

Not sure how to overcome this but it is not a mbed::Ticker problem or solution.

Thank You very much!