ATOMIC_BLOCK in avr-libc
# Dev NoteIn 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
- Stop Using Access Keys AlreadyAccess 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 UUIDv7Backend 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.
- Sharing My Experience with ZeaburIndependent developers often choose platforms like Vercel for deploying their services. However, when more advanced requirements arise, such as database connections, Vercel can become less convenient. Additionally, the pricing of typical cloud service providers can be quite expensive for solo developers. In this article, I’ll share some insights on using Zeabur and highly recommend it to everyone!
- Keyboard Enthusiast's Guide - Firmware EditionThis article is part of the IT 2023 Ironman Competition: A Beginner's Guide to Keyboards - Firmware Edition.