at the moment I am learning about using the "ATOMIC"-macros
I found this short thread which has a demo-code
I took this demo-code as a base and added comments.
Now I would like to add that - depending on the macro beeing active (= uncommented)
or
macro beeing commented different things were printed to the serial monitor
Though I am not very familiar with the #define-thing
So the ideal solution would be to only have to modify the line
// ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
and no other lines of code to set the variable atomicIsActive set to true or false
Then the printing to the serial monitor would show either the == true or the else-message
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
here is the complete sketch
// Demo-Code to show what happens to the value of 16 bit variable
// when modified in an interrupt service-routine (ISR)
// based on a demo-code written by user jraskell
// https://forum.arduino.cc/t/demonstration-atomic-access-and-interrupt-routines/73135
// the library TimerOne.h can be installed from the library-manager or
// from // https://github.com/PaulStoffregen/TimerOne
#include "TimerOne.h"
#include <util/atomic.h>
volatile unsigned int test = 0xFF00; // defining as volatile is not sufficient
unsigned long myCounter = 0;
unsigned long mylastCnt = 0;
boolean atomicIsActive = false;
void myInterrupt() {
//Interrupt just toggles the value of test. Either 0x00FF or 0xFF00
static bool alt = true;
if(alt)
test = 0x00FF;
else
test = 0xFF00;
alt = !alt;
}
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
Timer1.initialize(10000); // Call interrupt every 10ms = 10000 µseconds
Timer1.attachInterrupt(myInterrupt);
Timer1.start();
}
void loop() {
unsigned int local;
myCounter++;
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
// ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
{
local = test;
}
//Test value of local. Should only ever be 0x00FF or 0xFF00
if(!(local == 0xFF00 || local == 0x00FF)) {
//local value incorrect due to NON-atomic access of test
// the printing shows how fast the loop is iterating and how often
// variable local contains a non-valid value
Serial.print(myCounter - mylastCnt);
Serial.print(" ");
Serial.println(local, HEX);
mylastCnt = myCounter;
}
}
What I would like to achieve is
depending on the line
beeing commented out
// ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
or
beeing active = no "//" on the left
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
that if commented out the serial printing will be
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
.
.
or in case
that this line is active like shown below
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
the serial printing will be
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
The user shall be able to just add / remove the "//" to this single line
// ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // if this line is UN-commented ATOMIC_BLOCK is ACTIVE
and then the serial printing will be different explaining what the user can expect to see in the serial monitor in each case
// Demo-Code to show what happens to the value of 16 bit variable
// when modified in an interrupt service-routine (ISR)
// based on a demo-code written by user jraskell
// https://forum.arduino.cc/t/demonstration-atomic-access-and-interrupt-routines/73135
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
#define ATOMIC_ACTIVE
// the library TimerOne.h can be installed from the library-manager or
// from // https://github.com/PaulStoffregen/TimerOne
#include "TimerOne.h"
#include <util/atomic.h>
volatile unsigned int test = 0xFF00; // defining as volatile is not sufficient
unsigned long myCounter = 0;
unsigned long mylastCnt = 0;
void myInterrupt() {
//Interrupt just toggles the value of test. Either 0x00FF or 0xFF00
static bool alt = true;
if(alt)
test = 0x00FF;
else
test = 0xFF00;
alt = !alt;
}
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
#if defined(ATOMIC_ACTIVE)
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
#else
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
#endif
Timer1.initialize(10000); // Call interrupt every 10ms = 10000 µseconds
Timer1.attachInterrupt(myInterrupt);
Timer1.start();
}
void loop() {
unsigned int local;
myCounter++;
#if defined(ATOMIC_ACTIVE)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#endif
{
local = test;
}
//Test value of local. Should only ever be 0x00FF or 0xFF00
if(!(local == 0xFF00 || local == 0x00FF)) {
//local value incorrect due to NON-atomic access of test
// the printing shows how fast the loop is iterating and how often
// variable local contains a non-valid value
Serial.print(myCounter - mylastCnt);
Serial.print(" ");
Serial.println(local, HEX);
mylastCnt = myCounter;
}
}
Yes this is the basic principle how these #if def's work
And it is the same principle as user @b707 posted applied to the demo-code that I have posted.
It might be that this solution of using two ifdefs is the one with the lowest effort possible
and
reducing the effort even more is simply not possible.
This is my initial question:
can the line
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
which is a macro
sometimes this line is part of the compiled code
and
some other times it is not part of the compiled code
simply by commenting out like this
no macros are part of compiled code. but they can affect compiled code.
in your code above, with the if condition either true/false, the compiler optimizes out the case, that is false. in other words, there are only 3 lines in the compiled code
A long question, but the answer is quite short "no".
Since comments are stripped before the compiler parses the code, comments can not be used to modify the compiled code. The compiler can't make a decision based on something not being declared or called.
However, the preprocessor can make decisions like that. I would suggest an example, but you will probably reject it so I don't want to spend time on it.
It's a macro invocation to be specific. The macro is expanded, and then result of the expansion is passed to the compiler.
The line
// ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
however, is not a macro expansion or code, it's a comment.
#ifdef tests if a macro has been defined. There is no directive to test if a macro has been expanded.
The normal way to achieve what you want is define a wrapper macro, but you seem to have specifically ruled out any option like that :
You even put it in RED BOLD CAPS, so I guess that means it is an absolute deal breaker for you. If you could make your requirements less restrictive, I could suggest a solution.
// Demo-Code to show what happens to the value of 16 bit variable
// when modified in an interrupt service-routine (ISR)
// based on a demo-code written by user jraskell
// https://forum.arduino.cc/t/demonstration-atomic-access-and-interrupt-routines/73135
// the library TimerOne.h can be installed from the library-manager or
// from // https://github.com/PaulStoffregen/TimerOne
#include "TimerOne.h"
#include <util/atomic.h>
volatile unsigned int test = 0xFF00; // defining as volatile is not sufficient
unsigned long myCounter = 0;
unsigned long mylastCnt = 0;
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
#define USE_ATOMIC
#ifdef USE_ATOMIC
#define MY_ATOMIC_BLOCK(x) ATOMIC_BLOCK(x)
boolean atomicIsActive = true;
#else
#define MY_ATOMIC_BLOCK(x) if(1)
boolean atomicIsActive = false;
#endif
void myInterrupt() {
//Interrupt just toggles the value of test. Either 0x00FF or 0xFF00
static bool alt = true;
if(alt)
test = 0x00FF;
else
test = 0xFF00;
alt = !alt;
}
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
Timer1.initialize(10000); // Call interrupt every 10ms = 10000 µseconds
Timer1.attachInterrupt(myInterrupt);
Timer1.start();
}
void loop()
{
unsigned int local;
myCounter++;
MY_ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
local = test;
}
//Test value of local. Should only ever be 0x00FF or 0xFF00
if(!(local == 0xFF00 || local == 0x00FF)) {
//local value incorrect due to NON-atomic access of test
// the printing shows how fast the loop is iterating and how often
// variable local contains a non-valid value
Serial.print(myCounter - mylastCnt);
Serial.print(" ");
Serial.println(local, HEX);
mylastCnt = myCounter;
while (1);
}
}
the output after the pre-processor can be seen using the gcc -E option
with #define USE_ATOMIC
# 0 "bob.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "bob.cpp"
volatile unsigned int test = 0xFF00;
boolean atomicIsActive = true;
void setup() {
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
}
void loop()
{
unsigned int local;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
local = test;
}
}
with // #define USE_ATOMIC -- corrected
# 0 "bob.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "bob.cpp"
volatile unsigned int test = 0xFF00;
# 16 "bob.cpp"
boolean atomicIsActive = false;
void setup() {
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
}
void loop()
{
unsigned int local;
if(1)
{
local = test;
}
}
this is great. I have not yet fully understood how it works together to use the mutliple #ifdef etc.
But it works ehm works not
when the line
#define USE_ATOMIC
the serial output is as expected
13:03:57.284 -> Setup-Start
13:03:57.284 -> ATOMIC_RESTORESTATE is active
13:03:57.284 -> you should NOT see any serial printing
13:03:57.284 -> because variables are always valid through ATOMIC_RESTORESTATE
if the line is commented out like this
//#define USE_ATOMIC
the serial output is ..... not as expected
what I really get is
13:10:14.124 -> Setup-Start
13:10:14.124 -> ATOMIC_RESTORESTATE is DE-activated
13:10:14.124 -> you should see serial printing
13:10:14.124 -> each time the variable has a non-valid value
13:10:14.124 -> 8392 0
and what the code from user @b707 is giving is
with
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
//#define ATOMIC_ACTIVE
13:18:01.562 -> Setup-Start
13:18:01.562 -> ATOMIC_RESTORESTATE is DE-activated
13:18:01.562 -> you should see serial printing
13:18:01.562 -> each time the variable has a non-valid value
13:18:01.751 -> 94154 0
13:18:01.845 -> 35724 0
13:18:02.033 -> 98520 0
13:18:02.223 -> 75904 FFFF
13:18:02.269 -> 22366 0
13:18:02.317 -> 26754 0
13:18:02.317 -> 4135 FFFF
13:18:02.507 -> 67247 0
13:18:02.744 -> 116465 0
13:18:02.932 -> 71583 0
13:18:02.932 -> 4137 FFFF
13:18:02.980 -> 17717 FFFF
13:18:02.980 -> 8720 FFFF
13:18:03.074 -> 31364 0
So something is different with the code and its macro that user @bobcousins posted
But as this kind of macro-using is very new to me I have no idea what to change to make it work as expected.
For convenience the code from user @bobcousins
where just this line
is changed through commenting out
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
//#define USE_ATOMIC
bobcousin's code
// Demo-Code to show what happens to the value of 16 bit variable
// when modified in an interrupt service-routine (ISR)
// based on a demo-code written by user jraskell
// https://forum.arduino.cc/t/demonstration-atomic-access-and-interrupt-routines/73135
// the library TimerOne.h can be installed from the library-manager or
// from // https://github.com/PaulStoffregen/TimerOne
#include "TimerOne.h"
#include <util/atomic.h>
volatile unsigned int test = 0xFF00; // defining as volatile is not sufficient
unsigned long myCounter = 0;
unsigned long mylastCnt = 0;
// if this line below is UN-commented ATOMIC_BLOCK is ACTIVE
//#define USE_ATOMIC
#ifdef USE_ATOMIC
#define MY_ATOMIC_BLOCK(x) ATOMIC_BLOCK(x)
boolean atomicIsActive = true;
#else
#define MY_ATOMIC_BLOCK(x) if(1)
boolean atomicIsActive = false;
#endif
void myInterrupt() {
//Interrupt just toggles the value of test. Either 0x00FF or 0xFF00
static bool alt = true;
if(alt)
test = 0x00FF;
else
test = 0xFF00;
alt = !alt;
}
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
if (atomicIsActive) {
Serial.println("ATOMIC_RESTORESTATE is active");
Serial.println("you should NOT see any serial printing");
Serial.println("because variables are always valid through ATOMIC_RESTORESTATE");
}
else {
Serial.println("ATOMIC_RESTORESTATE is DE-activated");
Serial.println("you should see serial printing");
Serial.println("each time the variable has a non-valid value");
}
Timer1.initialize(10000); // Call interrupt every 10ms = 10000 µseconds
Timer1.attachInterrupt(myInterrupt);
Timer1.start();
}
void loop()
{
unsigned int local;
myCounter++;
#ifdef USE_ATOMIC
MY_ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#endif
{
local = test;
}
//Test value of local. Should only ever be 0x00FF or 0xFF00
if(!(local == 0xFF00 || local == 0x00FF)) {
//local value incorrect due to NON-atomic access of test
// the printing shows how fast the loop is iterating and how often
// variable local contains a non-valid value
Serial.print(myCounter - mylastCnt);
Serial.print(" ");
Serial.println(local, HEX);
mylastCnt = myCounter;
while (1);
}
}
The first line gives ATOMIC_BLOCK() a second name: MY_ATOMIC_BLOCK().
The x in brackets tells the preprocessor to copy the parameter given in a call to MY_ATOMIC_BLOCK(whatEverIsWrittenHere) to the call of ATOMIC_BLOCK(whatEverIsWrittenHere).
The second line declares a boolean variable with the state "true" (which is used in setup() to print the appropriate message).
If USE_ATOMIC is not defined the following two lines are used by the preprocessor and compiler:
While it's interesting and a good mental exercise to manually expand those macros to see how they work, it turns out that (IMO) they have little practical value when programming in a single-core, single-thread environment. For example, what this code block does:
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
//
// Do something atomic here
//
}
Can be accomplished (with the exception of one corner case) using:
noInterrupts();
//
// Do something atomic here
//
interrupts();
That one corner case is when you want to call the same function from both regular and ISR code. In that case, the first code above has the advantage that it returns the the interrupt control register to its previous state while the second code unconditionally reenables interrupts. That unconditional reenabling is not desirable if the code is running within interrupt context as you generally want to keep interrupts disabled during the entire ISR and have them restored on exit.
As an aside, note that such code (while not technically reentrant) may produce confusing results if static or global variables are involved.