Safely using interrupts

I'm working on a rather complex robotics project run on an arduino mega. Part of the machine uses DC motors with rotary encoders. I gather that I should use interrupt functions to track when the encoder value changes, but this thread suggests that the interrupts can crash the code if they come at the wrong moment:

First understand that the interrupt can interrupt at ANY time. On an 8-bit Arduino, that can occur in between requesting the two bytes of a 16-bit integer. So this code will have problems...

The poster then goes on to suggest a "safe copy" function that blocks interrupts while reading the value that the interrupt function modifies.

My question is, do I need such function only when I'm reading something that the interrupt could affect, or could the interrupt function crash the code while ANY integer is being read, or calculation is being made, etc....

You only need to disable interrupts while reading (or better copying) a multibyte variable that can be changed in (by) an ISR.

The problem relates to multi byte variables that are changed by an ISR then used outside of the ISR and is easily worked around by copying the variable with interrupts turned off then working with the copy

You can recognize those variables because they are the same ones you have to put the "volatile" keyword on: Any variable changed in an ISR that is also used outside the ISR.

Here is a quick example showing the way to guard the variable from the ISR and making a copy to use in the loop() code.

volatile unsigned long count = 0;

void setup()
{
   Serial.begin(115200);
   attachInterrupt(digitalPinToInterrupt(2), counter, FALLING);
}

void loop()
{
   noInterrupts();  // disable interrupts
   unsigned long countCopy = count; // long enough to copy variable
   interrupts(); // re-enable interrupts

   Serial.print("The count is ");
   Serial.println(countCopy);
   count = 0;
   delay(1000);
}

void counter()
{
  count++;  // multi-byte variable from ISR
}

EDIT: there is a potential problem with this example as pointed out and corrected in post #7 by @johnwasser. Thanks John.

perfect, thanks everyone for the fast and clear replies!

Warning: The ISR could increment 'count' half-way through your code setting 'count' to zero. This is probably not a problem in this case since I assume that the unsigned long is set in byte order and an Arduino UNO is little-endian (LSB first). That would mean the increment would be from 0 to 1.

On a big-endian processor, you might have a count of 0x0007FFFF and get an interrupt between setting the top two bytes to 0 and setting the bottom two bytes to 0:

0x0007FFFF -> 0x0007FFFF // Clear first byte
0x0007FFFF -> 0x0000FFFF // Clear second byte
Interrupt: 
0x0000FFFF -> 0x00010000 // increment
0x00010000 -> 0x00010000 // Clear third byte
0x00010000 -> 0x00010000 // Clear fourth byte

So now 'count' is 0x00010000 after being set to zero.

To prevent the issue, clear 'count' after copying it and before enabling interrupts.

void loop()
{
   noInterrupts();  // disable interrupts
   unsigned long countCopy = count; // long enough to copy variable
   count = 0;
   interrupts(); // re-enable interrupts

   Serial.print("The count is ");
   Serial.println(countCopy);
   delay(1000);
}

I agree. Thanks for the correction.

I think that when a byte variable is incremented or decremented in an ISR (e.g. count++) the arithmetic operations are not atomic, and the "safe copy' protected access method should be used in all cases.

https://stackoverflow.com/questions/36381932/c-decrementing-an-element-of-a-single-byte-volatile-array-is-not-atomic-why

That is true when the byte variable is read AND WRITTEN in both places. The reported problem was an increment in the ISR was not staying in effect when it happened in the middle of a decrement outside the ISR. If only one or the other modified the byte, but not both, then no discrepancy would have occurred.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.