ATOMIC_BLOCK in avr-libc

Written byKalanKalan
💡

If you have any questions or feedback, pleasefill out this form

Table of Contents

    This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.

    In avr-libc, there's a file called <util/atomic.h>. My first reaction was: since AVR microcontrollers are single-core, why do we even need atomic operations? This instinctively led me to check the documentation.

    The macros in this header file deal with code blocks that are guaranteed to be executed Atomically or Non-Atomically. The term "Atomic" in this context refers to the inability of the respective code to be interrupted.

    Even in microcontrollers, various interrupts can be utilized, meaning that code execution can be interrupted mid-way. Using atomic operations ensures that the code enclosed within them is not affected by interrupts (temporarily disabling interrupts).

    ATOMIC_BLOCK(ATOMIC_FORCEON) {
      // do something critical
    }

    It feels quite similar to a combination of cli() and sei(), but according to the documentation, it seems to do more than just that. The internal implementation looks like this:

    #define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
                               __ToDo ; __ToDo = 0 )

    It accepts type as a parameter, which can be either ATOMIC_RESTORESTATE or ATOMIC_FORCEON.

    #define ATOMIC_RESTORESTATE uint8_t sreg_save \
        __attribute__((__cleanup__(__iRestore))) = SREG

    This appears to have a similar effect to sei, but what's more interesting is the use of __attribute__. In GCC, you can use __attribute__ to dictate how a variable or function should be preserved, a behavior defined in the implementation of libc. For example, in AVR, you can write it like this:

    #include <avr/pgmspace.h>
    const int my_var[2] PROGMEM = { 1, 2 };

    Here, PROGMEM is essentially an __attribute__:

    #ifndef __ATTR_PROGMEM__
    #define __ATTR_PROGMEM__ __attribute__((__progmem__))
    #endif

    Generally, variables reside in memory, but in microcontrollers, memory is often scarce. Unlike typical computers, where the program code is usually written and compiled directly into program memory (flash memory), if the program code is not extensive, the flash memory can have a significant amount of space left over. In such cases, by using PROGMEM, variables can be stored in flash memory, thereby reducing the usage of RAM. To read these variables, you can use pgm_read_word.

    Returning to ATOMIC_BLOCK, once we expand it fully, the code looks like this:

    for (uint8_t sreg_save __attribute__((__cleanup__(__iRestore))) = 0;  __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0) {
       // do something critical
    }

    This ensures that the execution of the code will not be affected by interrupts. Using a for loop in this way feels quite ingenious; I never thought of applying it like this.

    If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨

    Buy me a coffee