This answer covers: Which Arduinos support ATOMIC_BLOCK? And how can I duplicate this concept in C with __attribute__((__cleanup__(func_to_call_when_x_exits_scope)))
and in C++ with class constructors and destructors?.
Which Arduino's support the ATOMIC_BLOCK
macros?
Which Arduinos support ATOMIC_BLOCK?
Only AVR (ATmel AVR architecture) microcontrollers support the ATOMIC_BLOCK
macros, because these macros are part of the avr-libc library, which, you guessed it!, supports only AVR microcontrollers.
AVR-based Arduinos include those based on the ATmega328 microcontroller (Arduino Uno, Nano, Mini, etc.), ATmega2560 (Arduino Mega 2560), ATmega32U4 (Arduino Leonardo, Pro Micro), etc.
How are the ATOMIC_BLOCK
macros implemented in C with the gcc compiler, and where can I see their source code?
avr-libc is available for download here: https://www.nongnu.org/avr-libc/ --> "source code and documentation" link under the "Downloads" section. Ex: here is the tarball for v 2.0.0: http://download.savannah.gnu.org/releases/avr-libc/avr-libc-2.0.0.tar.bz2.
The entire source code for the ATOMIC_BLOCK
macros is contained in avr-libc-2.0.0/include/util/atomic.h.
ATOMIC_BLOCK
is an ingenious macro written in C and relying upon the gcc "cleanup" attribute C extension which can call a function you define when a variable goes out of scope. Essentially, this is identical to a C++ class destructor which gets called when an instance of a class in C++ goes out of scope. The gcc "cleanup" attribute extension is a way to get C++-like behavior in C.
Here is the official gcc documentation: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html. It states:
cleanup (cleanup_function)
The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.
If -fexceptions is enabled, then cleanup_function is run during the stack unwinding that happens during the processing of the exception. Note that the cleanup attribute does not allow the exception to be caught, only to perform an action. It is undefined what happens if cleanup_function does not return normally.
Its format is like this:
int x __attribute__((__cleanup__(func_to_call_when_x_exits_scope))) = 0;
You use it like this:
RUN A REAL EXAMPLE OF THIS CODE IN C ONLINE HERE.
static __inline__ void cleanup_my_byte(uint8_t *my_byte_p)
{
Serial.print("my_byte is going out of scope! Its value is ");
Serial.println(*my_byte_p);
}
void setup()
{
uint8_t my_byte __attribute__((__cleanup__(cleanup_my_byte))) = 7;
} // The following function call occurs automatically here as `my_byte`
// exits its scope!:
// `cleanup_my_byte(&my_byte);`
Studying the avr-libc-2.0.0/include/util/atomic.h file referenced above, you can see how the ATOMIC_RESTORESTATE
macro is implemented, using this attribute. I've put just the relevant pieces from that file here:
#include <avr/io.h>
#include <avr/interrupt.h>
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
__ToDo ; __ToDo = 0 )
static __inline__ uint8_t __iCliRetVal(void)
{
cli();
return 1;
}
#define ATOMIC_RESTORESTATE uint8_t sreg_save \
__attribute__((__cleanup__(__iRestore))) = SREG
static __inline__ void __iRestore(const uint8_t *__s)
{
SREG = *__s;
__asm__ volatile ("" ::: "memory");
}
Note that cli()
clears (turns off) interrupts, and sei()
sets (turns on) interrupts.
So, you can see that ATOMIC_RESTORE_STATE
gets replaced by uint8_t sreg_save __attribute__((__cleanup__(__iRestore))) = SREG
, which backs up the global interrupt state from the AVR SREG
8-bit register into sreg_save
, then calls this function call when sreg_save
exits its scope: __iRestore(&sreg_save);
. That function call to __iRestore()
then sets the SREG
register back to where it was, restoring the interrupt state (turning it ON if it was ON before, or OFF if it was OFF before).
The official documentation for ATOMIC_BLOCK
is here: https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html. An example usage is this:
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
my_var_copy = my_var;
}
The ATOMIC_RESTORESTATE
part backs up the SREG
register and sets up the call for __iRestore(&sreg_save);
to occur when sreg_save
exits this block scope, as we've already discussed, and the ATOMIC_BLOCK
part gets converted into this: for ( ATOMIC_RESTORESTATE, __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0 )
. This means that the entire block is now an ingenious for
loop which runs only once! Prior to entering the for
loop block, __iCliRetVal()
is called once, clearing (turning OFF) interrupts, and __ToDo
is set to 1
, or true
, so the for
loop runs at least once, and then at the end of the first run it is set to 0
, or false
, to stop the for
loop after one single run! As we've already gone over, the sreg_save
variable now goes out-of-scope at the closing brace and the call to __iRestore(&sreg_save);
is made, via the gcc extension, to restore the interrupt state! Ingenious!
How could you implement the ATOMIC_BLOCK
functionality in Arduino in C++ (as opposed to avrlibc's gcc C version)?
In C++ (Arduino is C++, so we don't have to be limited to just C plus gcc extensions, as was the constraint in the avrlibc ATOMIC_BLOCK
macros), you can get a similar effect using destructors in structs or classes (or, just use the avrlibc pattern shown above if you like, as that gcc C code is also perfectly valid gcc C++ code!).
Option 1: example to create a usage style similar to C++'s std::lock_guard
This, for example, would implement a form of ATOMIC_BLOCK(ATOMIC_FORCEON) {}
, using a simple class in C++:
It relies on Arduino's built-in functions interrupts()
(same as avrlibc's sei()
function) and noInterrupts()
(same as avrlibc's cli()
function). I wish that Arduino also had built-in saveInterrupts()
and restoreInterrupts()
functions, but unfortunatley they don't. Someone will have to go file a feature request and request them, or implement them themselves and do a Pull Request (PR) on GitHub to try to get them back into the main Arduino branches.
RUN A REAL EXAMPLE OF THIS CODE IN C++17 ONLINE HERE.
#define ATOMIC_BLOCK_FORCEON AtomicBlockForceOn atomicBlockForceOn_
class AtomicBlockForceOn
{
public:
// Constructor: called when the object is created
inline AtomicBlockForceOn()
{
noInterrupts(); // turn interrupts OFF
}
// Destructor: called when the object is destroyed (ex: goes
// out-of-scope)
inline ~AtomicBlockForceOn()
{
interrupts(); // turn interrupts ON
}
};
That's it! Now use it like this:
uint16_t my_var_copy;
{
ATOMIC_BLOCK_FORCEON;
// now do whatever you want here, with interrupts disabled; ex:
my_var_copy = my_var;
} // the `atomicBlockForceOn_` object's destructor is called here
// and interrupts are automatically turned back ON!
Pretty cool!
This, I imagine, is probably exactly how C++'s std::lock_guard
is implemented! Its usage is very similar:
void safe_increment()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
// g_i_mutex is automatically released when lock
// goes out of scope
}
Option 2: example to create a usage style similar to avrlibc's ATOMIC_BLOCK
macros
To obtain a usage style of ATOMIC_BLOCK(ATOMIC_FORCEON) { }
in C++ one can definitely use the gcc C style which avrlibc uses, since that is also valid C++, but let's continue with the C++ class style shown just above instead, with just a few tweaks. We will add a couple more helper macros and a couple more functions to the class to make this work. Here it is, with a usage example shown at the end which is identical to how avrlibc's ATOMIC_BLOCK
can be used! I think this is pretty cool too.
RUN A REAL EXAMPLE OF THIS CODE IN C++17 ONLINE HERE.
#define ATOMIC_BLOCK(type) for(type; type##_OBJECT_NAME.run(); \
type##_OBJECT_NAME.stop())
#define ATOMIC_FORCEON_OBJECT_NAME atomicBlockForceOn_
#define ATOMIC_FORCEON AtomicBlockForceOn ATOMIC_FORCEON_OBJECT_NAME
class AtomicBlockForceOn
{
public:
// Constructor: called when the object is created
inline AtomicBlockForceOn()
{
noInterrupts(); // turn interrupts OFF
}
// Destructor: called when the object is destroyed (ex: goes
// out-of-scope)
inline ~AtomicBlockForceOn()
{
interrupts(); // turn interrupts ON
}
// Can we run? Returns true to run the `for` loop or
// `false` to stop it.
inline bool run()
{
return run_now;
}
// Tell the `for` loop to stop
inline void stop()
{
run_now = false;
}
private:
bool run_now = true;
};
Use it like avrlibc's ATOMIC_BLOCK() { }
macro!
Usage example:
uint16_t my_var_copy;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
// now do whatever you want here, with interrupts disabled; ex:
my_var_copy = my_var;
} // the `ATOMIC_FORCEON_OBJECT_NAME` (`atomicBlockForceOn_`) object's
// destructor is called here and interrupts are automatically
// turned back ON!
Further reading:
- Nick Gammon's excellent "Interrupts" page, which is how I first got started learning about interrupts in 2012~2014 or so: https://gammon.com.au/interrupts.
- Other answer's I've done about interrupts, locks/guards, and things:
- global volatile variable not being updated in ISR
- My own race condition problem: https://stackoverflow.com/questions/36381932/c-decrementing-an-element-of-a-single-byte-volatile-array-is-not-atomic-why
- My own race condition solution, demonstrating 3 (or 4, depending on how you look at it) ways to enforce "atomic access guards" on AVR-based 8-bit Arduinos: https://stackoverflow.com/questions/36381932/c-decrementing-an-element-of-a-single-byte-volatile-array-is-not-atomic-why/39693278#39693278
- What's that weird
type##_OBJECT_NAME
thing I did in the macro anyway? Read more about macro token concatenation using the ##
preprocessor operator in the official gcc C++ user manual here: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html. Note: this also works in C of course.
- gcc
inline
or __inline__
, and static inline
or static __inline__
rules:
https://gcc.gnu.org/onlinedocs/gcc/Inline.html
#ifdef ATOMIC_BLOCK
?#ifndef ATOMIC_BLOCK /* define your own ATOMIC_BLOCK here */ #endif
ATOMIC_BLOCK
. I've added a potential solution. Do you know of any code that saves, disables and restores the global interrupt flag each Arduino architecture?ATOMIC_BLOCK
is not provided by the Arduino core, but by the avr-libc.