avr-libc contains <util/atomic.h>
. My initial reaction was that AVR is a single-core architecture, so why would we need atomic operations? Therefore, I instinctively looked at 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, there are various interrupts that can be used, so the execution of the code may be interrupted. By using atomic operations, we can ensure that the code block enclosed within it is not affected by interrupts (temporarily disabling interrupts).
ATOMIC_BLOCK(ATOMIC_FORCEON) {
// do something critical
}
At first glance, it seems similar to the combination of cli()
and sei()
, but according to the documentation, it does more. The internal implementation is as follows:
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
__ToDo ; __ToDo = 0 )
It can accept type
as a parameter, and the possible values for the parameter are ATOMIC_RESTORESTATE
and ATOMIC_FORCEON
.
#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG
It seems similar to the effect of sei()
, but what's interesting is the use of __attribute__
. In GCC, you can use __attribute__
to determine how variables or functions should be stored. This behavior is defined in the implementation of libc
. For example, in AVR, you can write:
#include <avr/pgmspace.h>
const int my_var[2] PROGMEM = { 1, 2 };
Here, PROGMEM
is actually an __attribute__
:
#ifndef __ATTR_PROGMEM__
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#endif
Usually, variables are stored in memory. However, in microcontrollers, memory is often limited. But unlike regular computers, microcontroller code is typically pre-written and directly burned into program memory (flash memory). If the code is not too large, there may be more available space in flash memory. In such cases, variables can be stored in flash memory using PROGMEM
, thereby reducing the usage of RAM. To read such variables, you can use pgm_read_word
.
Returning to ATOMIC_BLOCK
, when we expand it completely, the code would look 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 is not affected by interrupts. It's quite fascinating to see the use of a for
loop in this way. I never thought it could be applied like this.