Setting a boolean variable with #ifdefined

Hi everybody,

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;
  }
}

I don't understand - do you want to manage the print depending of the macro or of the boolean variable?
Because there is no point to use them both.

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

is it what you want?

// 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;
  }
}

why not simply


#define ACTIVE

void
setup ()
{
    Serial.begin (9600);

#ifdef ACTIVE
    Serial.println ("ACTIVE");
#else
    Serial.println ("IN-ACTIVE");
#endif
}

void
loop ()
{
}

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

// ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

can this change between commented / uncommented

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

// ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

be used WITHOUT adding another

# ifdefine 

to execute different C++function-calls ?

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.

1 Like

The line

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

is a macro. Something that is handled by the pre-processor
So is there a way with using pre-processor commands like

#ifdef
#ifnotdef
#else

a possability to add some lines of code "A"
or for example
on the #else-case
add some other lines of code "B"

?

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.

1 Like

if a wrapper-macro
is something different than the #define that user @b707 posted in post # 5

please post an example for a wrapper-macro

Ok, here goes :

// 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);
  }
}

not sure what above demonstrates.

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;
  }
}

Looks like it executes the statement

     local = test;

Inside an atomic block, or it doesn't .... depending on whether or not USE_ATOMIC is defined.

In the latter case, the assignment statement will eventually be corrupted by code in the ISR (on an 8-bit processor).

Hi @StefanL38 ,

I give it a try:

  • We assume that the macro ATOMIC_BLOCK() is always defined in a sketch.
  • If this macro is used in loop() or a function called in loop() you want setup() to print the message "...is active".
  • If this macro is not used in loop() or a function called in loop() you want setup() to print the message "...is De-activated".

Is this a correct interpretation of your post #1?

Wow @bobcousins

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);
  }
}

I think it is just this line that makes the difference ...

@bobcousins 's sketch stops here when the first mismatch was found...

That's not related to the macro.

In a nutshell: The macro MY_ATOMIC_BLOCK() resolves in

  • ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    or
  • if(1)

where it is called and defines boolean atomicIsActive either as true or false.

In some more detail
#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

To understand the approach just follow the macro definition above:

  • If USE_ATOMIC is defined the following two lines are used by the preprocessor and compiler:
#define MY_ATOMIC_BLOCK(x) ATOMIC_BLOCK(x)
boolean atomicIsActive = true;
  • 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:

#define MY_ATOMIC_BLOCK(x) if(1)
boolean atomicIsActive = false;
  • The first line defines that whereever MY_ATOMIC_BLOCK(x) is written in the sketch it shall be replaced by "if(1)" by the preprocessor.
  • The x in brackets (the parameter) will be discarded (ignored).
  • The second line declares a boolean variable with the state "false" (which is used in setup() to print the appropriate message).

The outcome after preprocessing is nicely shown in post #14. it is either

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
  {
     local = test;
  }

or

  if(1)
  {
     local = test;
  }

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.

1 Like

Yes, sorry I added that just so I see the output easier.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.