Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ decrementing an element of a single-byte (volatile) array is not atomic! WHY? (Also: how do I force atomicity in Atmel AVR MCUs/Arduino)

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...


Read the "Atomic access" section here: http://www.gammon.com.au/interrupts. This is a great source.


Related (answer for STM32 MCUs):

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?.

like image 458
Gabriel Staples Avatar asked Sep 15 '25 00:09

Gabriel Staples


1 Answers

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:

  • load the value into a register
  • decrement the register
  • store the value back

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.

like image 54
Michael Burr Avatar answered Sep 17 '25 14:09

Michael Burr