I2C crash when called inside a interrupt callback?

he setup I use the I2C library (wire.h) to set the I2C RTC without any problem.
Then I have a timer based interrupt callback routine where I flash a LED.
I would want to read the RTC via I2C inside the callback routine but in this way the CPU hang up and all stop working including the interrupt requests (the LED stop flashing).
If I move the RTC reading outside the callback routine to the main loop all works good!

So I suppose that the I2C library (wire.h) can’t be placed in a interrupt called routine. Is this true? or I’m wrong? Is there a workaround for this?

#include "LiquidCrystal.h"
#include "TimerOne.h"
#include "Wire.h"

#define DS1307_ADDRESS 0x68
byte zero = 0x00; //workaround for issue #527

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);           

// define some values used by the panel and buttons
int lcd_key     = 0;
int adc_key_in  = 0;

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

int read_LCD_buttons() {            // read the buttons
  adc_key_in = analogRead(0);       // read the value from the sensor

  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  // We make this the 1st option for speed reasons since it will be the most likely result

  if (adc_key_in > 1000) return btnNONE;

  // For V1.1 us this threshold
  if (adc_key_in < 50)   return btnRIGHT;
  if (adc_key_in < 250)  return btnUP;
  if (adc_key_in < 450)  return btnDOWN;
  if (adc_key_in < 650)  return btnLEFT;
  if (adc_key_in < 850)  return btnSELECT;

  return btnNONE;                // when all others fail, return this.
}

void setup() {
  Wire.begin();
  setDateTime();                     //MUST CONFIGURE IN FUNCTION
  pinMode(13, OUTPUT);
  Timer1.initialize(500000);         // initialize timer1, and set a 1/2 second period
  Timer1.pwm(3, 512);                // setup pwm on pin 9, 50% duty cycle
  Timer1.attachInterrupt(callback);  // attaches callback() as a timer overflow interrupt
  lcd.begin(16, 2);                 // start the library
  lcd.setCursor(0, 0);              // set the LCD cursor   position
  lcd.print("DATE & TIME");          // print a simple message on the LCD
}


void callback() {
  digitalWrite(13, digitalRead(13) ^ 1);
}

void setDateTime() {

  byte second =      0; //0-59
  byte minute =      47; //0-59
  byte hour =        10; //0-23
  byte weekDay =     5; //1-7
  byte monthDay =    14; //1-31
  byte month =       2; //1-12
  byte year  =       14; //0-99

  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(zero); //stop Oscillator

  Wire.write(decToBcd(second));
  Wire.write(decToBcd(minute));
  Wire.write(decToBcd(hour));
  Wire.write(decToBcd(weekDay));
  Wire.write(decToBcd(monthDay));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));

  Wire.write(zero); //start
  Wire.endTransmission();
}

byte decToBcd(byte val) {
  // Convert normal decimal numbers to binary coded decimal
  return ( (val / 10 * 16) + (val % 10) );
}

byte bcdToDec(byte val)  {
  // Convert binary coded decimal to normal decimal numbers
  return ( (val / 16 * 10) + (val % 16) );
}

void printDate() {
  //Reset the register pointer
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(zero);
  Wire.endTransmission();

  Wire.requestFrom(DS1307_ADDRESS, 7);

  int second = bcdToDec(Wire.read());
  int minute = bcdToDec(Wire.read());
  int hour = bcdToDec(Wire.read() & 0b111111); //24 hour time
  int weekDay = bcdToDec(Wire.read()); //0-6 -> sunday - Saturday
  int monthDay = bcdToDec(Wire.read());
  int month = bcdToDec(Wire.read());
  int year = bcdToDec(Wire.read());

  String DT = "";
  DT.concat(monthDay);
  DT.concat("/");
  DT.concat(month);
  DT.concat("/");
  DT.concat(year);
  DT.concat(" ");
  DT.concat(hour);
  DT.concat(":");
  DT.concat(minute);
  DT.concat(":");
  DT.concat(second);
  
  lcd.setCursor(0, 1);              
  lcd.print(DT);
}

void loop() {
  
  printDate();
  
  //  lcd.setCursor(9, 1);            // move cursor to second line "1" and 9 spaces over
  //  lcd.print(millis() / 1000);     // display seconds elapsed since power-up
  //
  //  lcd.setCursor(0, 1);            // move to the begining of the second line
  //  lcd_key = read_LCD_buttons();   // read the buttons
  //
  //  switch (lcd_key) {              // depending on which button was pushed, we perform an action
  //
  //    case btnRIGHT: {            //  push button "RIGHT" and show the word on the screen
  //        lcd.print("RIGHT ");
  //        break;
  //      }
  //    case btnLEFT: {
  //        lcd.print("LEFT   "); //  push button "LEFT" and show the word on the screen
  //        break;
  //      }
  //    case btnUP: {
  //        lcd.print("UP    ");  //  push button "UP" and show the word on the screen
  //        break;
  //      }
  //    case btnDOWN: {
  //        lcd.print("DOWN  ");  //  push button "DOWN" and show the word on the screen
  //        break;
  //      }
  //    case btnSELECT: {
  //        lcd.print("SELECT");  //  push button "SELECT" and show the word on the screen
  //        break;
  //      }
  //    case btnNONE: {
  //        lcd.print("NONE  ");  //  No action  will show "None" on the screen
  //        break;
  //      }
  //  }
}

You can not call anything which requires interrupts from within another ISR. The arduinos are setup to turn off all interrupts during an ISR. So even delay stops working and Serial.print/write is very dodgey.

You must keep ISRs short the ideal is just to increment a counter or set a flag.

Mark

I was writing an answer, but meanwhile homes4 wrote the same.

The Wire library uses a mix of interrupts, polling, multiple buffers and so on. You can’t use it in an interrupt, except a few specific Wire functions in its own I2C callback routines.

In that callback function, you set a flag: mustReadRTC = true;
In the loop() function you test the flag

if( mustReadRTC)
{
  mustReadRTC = false;   // reset flag
  // read the RTC
  ...
}

Caltoa: In that callback function, you set a flag: mustReadRTC = true; In the loop() function you test the flag

if( mustReadRTC)
{
  mustReadRTC = false;   // reset flag
  // read the RTC
  ...
}

Good idea! Thanks!