Detect state change when running other tasks

This is code for a ESP 8266 (WeMos D1 mini).

Problem statement - When turn signal of motorcycle has been switched on for a period of time, remind user to switch it off by sounding a buzzer, while performing other functions.

My approach - Since the turn signal goes high/low multiple times when switched on, it wouldn't be possible to take the time the pin goes high(since every time the pin goes low, it will be reset). Also, there might be instances when the turn signal is turned on, turned off and again turned on.

I decided to use a detect change state for this. ie.) First determine the number of times pin goes high/low when turn signal is switched on for a period of time(1 minute). Then write code such that buzzer goes high, when the number of times the pin is high exceeds the above mentioned value.

Now, there are several example codes for detect state change, however, i need to be able to run other tasks while this is occuring.

This other task is the transmission of an ESP Now signal containing an integer based on pin state of 3 pins(Brake and L and R turn signal)

My understanding is I have to combine my knowledge of the Blink w/o delay and the detect state change code to accomplish this.

However, I am new to coding and this seems to go over my head.

In my code, either the esp now takes place or the change detection state work, but not both and I would like both to work.

Any pointers in the right direction and I would be very thankful.

//this is the transmitter
#include <ESP8266WiFi.h>
#include <espnow.h>

uint8_t mac_peer[] = {0xE8, 0x9F, 0x6D, 0x8F, 0xDF, 0x56}; //receiver MAC

const int leftIN = D5;
const int rightIN = D6;
const int brakeIN = D7;
const int buzzerOUT = D3;
const int inBuiltLED = D4;


uint8_t transmit = 0;

int Counter = 0;
int Lsignalstate = 0;
int Rsignalstate = 0;
int lastsignalstate = 0;

int start;

void setup()
{
    WiFi.mode(WIFI_STA);
    if(esp_now_init() != 0)
    {
        return;
    }
    esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
    
    esp_now_add_peer (mac_peer, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
    
    pinMode(leftIN,INPUT_PULLUP);
    pinMode(rightIN,INPUT_PULLUP);
    pinMode(brakeIN,INPUT_PULLUP);
    pinMode(inBuiltLED, OUTPUT);
    pinMode(buzzerOUT, OUTPUT);
}
void loop()
{
    start = millis();
    digitalWrite(inBuiltLED, HIGH);
    if(digitalRead(brakeIN) == LOW)
    {
        transmit = 1;
    }
    else if(digitalRead(leftIN) == LOW && digitalRead(rightIN) == LOW)
    {
        transmit = 2;
    }
    else if(digitalRead(leftIN) == LOW)
    {
        transmit = 3;
    }
    else if(digitalRead(rightIN) == LOW)
    {
        transmit = 4;
    }
    if (transmit == 1 || transmit == 2 || transmit == 3 || transmit ==4)
    {
        esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
        delay(200);
        transmit = 0;
        esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
    }
    

while (millis() - start <60000)
{
Lsignalstate = digitalread(leftIN);
    Rsignalstate = digitalread(rightIN);

if (Lsignalstate != lastsignalstate || Rsignalstate != lastsignalstate )
{
if (Lsignalstate == LOW || Rsignalstate == LOW)
{
Counter ++;
}
delay(50);
}
if (Counter >=150) /*number of times pin goes high in 1 minute. Arrived by previous program running pin state change detection*/
{
digitalWrite(buzzerOUT, LOW);
Counter = 0;
}

if (Counter <150)
{
Counter = 0;
}
}
}

Thank you for the help.

If your system supports tasks then create a task for each independent job. In each task wait for an event so that other tasks can run.

Hi @alphamike_1612

you are another victim of the very poor explanations that this famous notorious blink without understand-example code.

There is a fundamental difference between coding timing with delay() which is blocking
and coding non-blocking timing

This difference is:
using delay() means linear-code-execution

non-blocking timing means:
global circular-code-execution

global circular in the sence of the whole void-loop shall run over and over again
looping = run down jump back to the top
run down jump back to the top
run down jump back to the top
run down jump back to the top
and doing this

  • fast
  • executing each line again and again and again
  • repeatedly check how much time has passed by
    and only in case a certain amount of time has passed by execute timed actions from time to time

In your code you inserted a while-loop that is blocking again

while (millis() - start <60000)

which means execute only the code inside the while-loop until the condition
millis() - start < 60000 becomes false
this means the other code gets not executed.

to make both parts of the code execute all the time
you code

if (a certain amount of time has passed by) {
  execute Action

the if-condition itself is executed with each iteration of loop()
But executing the action is only done in case the if condition becomes true
which happens only after a certain amount of time

The looping is done global by void loop() itself
executing the action that shall only happen from time to time
is IF-conditionally to a certain amount of time.

The details of this time-measuring is to store a reference-point in time
and then do a 100000 times repeated checking with an if-condition
since reference-point has more time than 60000 milliseconds have passed by?

This blink without understand example of the arduino-IDE is unescessary hard to understand because it does

  • not explain this fundamental difference between linear-code-execution and globally circular-code-execution
  • scatter variables to different places in the code
  • uses badly chosen variable names

I claim that I have written a much easier to understand version of non-blocking timing with a tutorial that explains it

I'm very interested in improving this tutorial.

And the best way to improve it is to read questions of newcomers and to answer these questions

best regards Stefan

It depends :frowning:

On a RTOS system delay() is implemented in a non-blocking way that only stops this task for the given time. Polling instead can block other tasks from running - found out when the polling task blocked the only free core and the watchdog started barking.

Hello alphamike_1612

That is my recipe for getting started:

Take a peace of paper, a pencil and design, based on the IPO model, a program structure plan before start coding.
Identify functions and their depencies. Now start with coding and testing, function by function.
At the end merge all tested function together to get the final sketch.
The basics for coding the project are to be found in the IDE as examples.

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

This code-version replaces all delay() with non-blocking timing
and therefore can do the transmitting and the blinker monitoring both at a high repeating-frequency :

//this is the transmitter
#include <ESP8266WiFi.h>
#include <espnow.h>

uint8_t mac_peer[] = {0xE8, 0x9F, 0x6D, 0x8F, 0xDF, 0x56}; //receiver MAC

const int leftIN = D5;
const int rightIN = D6;
const int brakeIN = D7;
const int buzzerOUT = D3;
const int inBuiltLED = D4;


uint8_t transmit = 0;

int Counter = 0;
int Lsignalstate = 0;
int Rsignalstate = 0;

int lastLsignalstate = 0;
int lastRsignalstate = 0;

unsigned long myBlinkerTimer;
unsigned long myESP_sendTimer;

unsigned long BlinkerLOW_HIGH;
unsigned long BlinkerHIGH_LOW;

unsigned long lastBlinkerLOW_HIGH;
unsigned long lastBlinkerHIGH_LOW;

const unsigned long blinkerOnTime = 2000;

int lastsignalstate = 0;


void setup() {
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    return;
  }
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);

  esp_now_add_peer (mac_peer, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

  pinMode(leftIN, INPUT_PULLUP);
  pinMode(rightIN, INPUT_PULLUP);
  pinMode(brakeIN, INPUT_PULLUP);
  pinMode(inBuiltLED, OUTPUT);
  pinMode(buzzerOUT, OUTPUT);
}


void loop() {
  digitalWrite(inBuiltLED, HIGH);

  if (digitalRead(brakeIN) == LOW) {
    transmit = 1;
  }
  else if (digitalRead(leftIN) == LOW && digitalRead(rightIN) == LOW) {
    transmit = 2;
  }
  else if (digitalRead(leftIN) == LOW) {
    transmit = 3;
  }
  else if (digitalRead(rightIN) == LOW) {
    transmit = 4;
  }

  // check if more than 200 milliseconds have passed by
  if ( TimePeriodIsOver(myESP_sendTimer, 200) ) {
    // if REALLY more than 200 milliseconds have passed by
    // update variable myESP_sendTimer automatically
    
    //if (transmit == 1 || transmit == 2 || transmit == 3 || transmit == 4) {
    if (transmit != 0) { // does the some as || 1,2,3,4
      transmit = 0;
      esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
      esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
    }
  }

  buzzIfBlinkerIsOnTooLong();
}

// 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 buzzIfBlinkerIsOnTooLong() {
  Lsignalstate = digitalRead(leftIN);
  Rsignalstate = digitalRead(rightIN);

  // check if state of blinker has changed since last iteration of loop()
  if (lastRsignalstate != Rsignalstate) {
    lastRsignalstate = Rsignalstate;
    if (Rsignalstate == HIGH) {
      BlinkerLOW_HIGH = millis();
    }
    else {
      BlinkerHIGH_LOW = millis();
    }
  }

  if (lastLsignalstate != Lsignalstate) {
    lastLsignalstate = Lsignalstate;
    if (Lsignalstate == HIGH) {
      BlinkerLOW_HIGH = millis();
    }
    else {
      BlinkerHIGH_LOW = millis();
    }
  }

  // check if the changes low-HIGH-Low occurred in less than blinkerOnTime
  if (BlinkerHIGH_LOW - BlinkerLOW_HIGH < blinkerOnTime) {
    // if low-HIGH-low occurred in a short time it is a blink
    Counter++; // count the blink
  }

  // once every minute check if more than a threshold-value of blinks
  // did occure

  // check if more than 60000 milliseconds of time have passed by
  if ( TimePeriodIsOver(myBlinkerTimer, 60000ul) ) {
    //if REALLY more than 60000 milliseconds have passed by
    // variable myBlinkerTimer gets updated automatically
    // for the next checking

    if (Counter > 150) {
      digitalWrite(buzzerOUT, LOW);
      Counter = 0;
    }

    if (Counter < 150) {
      Counter = 0;
    }
  }
}  

several issues to think about

  • switches bounce, so reading a switch without adding some delay or ignoring the switch for a couple 10s msec results in multiple state changes. the 200 msec delay when sending transmit hides this

  • looks like you don't want transmit to indicate when the switch was released. wonder if this is a near-term fix

  • since start is reset to millis() each iteration of loop, the code spends most of its time in that while loop

  • i don't see where lastesignalstate is ever updated, so that condition is always true

  • i don't see where the busser is every turned off

  • isn't if (Counter <150) always resetting Counter to zero

consider the following

  • it starts a timer (msecPeriod != 0) whenever a switch is pressed and stops it whenever a switch is released
  • the timer alternately turns on/off the LED and buzzer when it expires
  • doesn't handle the transmit code when 2 switches are pressed at the same time

hopefully you see how the code separates handling switch state changes without long delays and a timer

#undef MyHW
#ifdef MyHW
# include "sim.hh"
const byte PinSw [] = { A1, A2, A3 };
const byte PinBuz   = 9;
const byte PinLed   = LED_BUILTIN;

#else
const byte PinSw [] = { D5, D6, D7 };
const byte PinBuz   = D3;
const byte PinLed   = D4;
#endif

const int Nsw = sizeof(PinSw);
byte swState [Nsw];

byte transmitLst;
byte transmit;

const unsigned long MsecWarning = 5000;
unsigned long msecPeriod;
unsigned long msecLst;

enum { Off = HIGH, On = LOW };
char s [80];

// -----------------------------------------------------------------------------


void loop()
{
    unsigned long msec = millis();
    if (msecPeriod && msec - msecLst >= msecPeriod) {
        msecLst    = msec;
        msecPeriod = 500;
        digitalWrite (PinBuz, ! digitalRead (PinBuz));
        digitalWrite (PinLed, ! digitalRead (PinLed));
    }

    // check for switch changes
    transmit = 0;
    for (int n = 0; n < Nsw; n++)  {
        byte sw = digitalRead (PinSw [n]);
        if (swState [n] != sw)  {
            swState [n] = sw;
            delay (20);     // debounce

            if (LOW == sw) {
                transmit = 1 + n;
                // arm timer
                msecPeriod = MsecWarning;
                msecLst    = msec;
            }
            else  {
                transmit = 0;

                msecPeriod = MsecWarning;
                digitalWrite (PinBuz, Off);
                digitalWrite (PinLed, Off);
            }
#if 0
            esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
#else
            sprintf (s, " transmit %d", transmit);
            Serial.println (s);
#endif
        }
    }
}

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

    for (int n = 0; n < Nsw; n++)  {
        pinMode  (PinSw [n], INPUT_PULLUP);
        swState [n] = digitalRead (PinSw [n]);
    }

    digitalWrite (PinBuz, Off);
    digitalWrite (PinLed, Off);
    pinMode (PinBuz, OUTPUT);
    pinMode (PinLed, OUTPUT);
}
1 Like

another one of these codes that arise so many many questions that it is very unlikely to even start asking

@StefanL38
please refrain from further comments like these.

This tutorial

Shows you several different ways to flash an LED without blocking anything else. Use the one that makes most sense to you.

Yes, yes you do.

Please refrain from further comments like these.

a7

Again, I had the impression that our advocatus for novices did not read or understand the solutions proposed by other members.
What shall we do?

1 Like

I did read the example-code. If I take the time to do a deep analysis I will understand it in all details. From a quick reading I did understand which part does what.
Here the code with comments as far as I understand it without deep analysing it and with comments what is difficult to understand and would require commenting

// in general
// the logic is NOT explained and understanding the logic 
// IN DETAIL requires a good amount of knowledge and TIME
// ignoring this is what I call
// the EXPERTS BLINDNESS FOR BEGINNER DIFFICULTIES
#undef MyHW  // not explained what this does
#ifdef MyHW  // switch between gc's hardware and original hardware of TO
# include "sim.hh"
const byte PinSw [] = { A1, A2, A3 };
const byte PinBuz   = 9;
const byte PinLed   = LED_BUILTIN;

#else
// easy to understand variable names eliminated
const byte PinSw [] = { D5, D6, D7 }; 
const byte PinBuz   = D3;
const byte PinLed   = D4;
#endif

// not explained
// using arrays for compacting code by using loops
const int Nsw = sizeof(PinSw);
byte swState [Nsw];

byte transmitLst;
byte transmit;

const unsigned long MsecWarning = 5000;
unsigned long msecPeriod; // timing variable
unsigned long msecLst;    // timing variable

// not explained
// define enumeration for using On/off instead of LOW / HIGH
enum { Off = HIGH, On = LOW };
char s [80];

// -----------------------------------------------------------------------------


void loop()
{
    // not explained
    unsigned long msec = millis();
    if (msecPeriod && msec - msecLst >= msecPeriod) {
        msecLst    = msec;
        msecPeriod = 500;
        digitalWrite (PinBuz, ! digitalRead (PinBuz));
        digitalWrite (PinLed, ! digitalRead (PinLed));
    }

    // check for switch changes
    // not explained it does debouncing
    transmit = 0;
    for (int n = 0; n < Nsw; n++)  {
        byte sw = digitalRead (PinSw [n]);
        if (swState [n] != sw)  {
            swState [n] = sw;
            delay (20);     // debounce

            if (LOW == sw) {
                transmit = 1 + n; // transmit values 1-4
                // arm timer
                msecPeriod = MsecWarning;
                msecLst    = msec;
            }
            else  {
                transmit = 0;

                msecPeriod = MsecWarning;
                digitalWrite (PinBuz, Off);
                digitalWrite (PinLed, Off);
            }
#if 0  // not explained switch to compile / not compile
            esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
#else
            sprintf (s, " transmit %d", transmit);
            Serial.println (s);
#endif
        }
    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin (9600);
    // configure pins 
    for (int n = 0; n < Nsw; n++)  {
        pinMode  (PinSw [n], INPUT_PULLUP);
        swState [n] = digitalRead (PinSw [n]);
    }

    digitalWrite (PinBuz, Off);
    digitalWrite (PinLed, Off);
    pinMode (PinBuz, OUTPUT);
    pinMode (PinLed, OUTPUT);
}

What shall you do?
Becoming a double-expert.
After beeing an expert for programming / coding itself
become an expert for beginner-difficulties that is able to do two things:

  1. writing compact, smart, sophisticated, OOP-code
  2. writing this code in a way that is easy to understand for beginners.

You think this is impossible?
hm ? ...---...---...---...--- what shall we do then?

The main purpose of a forum like this is to help - not to frustrate by presenting difficulties

I think these two goals are at cross purposes.

please explain how the above replaces the following (there are several flaws)

Hi gcjr,

yes you are right. I coded it wrong
my code-version does not pause for 200 milliseconds
like the original code
Not waiting for 200 ms might cause malfunction on the receiverside

So my code above needs a modification to have this 200 milliseconds between sending the value 1-4 and the following up zero

edit: a longer time ago I tested how fast esp_now can send messages.
Even 50 times per second was no problem.
As esp_now has a mechanism to register a call-back-function for the result if sending was successful or failed I guess the esp_now protocol has
some handshaking inbuild

as posted above. The code-version of post #6 has multiple bugs in sending the variable transmit via esp_now
here is a version that has the same functionality

  • if value of variable "transmit" has a value 1,2,3, or 4
    checked as beeing NOT zero != 0
if (transmit != 0.......
  • send this value of variable "transmit" and then initialise the timer-variable myESP_sendTimer; and set a flag that indicates the non-zero value has been sended
  • then start checking if 200 milliseconds of time have passed by
  • if 200 ms have passed by then set variable "transmit" to zero send the zero via esp_now and reset flag nonZeroTransmitted back to false
//this is the transmitter
#include <ESP8266WiFi.h>
#include <espnow.h>

uint8_t mac_peer[] = {0xE8, 0x9F, 0x6D, 0x8F, 0xDF, 0x56}; //receiver MAC

const int leftIN = D5;
const int rightIN = D6;
const int brakeIN = D7;
const int buzzerOUT = D3;
const int inBuiltLED = D4;


uint8_t transmit = 0;

int Counter = 0;
int Lsignalstate = 0;
int Rsignalstate = 0;

int lastLsignalstate = 0;
int lastRsignalstate = 0;

unsigned long myBlinkerTimer;
unsigned long myESP_sendTimer;

unsigned long BlinkerLOW_HIGH;
unsigned long BlinkerHIGH_LOW;

unsigned long lastBlinkerLOW_HIGH;
unsigned long lastBlinkerHIGH_LOW;

const unsigned long blinkerOnTime = 2000;
boolean nonZeroTransmitted = false;

int lastsignalstate = 0;


void setup() {
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    return;
  }
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);

  esp_now_add_peer (mac_peer, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

  pinMode(leftIN, INPUT_PULLUP);
  pinMode(rightIN, INPUT_PULLUP);
  pinMode(brakeIN, INPUT_PULLUP);
  pinMode(inBuiltLED, OUTPUT);
  pinMode(buzzerOUT, OUTPUT);
}


void loop() {
  digitalWrite(inBuiltLED, HIGH);

  if (digitalRead(brakeIN) == LOW) {
    transmit = 1;
  }
  else if (digitalRead(leftIN) == LOW && digitalRead(rightIN) == LOW) {
    transmit = 2;
  }
  else if (digitalRead(leftIN) == LOW) {
    transmit = 3;
  }
  else if (digitalRead(rightIN) == LOW) {
    transmit = 4;
  }

  //if (transmit == 1 || transmit == 2 || transmit == 3 || transmit == 4) {
  //when variable transmit is NOT zero and this non-zero value
  // of variable transmitt has NOT yet been send with esp_now
  if ( (transmit != 0) && !nonZeroTransmitted ) { 
    esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
    nonZeroTransmitted = true; 
    
    // store actual time as timely reference-point 
    // to enable checking how much time has passed by 
    // since non-zero-value of variable transmit has been sended
    myESP_sendTimer = millis(); 
  }

  // when non-zero value of variable transmit has been sended
  if (nonZeroTransmitted) {
    // check if more than 200 milliseconds have passed by
    if ( TimePeriodIsOver(myESP_sendTimer, 200) ) {
      // if REALLY more than 200 milliseconds have passed by
      // update variable myESP_sendTimer automatically
      transmit = 0;
      esp_now_send(mac_peer, (uint8_t *) &transmit, sizeof(transmit));
      nonZeroTransmitted = false;
    }
  }

  buzzIfBlinkerIsOnTooLong();
}

// 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 buzzIfBlinkerIsOnTooLong() {
  Lsignalstate = digitalRead(leftIN);
  Rsignalstate = digitalRead(rightIN);

  // check if state of blinker has changed since last iteration of loop()
  if (lastRsignalstate != Rsignalstate) {
    lastRsignalstate = Rsignalstate;
    if (Rsignalstate == HIGH) {
      BlinkerLOW_HIGH = millis();
    }
    else {
      BlinkerHIGH_LOW = millis();
    }
  }

  if (lastLsignalstate != Lsignalstate) {
    lastLsignalstate = Lsignalstate;
    if (Lsignalstate == HIGH) {
      BlinkerLOW_HIGH = millis();
    }
    else {
      BlinkerHIGH_LOW = millis();
    }
  }

  // check if the changes low-HIGH-Low occurred in less than blinkerOnTime
  if (BlinkerHIGH_LOW - BlinkerLOW_HIGH < blinkerOnTime) {
    // if low-HIGH-low occurred in a short time it is a blink
    Counter++; // count the blink
  }

  // once every minute check if more than a threshold-value of blinks
  // did occure

  // check if more than 60000 milliseconds of time have passed by
  if ( TimePeriodIsOver(myBlinkerTimer, 60000ul) ) {
    //if REALLY more than 60000 milliseconds have passed by
    // variable myBlinkerTimer gets updated automatically
    // for the next checking

    if (Counter > 150) {
      digitalWrite(buzzerOUT, LOW);
      Counter = 0;
    }

    if (Counter < 150) {
      Counter = 0;
    }
  }
}

as a question @alphamike_1612
does the ESP8266 detect the voltage of your blinker-lightbulbs with a voltage-divider?

best regards Stefan

Thank you everyone for your replies.
I will follow the IPO method and try to arrive at a logical conclusion.
@gcjr thank you for your code.
Seeing yours made me realise how poor my code writing skills are.
My input is an optocoupler.
I have yet to read through and understand all the codes provided.

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