Nano: Wire.h + (INT0 or INT1) = Crash.

Hi,

I'm not sure whether this post should be here or in the feasibility thread. Apologies if it's not the right place.

I usually develop with AVR Studio to program my AVR MCU using ASM. My friends keep explaining that I should revisit the Arduino because it is an "easier" platform to work with. However, every time I pick an Arduino up, I very quickly encounter an obstacle that prevents me going forwards, and inevitably, I return to AVR Studio and ASM. So here's my dilemma this time around...

I have tried using the DS1307 to trigger INT0 on the 1HZ square wave out. Within INT0, I then attempt to use the I2C bus which causes the Arduino to freeze, loop or crash... I can't identify exactly what is the cause, but in any case the program appears to stop working. I have since read that using calls to the wire library whilst in an externally triggered interrupt causes unpredictable results.

The thing I have yet to find is a rational explanation of why this happens, and an understanding of the Arduino architecture that justifies this behaviour. I have previously performed wire access within an external interrupt using ASM without a problem.

Thanks in advance.

It helps if you post the problem code.

The Wire library uses interrupts and if called within an ISR interrupts are disabled.

People have written very complex code using the Arduino IDE, there's no reason not to use it.

There is no Arduino architecture per se, the problem lies in your code.

You have failed to submit either the code that works nor the code that doesn't work.

Therefore your question is purely hypothetical.

Here's the bare code which I have stripped from my main application in order to demonstrate the issue in question. Remove the line as documented to test the interrupt. The on-board LED should blink at 1Hz intervals when the interrupt is operational.

#include <Wire.h>
#include "RTClib.h"

int LEDpin = 13;
volatile int LEDstate = LOW;

RTC_DS1307 RTC;

void setup () {
  pinMode(LEDpin, OUTPUT);
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
  RTC.writeSqwPinMode(SquareWave1HZ);
  attachInterrupt(0, CLKread, CHANGE); 
}

void loop () {
}

void CLKread() {
  LEDstate = !LEDstate;
  digitalWrite(LEDpin, LEDstate);
  DateTime now = RTC.now();   // COMMENT OUT THIS LINE TO TEST INTERRUPT
}

I think I have found the answer myself.

The Wire library documentation states that...

The Wire library's I/O blocks. This means that when sending or receiving over the I2C bus, your application is prevented from running until the communication is complete. In truth the operation of the TWI hardware in your processor is interrupt-driven, so your application should be free to run at full speed while the TWI communication takes place, but this capability is not utilized by the Wire library.

The Wire library has poor interrupt implementation.

The reason for the hang-up is that interrupts are hierarchical in that as a general rule higher interrupts can be interrupted by lower interrupts, but lower interrupts cannot be interrupted by higher interrupts. Since external interrupts INT0 and INT1 are both lower than than the TWI interrupt, the TWI interrupt does not execute as it cannot process because execution is currently within INT0. This causes the application to wait indefinitely for the I2C interrupt to occur.

Solution: Write my own I2C code.

Hi!

I am having a similar problem as you described, could you give more detail about the changes you made to the TWI library?

Cheers!

Don't try to read the clock from within an ISR, is all the change you have to make. You could read the time in your main loop. It's not going to change thousands of times a second.

Fronkenpoop:
Solution: Write my own I2C code.

Sure you can do that, but it's not quite that simple. If you make I2C run in the background, what happens then if you have another interrupt and you start a second I2C transaction (such as reading the clock) while the first one is still running?

There are some things you can't usefully do in an ISR (such as doing serial printing) which is not just "poor implementation". If you plan to relax these restrictions you are going to have to start writing re-entrant code, have semaphores for atomic access to variables, and so on. It's not just "bad design".

From the way he talked the date of this thread, and his post count, I'd say he gave up on Arduino again and went back to programming through atmel studio. I doubt you'll be able to reach him, as he's posted but 3 times, all in this thread, which was last year.

The recommended solution is not to do what that guy did to wind up in trouble in the first place. Don't try to use I2C, SPI, or Serial within an ISR. Your ISR's should be fast, and then set flags that you can check for within loop().

I was really trying to talk to anyone that might spot this thread one day.

I was taking to cbarreto, who addressed the op inquiring about his modifications to the library; I was in no way criticizing your posting.

Yes the guy who posted at first seems to be unnactive for a while.

I will describe a bit more my problem:
I have a Master Device who communicates with the Slave through I2C.

My Slave is a custom board with an atmega328p and a SPI ADC that measures a thermocouple. The device is always sleeping, it wakes up with two different events: an I2C request, or with an DRDY that comes from the ADC and is detected with the INT0.

In a random time the i2c comms stop working, and i think is because of the hierarchy commented before.

when i have a I2C request event i just send the data:

void requestEvent()
{
  detachInterrupt (0);
  if (registerAddress == 10)
    Wire.write(dataArray,4);

  // Serial.println("Request");
  attachInterrupt (0, wakeUp, LOW);
}

When i have an interrupt i just set a flag to true:

void wakeUp()
{
  detachInterrupt(0);
  dataReady = true;
}

The main loop is only doing this:

void loop() {
  if (dataReady == true){
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;
    digitalWrite(ledPin, ledState);
    for (uint8_t i = 0; i<4; i++){
      dataArray[i] = (uint8_t)random(256);
    }
    dataReady = false;
    sleepNow();
  }
}

This is a code i put into an arduino uno to reproduce the error i am having in my custom board since is easier to debug.

Any ideas?

Thanks!

Update:

On my slave arduino I did a fix disabling the power of the I2C using power_twi_disable(); and re-enabling it after finishing the tasks of the INT0. It worked fine, but when I put this fix in my custom board there wasn't any change, it keeps failing. :frowning: