ATOMIC_BLOCK in avr-libc
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.
Related Posts
- When Measurement Becomes the Goal: From Window Tax to PR Counts I once wrote a small tool to count how many PRs I had contributed in a quarter, how many reviews I had left, and how many tickets I had closed, hoping to use the numbers to prove my output to my manager. My manager simply said performance is not judged by output alone. Only years later did I understand—when measurement becomes the goal, it is no longer a good measure. From Britain’s window tax and the Hanoi rat bounty to evaluating developers by PR count today, the mechanism is exactly the same.
- Using Cloudflare Images for Image Storage and Transformation Putting an image on a web page is the easiest thing in frontend. But doing it properly — resizing, generating every format, holding up under traffic — turns into a whole solution of its own. I ended up handing all of it to Cloudflare Images and keeping just one original.
- Stop Using Access Keys Already Access Keys are an easily overlooked security risk on AWS. Use OIDC with IAM Roles so GitHub Actions can securely access AWS resources without any secrets.
- Database Primary Keys: AUTO_INCREMENT, UUID, and UUIDv7 Backend developers often have to decide on a primary key: auto increment or UUID? What about collisions? How much faster is UUIDv7 compared with created_at + index? After benchmarking 20 million rows and looking at the design trade-offs, this post gives you the answer.