I just lost days, literally, ~25 hours of work, due to trying to debug my code over something simple that I didn't know while making a fireshooting hexacopter BattleBot (see it here and on my personal website here).
It turns out decrementing an element of a single-byte array in C++, on an AVR ATmega328 8-bit microcontroller (Arduino) is not an atomic operation, and requires atomic access guards (namely, turning off interrupts). Why is this??? Also, what are all of the C techniques to ensure atomic access to variables on an Atmel AVR microcontroller?
Here's a dumbed down version of what I did:
// Global variables:
const uint8_t NUM_INPUT_PORTS = 3;
volatile uint8_t numElementsInBuf[NUM_INPUT_PORTS];
ISR(PCINT0_vect) // External pin change interrupt service routine on input port 0
{
// Do stuff here
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
numElementsInBuf[i]++;
}
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
// Do stuff here
numElementsInBuf[i]--; // <-- THIS CAUSES ERRORS!!!!! THE COUNTER GETS CORRUPTED.
}
}
Here's the version of loop that's fine:
loop()
{
for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
{
// Do stuff here
noInterrupts(); // Globally disable interrupts
numElementsInBuf[i]--; // Now it's OK...30 hours of debugging....
interrupts(); // Globally re-enable interrupts
}
}
Notice the "atomic access guards", i.e., disabling interrupts before decrementing, then re-enabling them after.
Since I was dealing with a single byte here, I didn't know I'd need atomic access guards. Why do I need them for this case? Is this typical behavior? I know I'd need them if this was an array of 2-byte values, but why for 1-byte values???? Normally for 1-byte values atomic access guards are not required here...
So we know that reading from or writing to any single-byte variable on AVR 8-bit MCUs is an atomic operation, but what about STM32 32-bit MCUs? Which variables have automatic atomic reads and writes on STM32? The answer is here: Which variable types/sizes are atomic on STM32 microcontrollers?.
The ATmega328 data sheet indicates that:
The ALU supports arithmetic and logic operations between registers or between a constant and a register
It doesn't mention the ALU being able to operate directly on memory locations. So in order to decrement a value, this means that the processor must perform several operations:
Therefore the decrement operation is not atomic unless you do something special to make it atomic, such as disable interrupts. This kind of read/modify/write requirement is probably more common than not for updating memory.
The details of how an operation can be made atomic are platform dependent. Newer versions of the C and C++ standards have explicit support for atomic operations; I have no idea if a toolchain for the ATmega supports these newer standards.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With