Reading a sensor using I^2C in the ISR

Hello everyone,

I have the Adafruit MPRLS Ported Pressure Sensor. It uses I^2C for communication. For my project, these pressure values need to be stored in a buffer continuously. Unfortunately, the code interrupts after the sensor has been calibrated. The interrupt time has already been increased, but this did not solve the problem. (The reason for the communication interruption is due to I^2C, which also uses interrupts).

Can someone please help me figure out how to read the values in the ISR anyway?

Thank you very much for any help!

Here is my code:

#include <Wire.h>
#include "Adafruit_MPRLS.h"
#include <Adafruit_NeoPixel.h>  


const int PIN = 6;              
const int NUM_PIXELS = 11;
Adafruit_NeoPixel pixels(NUM_PIXELS, PIN, NEO_GRB + NEO_KHZ800);


int fuellstand = 0;

const int RESET_PIN = -1;
const int EOC_PIN = -1;

const int Engine = 2;
const int button = 8;
int value = 0;

Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);

// ---- Konstanten ----
const float rho = 1000.0;  // Dichte Wasser (kg/m³)
const float g_acc = 9.81;  // Erdbeschleunigung (m/s²)
const float A = 0.01;      // 10 cm × 10 cm = 0.01 m²
// ---------------------

float p0_hPa = 0;  // Umgebungsdruck (wird beim Start gemessen)

const int BUILED = 13;

const int BUFFERSIZE =  100;          
int Buffer[BUFFERSIZE] = { 0 };  




void setup() {
  pinMode(BUILED, OUTPUT);
  pinMode(button, INPUT_PULLUP);
  pinMode(Engine, OUTPUT);
  pixels.begin();

  Serial.begin(9600);
  while (!Serial) { delay(10); }
  Serial.println("MPRLS - Diagnose startet...");


  Serial.println("Initialisiere MPRLS Sensor...");
  if (!mpr.begin()) {
    Serial.println("FEHLER: MPRLS Sensor nicht gefunden!");
  }
  Serial.println("MPRLS Sensor gefunden!");


  Serial.println("Kalibriere...");
  const int samples = 10;
  float sum = 0;
  for (int i = 0; i < samples; i++) {
    float pressure = mpr.readPressure();
    Serial.print("Messung ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(pressure);
    sum += pressure;
    delay(200);
  }
  p0_hPa = sum / samples;

  Serial.print("Referenzdruck: ");
  Serial.print(p0_hPa);
  Serial.println(" hPa");
  Serial.println("Kalibrierung abgeschlossen!");

  Timer1_init_us(1000);
}








void NeoPixel_strip(int fuellstand) {

  if (fuellstand > 1500) {
    pixels.fill(pixels.Color(0, 150, 0), 0, 11);
    pixels.show();
  }

  if (fuellstand <= 1500 && fuellstand > 1250) {

    pixels.fill(pixels.Color(0, 150, 0), 0, 4);
    pixels.fill(pixels.Color(255, 120, 0), 4, 8);
    pixels.fill(pixels.Color(150, 0, 0), 8, 11);
    pixels.show();
  }

  if (fuellstand <= 1250 && fuellstand > 1000) {

    pixels.fill(pixels.Color(255, 120, 0), 4, 8);
    pixels.fill(pixels.Color(150, 0, 0), 8, 11);
    pixels.show();
  }

  if (fuellstand <= 1000 && fuellstand > 750) {


    pixels.fill(pixels.Color(255, 120, 0), 6, 8);
    pixels.fill(pixels.Color(150, 0, 0), 8, 11);
    pixels.show();
  }

  if (fuellstand <= 750 && fuellstand > 500) {

    pixels.fill(pixels.Color(255, 120, 0), 7, 8);
    pixels.fill(pixels.Color(150, 0, 0), 8, 11);
    pixels.show();
  }

  if (fuellstand <= 500 && fuellstand > 200) {

    pixels.fill(pixels.Color(150, 0, 0), 8, 11);
    pixels.show();
  }

  if (fuellstand <= 200) {

    pixels.fill(pixels.Color(250, 0, 0), 0, 3);
    pixels.show();
  }
}




void loop() {

  //Mittelwert Buffer
  float mlAV = 0.0;
  for (int i = 0; i < BUFFERSIZE; i++) {
    mlAV += Buffer[i];
  }
  mlAV = mlAV / BUFFERSIZE;

  Serial.print("MW:");
  Serial.println(mlAV);

  NeoPixel_strip(mlAV);

  value = digitalRead(button);

  if (value == LOW) {
    digitalWrite(Engine, HIGH);
  } else {
    digitalWrite(Engine, LOW);
  }

  delay(500);

}



ISR(TIMER1_COMPA_vect)  // ISR Timer1
{
  static int timer = 0;
  static bool state = 0;

  static int BufferCounter = 0;
  //fa=1kHz

  float pressure_hPa = mpr.readPressure();

  float diff_hPa = pressure_hPa - p0_hPa;
  float diff_Pa = diff_hPa * 100.0;  // 1 hPa = 100 Pa

  // Höhe der Wassersäule in Metern
  float h = diff_Pa / (rho * g_acc);

  // Volumen in ml
  float volumen_ml = (A * h) * 1e6;

  Buffer[BufferCounter] = volumen_ml;
  BufferCounter++;
  if (BufferCounter >= BUFFERSIZE) {
    BufferCounter = 0;
  }

  //Test
  //1Hz Blinker
  timer++;
  if (timer >= 500) {
    timer = 0;
    state = !state;
    digitalWrite(BUILED, state);
  }
}








//inspired by Google Code project http://code.google.com/p/arduino-timerone/
//Value in microseconds
//ISR(TIMER1_ISR(TIMERx_vect)){} is necessary
void Timer1_init_us(long microseconds) {
  unsigned char clockSelectBits;
  long RESOLUTION = 65536;

  TCCR1A = 0;
  TCCR1B = _BV(WGM13);
  long cycles = (F_CPU / 2000000) * microseconds;
  if (cycles < RESOLUTION) clockSelectBits = _BV(CS10);
  else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11);
  else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10);
  else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12);
  else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10);
  else cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10);

  char SREG_bak = SREG;
  cli();          // Disable interrupts for 16 bit register access
  ICR1 = cycles;  // ICR1 is TOP in phase & frequency correct PWM mode
  SREG = SREG_bak;

  TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
  TCCR1B |= clockSelectBits;

  TIMSK1 = _BV(TOIE1);
  TCCR1B |= clockSelectBits;
}

//______________________________________________________________________________________________________________________________________________

What does that mean?

Chances are you can't, and definitely shouldn't. If the component sends an interrupt when data is ready, just set a (volatile) flag in the ISR to be picked up and acted upon by your non-ISR code.

Please explain this more. I don't understand why this is needed. Your neopixels are only updated, and results sent to serial monitor, every 500ms.

Do you know how long it takes to read the sensor? Might be worth measuring this by taking 1000 readings as quickly as possible and timing how many microseconds it takes.

Why are you trying to use a timer interrupt to take the readings? Interrupt routines need to be simple and fast. Performing complex I/O operations like printing to serial monitor or using i2c communications is a bad idea.

There is no good reason to do that.

Simply set a global flag (declared volatile) indicating that new data are ready, and the main routine reads it.

Hey, thanks for the quick reply. Later on, a pump will need to transport a certain amount of water. To maintain the current water level, I am using an ISR.

Measurements showed:
Slowest measurement: 6372 μs

The time interrupt seemed like a good solution to me for obtaining consistently valid measurement results.

Only if you have delays or other blocking functions in your program!

Correct, and that is indeed the case! (The code shown above is only an excerpt that illustrates the problem.)

So, you are putting band aids on a broken program.

Not really :slight_smile: I'm actually trying to optimize my working code with an ISR.

Is that for all 1000 readings or just one? 6.4us per reading sounds amazingly fast for i2c.

It isn't, as you now know.

How long do you think the pump will take to stop? Is millisecond timing really justified?

Will your decision be based on the average of 100 readings, so will take at around 0.1 seconds for that average to change.

My advice would be:

  1. Don't use interrupts. Take a reading in your code each time millis() changes.
  2. Don't use delay(), allowing the above to work.
  3. Use millis() to determine when 500 milliseconds has passed and update your LEDs.
  4. Use a faster baud rate like 115200. This isn't the 1980s! At 9600 baud, it takes around 1ms to send a single character.
  5. To make your code more efficient, don't sum up all 100 readings to calculate your average, keep a running total by adding the new reading and subtracting the oldest one.

Thank you very much for taking the time to provide such a detailed response.

  1. I will now pursue this idea further, thank you.

2)/3) I will follow up on that too!

  1. This is already the case in the main program.

  2. Of course, the code above is only an example; I will do it differently in the future.

Thank me by answering my question!