ATOMIC_BLOCK in avr-libc
In avr-libc, there is a <util/atomic.h>. My initial reaction was that AVR chips are strictly single-core, so why would anyone need atomic operations? Out of curiosity, I took a look at the documentation.
The macros in this header file deal with code blocks that are guaranteed to be excuted Atomically or Non-Atmomically. The term “Atomic” in this context refers to the unability of the respective code to be interrupted.
Even on microcontrollers, various interrupts can be used, which means code execution can be interrupted mid-way. Using atomic operations ensures that the enclosed code block will not be affected by interrupts (by temporarily disabling them).
ATOMIC_BLOCK(ATOMIC_FORCEON) {
// do something critical
}
At first glance, it feels a lot like combining cli() and sei(), but according to the documentation, it seems to do more. The internal implementation looks like this:
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
__ToDo ; __ToDo = 0 )
It takes type as a parameter, with the options being ATOMIC_RESTORESTATE and ATOMIC_FORCEON.
#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG
It looks similar to the effect of sei, but what’s more interesting is the use of __attribute__. In GCC, you can use __attribute__ to specify how variables or functions should be handled. This behavior is defined in the libc implementation. 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
Normally, variables are stored in RAM; however, RAM in microcontrollers is often scarce. Unlike regular computers, microcontroller code is typically pre-written, compiled, and flashed directly into program memory (flash memory). If the codebase is small, there tends to be plenty of flash memory left over. In this case, you can use PROGMEM to store variables in flash memory, thereby reducing RAM usage. When reading them, you can use pgm_read_word to access the variables.
Returning to ATOMIC_BLOCK, when we expand it completely, 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 code’s execution will never be affected by interrupts. Pulling this off with a for loop feels pretty clever—I never would have thought of using it this way.
Related Posts
- When a Measure Becomes a Target: From the Window Tax to Pull Request Counts I once wrote a script to tally how many PRs I contributed in a quarter, how many reviews I left, and how many tickets I closed, hoping to use numbers to prove my output to my manager. My manager simply remarked that performance isn't just about output. Years later, I finally understood—when a measure becomes a target, it ceases to be a good measure. From the British window tax and the Hanoi rat bounty to evaluating developers by PR counts today, the underlying mechanism is exactly the same.
- Using Cloudflare Images for Image Storage and Transformation Putting an image on a webpage is the simplest task in frontend development. But doing it properly—including resizing, generating multiple formats, and withstanding heavy traffic—is actually an entire end-to-end solution. Eventually, I offloaded everything to Cloudflare Images, keeping only a single original image.
- Stop Using AWS Access Keys Access Keys are an easily overlooked security risk in AWS. By pairing OIDC with IAM Roles, GitHub Actions can securely operate AWS resources without storing any secrets.
- Database Primary Keys: AUTO_INCREMENT, UUID, and UUIDv7 Backend developers often face the choice of primary keys: should you use auto-increment or UUID? What about collisions? How does UUIDv7 compare to created_at + index in performance? Here are the design decisions and benchmark results from testing 20 million rows.