i2c usage inside an ISR does not work

Hi Arduino community.

I have a quite annoying problem which i cannot find a solution for.
I'm trying to read sensor data over i2c bus inside an ISR. I can read the data if the function is called in the main loop, so i assume my i2c-usage is ok. But nonetheless, if i try to use said interrupt to call the exact same function as an ISR, my program always stops at wire.begintransmission, i guess. (tested with serial.print-outputs)

The implementation of the interrupt itself was tested in a dummy program which only says "hello world" if it detects an interrupt. Both things work alone, but combined, they do not.

Can anyone of you help me?
Below is my code, the interrupt-related lines are commented. the readbma020()-function is called manually during loop() at the moment.

#include <AudioShield.h>
#include <SD.h>
#include <Wire.h>
#define SENS_ADDR 0x38
#define FILENAME "values.txt"

byte data[6];          //Byte field for incoming i2c data
int bma020_x;          //Current G on X direction
int bma020_y;          //Current G on Y direction
int bma020_z;          //Current G on Z direction
File myFile;
int counter;

void setup() {
  Wire.begin();
  Serial.begin(9600);
  initBMA020();
  counter=0;
  Serial.println("Initializing SD card...");

  if (!SD.begin(SD_CS)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  myFile = SD.open(FILENAME, FILE_WRITE);
  delay(50);
  pinMode(2, INPUT);
  attachInterrupt(0, readBMA020, RISING);
  //reset_INT();
}

void loop() {
    //readBMA020();
    //Show output
    Serial.print(counter);
    Serial.print(" X: ");
    Serial.print(bma020_x);
    Serial.print("   Y: ");
    Serial.print(bma020_y);
    Serial.print("   Z: ");
    Serial.print(bma020_z);
    Serial.println();
    
  if (counter==100){
    myFile.close();
    //detachInterrupt(digitalPinToInterrupt(2);
    Serial.println("done.");
  }
  counter++;
    delay(200);
}

void initBMA020() {
  byte control; 
  Serial.println("InitBMA...");
  //Init default reg 0x14
  writeRegister(0x38, 0x15, 0x80);
 
  //Range Settings
  // Range to 2g --> 255 in datafield is ~1g
  // Range to 4g --> 127 in datafield is ~1g
  // Range to 8g --> 63  in datafield is ~1g
 
  //Set Range + Bandwidth
  control = readRegister(0x38,0x14);
  control = control & 0xE0;        // save bits 7,6,5
  control = control | 0x10;        // Range to 8g=0x10/4g=0x08/2g=0x00 (<4,3>=XX) 
  control = control | 0x00;        // Bandwidth 25 Hz (<2:1>=000)
  writeRegister(0x38, 0x14, control);
  //activate new_INT
  //control=readRegister(0x38, 0x15);
  //control=control|0x20;
  //writeRegister(0x38,0x15,control);
  Serial.println("Register 0x15:");
  Serial.println(readRegister(0x38,0x15),BIN);
  delay(50);
  Serial.println("Register 0x14:");
  Serial.println(readRegister(0x38,0x14),BIN);
  delay(50);
  Serial.println("InitBMA successful!");
 
}

byte readRegister(byte addr, byte reg){
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom(addr, (byte)1);
  return Wire.read();
}

void writeRegister(byte addr, byte reg, byte value){
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

void readBMA020() {
  int i;
 //read data for registers <0x02:0x07>
 Serial.println("INT-Routine");
  Wire.beginTransmission(0x38);
  Wire.write(0x02);
  Wire.endTransmission();
  Wire.requestFrom(0x38, 6);
  
  for (i = 0; i < 6; i++) {
    data[i]=Wire.read();
    myFile.print(data[i],BIN);
    myFile.print("#");
  }
  Serial.println("read OK");
 //integer=16bit --> only for output
  //Parse data information --> shift MSB to the left and add LSB/unused/new_data
  bma020_x = ( (int)data[1] << 8) + (int)data[0];
  bma020_y = ( (int)data[3] << 8) + (int)data[2];
  bma020_z = ( (int)data[5] << 8) + (int)data[4];
 //shift to the right to eliminate newdata+unused bits
  bma020_x = bma020_x >> (6); 
  bma020_y = bma020_y >> (6);
  bma020_z = bma020_z >> (6); 
} 
void reset_INT(){
  byte control;
  
  control=readRegister(0x38,0x0A);
  //Serial.println(control, BIN);
  control=control|0x40;
  //Serial.println(control, BIN);
  writeRegister(0x38,0x0A,control);
  Serial.println("Reset_INT done!");
}

Without seeing the code I can say that it is problem of ISR using inside of ISR. Remember, ISR should be as short as possible. Interrupts are disabled at jump to ISR and ATmega doesn't support inner interrupts.

Solution could be e.g. to set a flag inside of ISR and service it in the loop() if flag is set.

EDIT: Thank you. After reading the specification of i2c your statement makes perfect sense. I tried your solution and it worked like a charm.

Kirathon:
EDIT: Thank you. After reading the specification of i2c your statement makes perfect sense. I tried your solution and it worked like a charm.

Hello Kirathon. I have a similar problem: an I2C based ISR that works in the loop() function, but does not run inside the ISR. Could you please explain me where you included the flag?
Thanks a lot!

volatile byte flag = 0;

AnyISR()
{
  flag = 1;
}

void loop()
{
// test for ISR occurence
  if(flag) {
    flag = 0; // reset for next interrupt
  // do something
  }
}

Generally valid rule is that the ISR should be as simple and short as possible. If the MCU is ATmega, it doesn't support
inner interrupts. If you see the code posted by Kirathon, he use Serial and Wire inside of ISR but Serial (also Wire) depends on its own interrupt. Then, it is possible situation that the code execution hangs inside of ISR and it will be waiting for Serial interrupt occurence.
The code above is "guide" how to service the ISR without such problem:

  1. volatile variable (flag), you can use bit recognition/manipulation - byte = 8 bits = 8 different flags, volatile means that the variable will have always actual value during execution
  2. set the flag inside of the ISR and exit if no other steps are necessary
  3. service the flag inside of the loop().
    Do not forget, the IST have to be as short as possible. Any other interrupt can occur during ISR execution and we want to minimize chance to miss some other ISR (well tuned system).
    Just note that there is internal flag register for interrupts (ATmega) to remember their occurence if cannot be executed immediately. If more interrupts waiting for processing in one moment they are treated according their priority. If another interrupt happens while its internal flag is set, it will be omitted. The service routine will run only once. Read more in the datasheet.