How to display only once the information about the change of the state of the sensor in the loop

Hey,
I often encounter a situation when I would like to print a given sensor state only once or display information about it only once, at the moment when it was changed (for example, from HIGH to LOW state).
For example my code can be:

void loop() {
  if (mcp.digitalRead(relay1) == HIGH) {
    Serial.print("mcp.digitalRead(relay1) - HIGH");
    przyciskPilotPrawy1++;
  } else {
    Serial.print("mcp.digitalRead(relay1) - LOW");
    przyciskPilotPrawy0++;
  }

  if (mcp.digitalRead(photocell_2) == HIGH) {
    Serial.print("mcp.digitalRead(photocell_2) - HIGH");
    przyciskPilotPrawy1++;
  } else {
    Serial.print("mcp.digitalRead(photocell_2) - LOW");
    przyciskPilotPrawy0++;
  }
}

And in a normal situation the states relay1 and photocell_2 will be output all the time (multiple times). On the other hand, I would like each state to be written out once, and then to be written out only once when their state changes (for example, from LOW to HIGH).

Do you have maybe any ideas how to do it? I need an inspiration about it and the best method. I have a feeling that I often make things difficult for myself in this regard.

You need to detect when the input becomes HIGH or LOW rather than when it is HIGH or LOW

See the StateChangeDetection example in the IDE

3 Likes

Yes, I know about it, but I thought there was, for example, some modern library that simplifies it.

That was not your original question

Why complicate things with a library when all you have to do is check for a change of state and the current state ?

Put the code in a function, call the function, job done

Here are three macros that do

dbg: print

  • a fixed text
  • the variable-name
  • the variable value

print it every time line of code is executed
.
.
dbgi: print

  • a fixed text
  • the variable-name
  • the variable value

only after a specified interval of milliseconds has passed by
.
.
dbgc print

  • a fixed text
  • the variable-name
  • the variable value

only if the value of the variable has changed

the macro dbgc is limited to integer-variables

use the macro dbgcf for variables of type float

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *

// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  }
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// print only once when value has changed
#define dbgc(myFixedText, variableName) \
  { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }

#define dbgcf(myFixedText, variableName) \
  { \
    static float lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}

unsigned long MyTestTimer1 =  0;                   // Timer-variables MUST be of type unsigned long
unsigned long MyTestTimer2 =  0;                   // Timer-variables MUST be of type unsigned long

const byte    OnBoard_LED  = 13;

long myLong = 0;
float myFloat = -0.1;

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);

  // print value of variable myLong once every 500 milliseconds
  dbgi("dbgi1:", myLong, 500);

  // print value of variable myLong only if value has CHANGED
  // since last time it was printed
  dbgc("dbgc1:", myLong);


  // print value of variable myFloat only if value has CHANGED
  // since last time it was printed
  dbgcf("dbgcf:", myFloat);

  // check if 2000 milliseconds have passed by
  if ( TimePeriodIsOver(MyTestTimer1, 2000) ) {
    // when REALLY 2000 milliseconds have passed by
    myLong++; // increment variable myLong by 1
  }

  // check if 6000 milliseconds have passed by
  if ( TimePeriodIsOver(MyTestTimer2, 6000) ) {
    // when REALLY 6000 milliseconds have passed by
    myFloat += 0.5; // incrfement variable myFloat by 0.5
  }

}

best regards Stefan

Hello misiekd

Try to transfer this pseudo code:

newData=readData();
if (oldData!=newData) 
{
oldData=newData;
// do something on new data read
}

Have a nice day and enjoy coding in C++.

2 Likes

Arduino explains that with the example "State Change Detection"

you have a variable lastState which takes the current state.
On next iteration you check if the current state differs from lastState.

That's all.
All you need is some kind of "lastState" - no library needed for that.

2 Likes

Yes, but if I have for example 20 different sensors and I need to check it, it will be a lot of writing and a lot of code space taken up. The library can simply simplify it, not complicate it.

I used this all the time :slight_smile: But ok, I will try it again if no other and better methods are available.

Thanks but it will not work in my situation because this checks the last value, and the last value using this function will vary due to the use of many different sensors.

whatever expression you put in the dbgc-macro as the second parameter
it gets printed only once per change of this expression

example-expression

mcp.digitalRead(photocell_2)

void loop() {
  if (mcp.digitalRead(relay1) == HIGH) {
    //Serial.print("mcp.digitalRead(relay1) - HIGH");
    // print only once when the value of przyciskPilotPrawy1 has changed
    dbgc("mcp.digitalRead(relay1) - HIGH",przyciskPilotPrawy1);
    przyciskPilotPrawy1++;
  } else {
    //Serial.print("mcp.digitalRead(relay1) - LOW");
    // print only once if value of przyciskPilotPrawy0 has changed
    dbgc("mcp.digitalRead(relay1) - LOW",przyciskPilotPrawy0);    
    przyciskPilotPrawy0++;
  }

  if (mcp.digitalRead(photocell_2) == HIGH) {
    //Serial.print("mcp.digitalRead(photocell_2) - HIGH");
    // print only once if value of przyciskPilotPrawy1 has changed
    dbgc("mcp.digitalRead(photocell_2) - HIGH",przyciskPilotPrawy1);
    przyciskPilotPrawy1++;
  } else {
    //Serial.print("mcp.digitalRead(photocell_2) - LOW");
    // print only once if value of przyciskPilotPrawy0 has chnaged
    dbgc("mcp.digitalRead(photocell_2) - LOW",przyciskPilotPrawy0);
    przyciskPilotPrawy0++;
  }

  // print only once on change of digitalRead
  // print only once on change of digitalRead
  // print only once on change of digitalRead
  // print only once on change of digitalRead
  if (mcp.digitalRead(relay1) == HIGH) {
    //Serial.print("mcp.digitalRead(relay1) - HIGH");
    // print only once when the value of mcp.digitalRead(relay1) has changed
    dbgc("mcp.digitalRead(relay1) - HIGH",mcp.digitalRead(relay1));
    przyciskPilotPrawy1++;
  } else {
    //Serial.print("mcp.digitalRead(relay1) - LOW");
    // print only once if value of mcp.digitalRead(relay1) has changed
    dbgc("mcp.digitalRead(relay1) - LOW",mcp.digitalRead(relay1));    
    przyciskPilotPrawy0++;
  }

  if (mcp.digitalRead(photocell_2) == HIGH) {
    //Serial.print("mcp.digitalRead(photocell_2) - HIGH");
    // print only once if value of mcp.digitalRead(photocell_2) has changed
    dbgc("mcp.digitalRead(photocell_2) - HIGH",mcp.digitalRead(photocell_2));
    przyciskPilotPrawy1++;
  } else {
    //Serial.print("mcp.digitalRead(photocell_2) - LOW");
    // print only once if value of mcp.digitalRead(photocell_2 has chnaged
    dbgc("mcp.digitalRead(photocell_2) - LOW",mcp.digitalRead(photocell_2);
    przyciskPilotPrawy0++;
  }  
}

Send a notice when you have found a other or better solution

1 Like

It is unclear what this means.In your title you have written
How to display only once the information about the change of the state of the sensor in the loop

the (one single) sensor

Still the dbgc-macro works for each call separately and independant

If you need something inbetween you have to post a much more precise description of when a changed value shall be printed and when not
best regards Stefan

1 Like

Not if you use an array of values and a for loop to read the sensors

There is no way around storing the last printed values in the RAM except maybe using an FRAM-chip

1 Like

I read different sensors via loop() loop.

Most people read their sensors using the loop function.

If you want help simplifying your code (to use arrays, for example), post it using code tags. People will be happy to make suggestions.

1 Like

Ok, I know, if I will need any more help, I will write.
At the moment I got the best method from @paulpaulson

1 Like

There is an example in your IDE for that. The IDE Examples are lessons for beginners. In the IDE File->Examples-> and see the subjects with examples.

/*
  State change detection (edge detection)
 	
 Often, you don't need to know the state of a digital input all the time,
 but you just need to know when the input changes from one state to another.
 For example, you want to know when a button goes from OFF to ON.  This is called
 state change detection, or edge detection.
 
 This example shows how to detect when a button or button changes from off to on
 and on to off.
 	
 The circuit:
 * pushbutton attached to pin 2 from +5V
 * 10K resistor attached to pin 2 from ground
 * LED attached from pin 13 to ground (or use the built-in LED on
   most Arduino boards)
 
 created  27 Sep 2005
 modified 30 Aug 2011
 by Tom Igoe

This example code is in the public domain.
 	
 http://arduino.cc/en/Tutorial/ButtonStateChange
 
 */

// this constant won't change:
const int  buttonPin = 2;    // the pin that the pushbutton is attached to
const int ledPin = 13;       // the pin that the LED is attached to

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

void setup() {
  // initialize the button pin as a input:
  pinMode(buttonPin, INPUT);
  // initialize the LED as an output:
  pinMode(ledPin, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read the pushbutton input pin:
  buttonState = digitalRead(buttonPin);

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, increment the counter
    if (buttonState == HIGH) {
      // if the current state is HIGH then the button
      // wend from off to on:
      buttonPushCounter++;
      Serial.println("on");
      Serial.print("number of button pushes:  ");
      Serial.println(buttonPushCounter);
    } 
    else {
      // if the current state is LOW then the button
      // wend from on to off:
      Serial.println("off"); 
    }
  }
  // save the current state as the last state, 
  //for next time through the loop
  lastButtonState = buttonState;

  
  // turns on the LED every four button pushes by 
  // checking the modulo of the button push counter.
  // the modulo function gives you the remainder of 
  // the division of two numbers:
  if (buttonPushCounter % 4 == 0) {
    digitalWrite(ledPin, HIGH);
  } else {
   digitalWrite(ledPin, LOW);
  }
  
}

Somehow yes - somehow no.

This buttonStateChange-example does not show how to use functions

Showing the basic principle of functions as the second or third example and the other examples build on that would be much much better because it shows how to group lines of code into senseful sub-units where each sub-unit does one single thing.

This is a very fundamental question and because of this - as an exception - I allow myself to quote mutliple moderators at once so they all get a heads up about quoting

@PerryBebbington
@pert
@UKHeliBob
@alranel

Who at the arduino team decides what IDE-examples to modify / improve and which IDE-examples shall stay as they are?

best regards Stefan