More compact way for a special macro with an integrated timing-function

Hi everybody,

some time ago I wrote a macro for debugging purposes that makes it more efficient to add code that does debugoutput

This is the definition of the macro including a description how it works

#define dbg(myFixedText, variableName) \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); 
// usage dbg("fixed Text", variableName)
// example printing name and content of a variable called "myCounter"
// dbg("Demo-Text",myCounter);
// serialoutput will be '"Demo-Text" myCounter=1'
// which means this macro does three things
// 1. print the fixed text 
// 2. print the NAME of the variable
// 3. print the CONTENT of the variable 
// all in one line

As long as the code repeats the call for dbg(...) only at a low frequency this works well.

Now I'm thinking about an enhancement that allows to use a similar debug-output but with an additional requirement:
even if the call for dbgInterval(....) is done thousansds of times per second
do a print only once per second or a user-definable interval
Here is a democode that shows this through using a timing function

#define dbg(myFixedText, variableName) \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); 

// non-blocking timing-function
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

// millis()-timing-variables MUST be of type unsigned long
unsigned long MyTestTimer = 0; 
boolean buttonHasBeenPressed = false;

unsigned long myDebugIntervalTimer = 0;

const byte ledPin    = 13;
const byte buttonPin =  3;

void setup() {
  Serial.begin(115200); 
  Serial.print( F("\n Setup-Start  \n") );

  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  buttonHasBeenPressed = false;
}

void loop() {
  int buttonState;
  buttonState = digitalRead(buttonPin);

  // only print once per second even if loop repeats very fast
  if (TimePeriodIsOver (myDebugIntervalTimer,1000) ) {
    dbg("0:looping",buttonState);    
    dbg("0:",buttonHasBeenPressed);
  }

  // if-condition only true once 
  // because boolean flag is changed to "true"
  if ( !buttonHasBeenPressed && (buttonState == LOW) ) {
    digitalWrite(ledPin, HIGH);

    Serial.print( F("Button pressed! \n") );
    dbg("1:Button pressed",buttonState);
    buttonHasBeenPressed = true;
    // initialise timervariable with actual time
    MyTestTimer = millis(); 
  }    

  // if-condition only true once 
  // because boolean flag is changed to "false"
  if ( buttonHasBeenPressed && TimePeriodIsOver (MyTestTimer,5000) ) {
    digitalWrite(ledPin, LOW);
    buttonHasBeenPressed = false; 
    dbg("Time is over",digitalRead(ledPin));
  }
}  

Now the ideal thing would be to reduce the (macro) code that must be written as short as this

dbgi("fixed text",variablename,printInterval);

this has some implications:

  • for each new call of the macro a new timing-variable must be created
  • an if-condition must be added that checks if printInterval is over

I guess the timing-variable must be static to enable a local declaring at the place where the macro sits in the code.

I would have to use a MACRO-countervariable that would be incremented by one for each call of
the dbgI-macro to create a new timing-variable like

static unsigned long dbgTimer1

and with the next call of dbgI()

static unsigned long dbgTimer2

...

is there a possability to have a MACRO-variable and to increment the value of this MACRO-variable at "compile"-time?

best regards Stefan

#define dbg(myFixedText, variableName) \
  do { \
    static unsigned long dbgTimer; \
    if (TimePeriodIsOver (dbgTimer,1000) ) { \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); \
    } \
  } while (false)
1 Like

An interesting question. Beyond my knowledge of macros. Buy I have an idea. In the FastLED library, there is a macro called EVERY_N_MILLISECONDS. I wonder if it does some of the things you asked about. Maybe dig that out and try to figure out how it works?

Ah, yes. A loop that executes only once with a local variable inside it, so the same variable name can be used each time the macro is used. Good thinking @Coding_Badly !

1 Like

very clever idea. I tested it with two call right one after the other like this

  dbgi("0:looping",buttonState,200);    
  dbgi("0:",buttonHasBeenPressed,5000);

it delays but both are delayed by 200 milliseconds

#define dbg(myFixedText, variableName) \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); 


#define dbgi(myFixedText, variableName, intervalTime) \
  do { \
    static unsigned long dbgTimer; \
    if (TimePeriodIsOver (dbgTimer,#intervalTime) ) { \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); \
    } \
  } while (false)

  
// non-blocking timing-function
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

// millis()-timing-variables MUST be of type unsigned long
unsigned long MyTestTimer = 0; 
boolean buttonHasBeenPressed = false;

unsigned long myDebugIntervalTimer = 0;

const byte ledPin    = 13;
const byte buttonPin =  3;

void setup() {
  Serial.begin(115200); 
  Serial.print( F("\n Setup-Start  \n") );

  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  buttonHasBeenPressed = false;
}

void loop() {
  int buttonState;
  buttonState = digitalRead(buttonPin);

  dbgi("0:looping",buttonState,200);    
  dbgi("0:",buttonHasBeenPressed,5000);

/*
  // only print once per second even if loop repeats very fast
  if (TimePeriodIsOver (myDebugIntervalTimer,1000) ) {
    dbg("0:looping",buttonState);    
    dbg("0:",buttonHasBeenPressed);
  }
*/

  // if-condition only true once 
  // because boolean flag is changed to "true"
  if ( !buttonHasBeenPressed && (buttonState == LOW) ) {
    digitalWrite(ledPin, HIGH);

    Serial.print( F("Button pressed! \n") );
    dbg("1:Button pressed",buttonState);
    buttonHasBeenPressed = true;
    // initialise timervariable with actual time
    MyTestTimer = millis(); 
  }    

  // if-condition only true once 
  // because boolean flag is changed to "false"
  if ( buttonHasBeenPressed && TimePeriodIsOver (MyTestTimer,5000) ) {
    digitalWrite(ledPin, LOW);
    buttonHasBeenPressed = false; 
    dbg("Time is over",digitalRead(ledPin));
  }
}  

could this be due to compiler-optimisation removing the do while-loop?

And if yes where can I disable compiler-optimisation?

best regards Stefan

What's with that pound sign?

Enabling all warnings will help you find mistakes like that.

OK I got it working the way I want it to

one macro which will printout everytime without any interval and a macro that has a third parameter which is the interval in milliseconds

I replaced the function TimePeriodIsOver() with the basic standard function millis() and standard if-condition. So the macro works independant of my user-defined function TimePeriodIsOver()

#define dbg(myFixedText, variableName) \
        Serial.print( F(#myFixedText " "  #variableName"=") ); \
        Serial.println(variableName); 

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);

here the demo-code that shows how it works see post #2:

best regards Stefan

Some additional explanation how it works:

using the #define "statement" to make the compiler "write" code
#define can be used to make the compiler replace text that describes something with (almost) anything else

easy example
let's assume IO-pin number 4 shall be set to work as output
the command for defining an IO-pin to be configured as output is

pinMode(4,OUTPUT);
the number "4" says nothing about the purpose of this IO-pin

let's assume the IO-pin shall switch On/Off a buzzer

you would have to add a comment to explain
pinMode(4,OUTPUT); // IO-pin 4 is buzzer

the compiler needs the number. To make the code easier to read and understand
#define BuzzerPin 4
So the command looks like this
pinMode(BuzzerPin,OUTPUT);

the descriptive word "BuzzerPin" gets replaced through the compiler by a "4"
so what the compiler compiles is still
pinMode(4,OUTPUT);

the #define can do much more than just replace a word by a single number
it can replace multiple words like

#define prHello Serial.println("Hello World!");

identifier-part: prHello

replacement-part: Serial.println("Hello World!");

The mechanism behind it is the compiler acts like if you would

deleting the identifier-part:

and then

typing the replacement-part:

into your *.ino-textfile which is your sourcecode.

This is done right after clicking the compile or the upload-button in the IDE.

this means the macros act on a completely different level than the program-code
They act on the source-code-level

If your program is compiled and uploaded to the microcontroller things are "acting" on the program-level.

an expression like #myFixedText with a leading double-cross "#"
acts like a text-variable

#define dbg(myFixedText, variableName) \

parenthesis open myFixedText = use myFixedText as a macro-variable

a call of the macro looks like this:

dbg("entering case 1", 0);

This means all characters right behind the opening parenthese and the comma get stored into the macro-variable myFixedText

Serial.print( F(#myFixedText does

replace the character-sequence hold by the macro-variable myFixedText
so after this action the source-code looks like this

Serial.print( F("entering case 1"

The macro goes on
Serial.print( F(#myFixedText " " #variableName"=") ); \

which means after finishing the whole line your source-code looks is "transformed from

macro-call:

dbg("entering case 1", 0);

to

Serial.print( F("entering case 1  0=") );

macro-variable "variableName" holds the "0"

this part of the macro-code
Serial.print( F(#myFixedText " " #variableName"=") ); \

is replaced by the "0"
the rest
Serial.print( F(#myFixedText " " #variableName"**=") ); **

are again just characters
so at the end the source-code-line looks like this

Serial.print( F("entering case 1  0=") );

in principle the same thing happends with the rest of the macro

Serial.println(variableName);

With a modified example for the macro-call

dbg("damm! show me the content of variable",myStep);

results in

Serial.print(  F("damm! show me the content of variable myStep=") );
Serial.println(myStep);
dbg("what the heck is value of variable",myCounter);

results in

Serial.print(  F("what the heck is value of variable myCounter=") );
Serial.println(myCounter);

dbgi has some more code that is genious! It is a brlliant idea proposed by user
Coding_Badly

So his username is self-ironic

a do - while -loop with a fixed condition "false"

At first view this seems to be nonsense a do-while-loop where the condition always evaluates to "false" means
run down the code only once

so why not leave away the d-while-loop???

The purpose of adding the while-loop is it makes the variable intervalStartTime
local to the while-loop

This has the effect that with mutliples calls of macro dbgi
the always the same named variable intervalStartTime are each local and don't disturb each other.

This means the macro dbgi can be called thousands of times where each call has its own set of variables.

1 Like

It also makes the macro if-statement safe. The {insert offensive adjective} C designers thought an if without curly braces was a good idea. It's not. (ditto for while and for)

Given the macro in post #1 the output of this is going to be unexpected...

  if (waiting_for_input) dbg("still waiting", time_elapsed);

The do{}while version works as expected.

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