Crash with SPI in interrupt

Far from a pro at uC programming, so maybe I'm missing something fundamental here. . .

I'm attempting to retrieve data from a sensor over SPI and I need to read the data immediately after it becomes available. I'm using an interrupt signal generated by the external sensor to dictate when I need to read the data. The problem is that my Nano 33 BLE crashes whenever I try to read the data during the interrupt.

I prefer not to perform serial communication in an ISR, but the main loop might miss a sensor reading "cycle" if it becomes too complex.

Here's my code:

#include "MPU9250.h"

unsigned long lt = 0;
unsigned long dif = 0;
bool newData = 0;

// an MPU9250 object with the MPU-9250 sensor on SPI bus 0 and chip select pin 10
MPU9250 IMU(SPI,10);


void setup() {
  // serial to display data
  Serial.begin(2000000);
  while(!Serial) {}
  
  // setting DLPF bandwidth to 20 Hz
  IMU.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_20HZ);
  // setting SRD to 9 for a 100 Hz update rate
  IMU.setSrd(9);
  // enabling the data ready interrupt
  IMU.enableDataReadyInterrupt();
  // attaching the interrupt to microcontroller pin 1
  pinMode(6,INPUT);
  attachInterrupt(digitalPinToInterrupt(6),getIMU,RISING);
  SPI.usingInterrupt(digitalPinToInterrupt(6));
  lt = micros();
}

void loop() {
  // if the interrupt was triggered, just print how long it's been since the last one.
  if(newData) {
    dif = micros()-lt;
    lt = micros();
    Serial.println(dif);
    newData = false;
  }
}

void getIMU(){ 
  newData = true;
  IMU.readSensor();     //Works when this line is edited out
}

I've checked a few things that make me suspect this is caused by using SPI in the interrupt:

  • The external sensor is running as expected, and interrupt is generated very regularly.
  • Everything works if I comment out the IMU.readSensor() line.
  • Everything stops working when I uncomment that line.
  • Moving the IMU.readSensor() to the main loop makes things work.
  • If I re-write the interrupt without IMU.readSensor() (by replacing it with an equivalent SPI transaction then the nano 33 BLE still crashes.

Any help understanding what I'm doing wrong would be appreciated. I'm also totally open to advice on how to get reliable sensor readings without putting SPI comms in the interrupt

The Arduino Nano 33 BLE uses mbedOS and that really hates you doing anything that takes a long time and other bad things during interrupts. It stops the OS from doing its stuff e.g. switching to other tasks like USB.

If you want to learn more about this just Google mbedOS and interrupts. I found the following page. I did not read it, but it looked like it could be useful.

https://os.mbed.com/users/AjK/notebook/regarding-interrupts-use-and-blocking/

The easiest solution is to set a flag in the ISR and then run the code from loop.

Btw, Serial.begin() on the Nano 33 BLE ignores the baudrate parameter. You might as well use a standard value there. The speed is limited by the USB transfer rate.

Klaus_K:
The Arduino Nano 33 BLE uses mbedOS and that really hates you doing anything that takes a long time and other bad things during interrupts. It stops the OS from doing its stuff e.g. switching to other tasks like USB.

The easiest solution is to set a flag in the ISR and then run the code from loop.

Thanks for taking the time to reply!
I can't guarantee that the loop will run fast enough (once the rest of the project code is there) to read every single cycle from the sensors. I'm reading instantaneous rates of change and integrating them, so every missed sensor cycle increases the cumulative error and will have to get filtered out by sensor fusion. This is possible, but the filter already has to contend with sensor limitations, bias, offset, calibration errors, and natural phenomenon. I'd like to avoid dampening the filter with any unnecessary errors that slow it down further.
I've been suspecting that I might have the wrong board for this project. Based on this information, I'm thinking I should either offload the SPI communication to another thread using MBED (haven't looked into this yet), or migrate to a more powerful board (like a teensy 4.1). I'll try threads first and see if that leads to satisfactory results.

prototyper481:
I'm thinking I should either offload the SPI communication to another thread using MBED (haven't looked into this yet), or migrate to a more powerful board (like a teensy 4.1).

There is another option. The SPI is designed with an EasyDMA option. It is likely your best option in any case, even with a more powerful processor, moving a lot of data around is a waste of processor time.

I will read a little bit trough the datasheet since this sounds interesting. Maybe I can give you some pointers. No promises. :slight_smile:

Edit: I had a look through the source code.

  • The Arduino SPI code looks like a simple wrapper for the mbedOS SPI code only using simple functions.
  • mbedOS seems to have some DMA capability
  • the nRF sdk inside mbedOS folder has EasyDMA related source files e.g., register descriptions, functions

It is not easy to figure out how this all plays together. I am not sure how the EasyDMA from Nordic works together with the mbedOS DMA framework. I suspect the mbedOS functions are more simplistic and might not cover all the possible use cases.
I did not find a simple example on how to use this.

There is some example C code in the datasheet, but I do not know whether you can just use them on the Arduino Nano 33 BLE without breaking something. I suspect they are written for the Nordic SDK.

Klaus_K:
There is another option. The SPI is designed with an EasyDMA option. It is likely your best option in any case, even with a more powerful processor, moving a lot of data around is a waste of processor time.
. . .
I did not find a simple example on how to use this.

Funny, I just stumbled on some posts about DMA last night and was mulling that over. It sounds like DMA would be the best solution, but I've never used DMA on any microcontroller before, and there really don't seem to be any good examples. I also understand that it is very difficult to troubleshoot, since it's hard to "watch" what's happening at that level. Trial-and-error might be a tedious way to go about this, but I'm willing to put in effort as long as I see light at the end of the tunnel.
I'll post back if I get anywhere with it.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.