I2C within ISR?

Is it possible to use I2C communication within an interrupt routine? I am thinking about counting pulses on an interrupt pin and storing epoch from an I2C RTC. I realize that interrupt is disabled within the routine. If I2C requires interrupt, should I enable interrupt inside the ISR? The pulses are from a rain gauge. It bounces around a few times before stabilizing a 0.1 second HIGH, and then it bounces around a few more times before going LOW. The bouncing is around hundreds of microseconds each. I can detach the ISR inside the ISR though.

Detaching is usually considered to be an expensive operation. It is usually done with a single boolean variable to prevent multiple copies of the ISR running simultaneously. When the interrupt is triggered, first check that variable, if it's true then exit, otherwise set it to true and then re-enable interrupts.

void myISR() {
  static boolean IsRunning = false;
  if(IsRunning) return; //we have been interrupted while already processing an interrupt - ignore this ocurrence
  IsRunning = true;
  //Depending on the interrupt source, you may need to clear the interrupt flag here. Most Arduino interrupts are self-clearing.
  interrupts(); //re-enable interrupts so that interrupt-based functions can be used inside this function

  //your code here
  //Serial.print, I2C and SPI functions should all work OK with interrupts enabled


  noInterrupts(); //turn off interrupts so we can't be interrupted while resetting our special variable
  IsRunning = false;
  return;
}

I can’t imagine why it might be necessary to communicate within an ISR triggered by a raindrop. Isn’t it sufficient to do the communication outside the ISR ?

Am I allowed to say the usual? - “post your code”

…R

Do the I2C outside the ISR. It would even be wrong to do I2C inside an ISR, and even more when the ISR is from a bouncing source. The functions Wire.endTransmission() and Wire.requestFrom() wait until the I2C transmission has finished.

Regarding code, I have a very long test code and most of it is not related to this post. The rain gauge ISR is very short, incrementing a counter. I have not added RTC code to it.

Some more details. The project is a data logger. It spends most of the time sleeping with power down mode on WDT wake-up source. It wakes up after 5 minutes to read sensors and send data out. To find out total rain fall and how heavy the rain is, the data logger stores epoch time each time the rain gauge tips. If the rain gauge wakes the logger, it's easy to read RTC in the main loop. I have flags indicating the source of wake-up. But if the rain gauge tips when the logger is awake and collecting data, which could take 15 seconds, the main program will miss the flag with up to 15 second delay or even let the flag overflow if the rain gauge trips more than once while the main routine is collecting data.

There are various sensors with up to 13 second acquisition time and one needs mechanical wiping before sensing. If I turn delays in the main loop into state machine, it would be prohibitively complicated.

I think I should probably read and log millis in ISR if the logger is awake. Although it doesn't tick inside ISR, I just need the value so the main routine can convert it back to RTC after it's sensing routine is over.

for AVR, it is not possible to use i2c inside isr. It is possible with ARM processors. I asked same question in teensy forum and the author of the teensy specific i2c library (i2c_t3) said yes and the library even comes with an example doing just that.

liudr: But if the rain gauge tips when the logger is awake and collecting data, which could take 15 seconds, the main program will miss the flag

I appreciate you are not a beginner but I am still drawn to wonder why any action would take 15 seconds non-stop. I try to write functions that take only a number of microsecs or millisecs if they are really slow. Of course they need to repeat many times to get the job done. But they can check for other stuff between the repeats.

...R

There are a helper functions for a circular buffer. You could store the data from the ISR in a circular buffer. However, I agree with Robin2, maybe you have to re-write the sketch so functions don't take 15 seconds but only milliseconds.

An SDI-12 interfaced turbidity sensor takes a few seconds to return data. Before acquiring data, it needs to be wiped, which involves sending a pulse to the wiper's controller and wait. A serial port sonic ranger takes less than a second to get a reading. I wrote separate state machines to handle the logic of SDI-12 sensors. I'm not hiding my code. Unless someone reads through the SDI-12 spec and have experience handling it, the code will not be understandable.

liudr:
An SDI-12 interfaced turbidity sensor takes a few seconds to return data.

I don’t know the device but there was another Thread a while back where someone was using a weight sensor that needed time to read and his code was waiting. In fact it was not necessary to wait. He could initiate a reading and come back later for the result.

Would a similar system work with your device ?

…R

The SDI-12 seems to be a normal serial 7 bit with parity 1200 baud signal. The odd thing is that it is half-duplex on a single wire. A special break condition needs 12ms spacing.

Do you use a software implementation for the SDI-12 communication ? Then a few microseconds waiting is needed now and then (when an interrupt is used for incoming data). At a higher level, use millis() to wait during the break condition and to wait for the sensor to get ready.

liudr: If I turn delays in the main loop into state machine, it would be prohibitively complicated.

Is that because you try to put everything into the same state machine, or worse, one big switch-case?

Even to handle that sensor you should be able to write the wipe, query and answer as 3 separated parts each with a controlled trigger.

"Prohibitively complicated" is what you will end up with if you DON'T get rid of delay(), and implement one, or more, state machines instead. Spaghetti-coding complex sequences is the surest way to eventually losing all your hair, through endless bugs, and unexpected, often unpredictable, behaviors. Responsive performance is impossible using delay(), except for the most trivial operations. State machines allow the most complex sequences to be coded with relative ease, while maintaining responsive performance.

Regards, Ray L.

Peter_n: The SDI-12 seems to be a normal serial 7 bit with parity 1200 baud signal. The odd thing is that it is half-duplex on a single wire. A special break condition needs 12ms spacing.

Do you use a software implementation for the SDI-12 communication ? Then a few microseconds waiting is needed now and then (when an interrupt is used for incoming data). At a higher level, use millis() to wait during the break condition and to wait for the sensor to get ready.

Yes correct. That's only the signalling or physical layer. To communicate with a sensor, you also need to follow the protocol dictating text exchanged between the data logger and the sensor. Send in measurement command and wait for response of how many data points in how many seconds (not milliseconds, some takes 13 seconds), wait for time to expire or receive a service request. Then send in the data command and wait for data to return. The signalling layer is in an MCU by itself, which also handles resending in case of no response. Some sensors solely rely on resending commands to respond. The text exchange layer is on MEGA2560. I wrote it in a state machine but not completely. If I do, then I'll have a dozen more states, i.e. waiting on timer states that are tens to hundreds of milliseconds each. Only the main wait indicated by the sensor is not using delay.

Robin2: I don't know the device but there was another Thread a while back where someone was using a weight sensor that needed time to read and his code was waiting. In fact it was not necessary to wait. He could initiate a reading and come back later for the result.

Would a similar system work with your device ?

...R

Yes, the main waiting loop is not using delay for seconds, only small delays are using delay().

GoForSmoke: Is that because you try to put everything into the same state machine, or worse, one big switch-case?

Even to handle that sensor you should be able to write the wipe, query and answer as 3 separated parts each with a controlled trigger.

I have a few different state machines. I use switch case for state machines, not OOP. I find it sometimes unnecessary to have the overhead of OOP when I know there is only one such instant at all time.

liudr:
Yes, the main waiting loop is not using delay for seconds, only small delays are using delay().

I have a sketch that runs loop() typically over 57KHz to increment a counter, watch a button and blink a led.
I added delay(1) and got 970-980Hz. 16000 cycles wasted every loop() overbalances the rest by about 60x.
I suggest that “small” needs to be put in scale of the rest of the code, it is more probably not small especially in terms of how often sensing or high speed serial is handled. 16000 cycles per ms, do what?

What is the overhead with writing your own simple classes? C++ Container Classes like String is another whole ball of wax, I mean when you make your own, what extra values are stored?

You can go simpler using functions that have static variables as hard-coded I-O drivers and set global variables but when you want to reduce complexity, C++ classes have great tools to do just that.

I suggested something even more primitive, more simple. A way to reduce nesting and complexity.

If I have an array to process before some next step, I could code a for-loop with the process inside and then the whole next step. That task will be fat and impact other IO negatively.
Instead, I keep an index, same as a for-next, and manage that to process one array element per loop() and only when that is done before the next step, similarly spread out. IO and other tasks get interleaved without any more structure than order within loop(). And you don’t waste large numbers of cycles except for Arduino IDE compared to well written AVR assembler.