c++carduino

Problem to stop a function and restart it at the same location


I have a problem with my Arduino code. I'm building a small traffic-light system which I need for my base project.

Now I have a problem, I can't get the program to stop when the button is clicked twice and to continue running in the same place when it is clicked again.

#define one_Red 3
#define one_Yellow 4
#define one_Green 5
#define two_Red 6
#define two_Yellow 7
#define two_Green 8
#define redLed 10
#define greenLed 9
#define button 2

int programRunning = false;
int brightness = 0;
int fadeAmount = 5;
unsigned long previousMillis = 0;
const long interval = 30;

unsigned long taskStartTime = 0;
const unsigned long firstTaskDuration[] = {1000, 2000, 3000, 2000, 2000};
const unsigned long resetTaskDuration[] = {1000, 2000, 3000, 2000, 2000};
int currentStep = 0;

unsigned long lastButtonPress = 0;
int buttonPressCount = 0;

void setup()
{
  pinMode(one_Red, OUTPUT);
  pinMode(one_Yellow, OUTPUT);
  pinMode(one_Green, OUTPUT);
  pinMode(two_Red, OUTPUT);
  pinMode(two_Yellow, OUTPUT);
  pinMode(two_Green, OUTPUT);
  pinMode(redLed, OUTPUT);
  pinMode(button, INPUT_PULLUP);

  digitalWrite(one_Red, HIGH);
  digitalWrite(one_Yellow, HIGH);
  digitalWrite(one_Green, LOW);
  digitalWrite(two_Red, LOW);
  digitalWrite(two_Yellow, HIGH);
  digitalWrite(two_Green, HIGH);
  digitalWrite(redLed, HIGH);
}

void loop()
{
  pulseRedLed();
  handleButtonPress();

  if (programRunning)
  {
    runResetTask();
  }
  else
  {
    runFirstTask();
  }
}

void handleButtonPress()
{
  if (digitalRead(button) == LOW)
  {
    unsigned long currentMillis = millis();
    
    if (currentMillis - lastButtonPress > 800) 
    {
      lastButtonPress = currentMillis;
      buttonPressCount++;

      if (buttonPressCount == 2)
      {
        programRunning = !programRunning;
        buttonPressCount = 0; 
      }
    }
  }
  else
  {
    buttonPressCount = 0;
  }
}

void runFirstTask()
{
  if (digitalRead(button) == LOW && currentStep == 0)
  {
    taskStartTime = millis();
    currentStep = 1;
  }

  if (currentStep > 0)
  {
    unsigned long currentMillis = millis();
    if (currentMillis - taskStartTime >= firstTaskDuration[currentStep - 1])
    {
      taskStartTime = currentMillis;
      switch (currentStep)
      {
      case 1:
        digitalWrite(one_Green, HIGH);
        digitalWrite(one_Yellow, LOW);
        break;
      case 2:
        digitalWrite(one_Yellow, HIGH);
        digitalWrite(one_Red, LOW);
        break;
      case 3:
        digitalWrite(two_Red, HIGH);
        digitalWrite(two_Yellow, LOW);
        break;
      case 4:
        digitalWrite(two_Yellow, HIGH);
        digitalWrite(two_Green, LOW);
        programRunning = true;
        currentStep = 0;
        return;
      }
      currentStep++;
    }
  }
}

void runResetTask()
{
  if (digitalRead(button) == LOW && currentStep == 0)
  {
    taskStartTime = millis();
    currentStep = 1;
  }

  if (currentStep > 0)
  {
    unsigned long currentMillis = millis();
    if (currentMillis - taskStartTime >= resetTaskDuration[currentStep - 1])
    {
      taskStartTime = currentMillis;
      switch (currentStep)
      {
      case 1:
        digitalWrite(two_Green, HIGH);
        digitalWrite(two_Yellow, LOW);
        break;
      case 2:
        digitalWrite(two_Yellow, HIGH);
        digitalWrite(two_Red, LOW);
        break;
      case 3:
        digitalWrite(one_Red, HIGH);
        digitalWrite(one_Yellow, LOW);
        break;
      case 4:
        digitalWrite(one_Yellow, HIGH);
        digitalWrite(one_Green, LOW);
        programRunning = false;
        currentStep = 0;
        return;
      }
      currentStep++;
    }
  }
}

void pulseRedLed()
{
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;
    analogWrite(redLed, brightness);
    brightness += fadeAmount;

    if (brightness <= 0 || brightness >= 255)
    {
      fadeAmount = -fadeAmount;
    }
  }
}

I have tried to revise the method “handleButtonPress” with no success at the moment.


Solution

  • I would normally expect button-events to trigger an interrupt so that an ISR (interrupt service routine) was executed. The ISR would then detect events like button-down and button-up. It could include some time based filtering to handle contact bounce.

    Your code, however, seems to poll the button. And that seems to be - at least part of - the problem here.

    Let's start with handleButtonPress and forget the other functions for a while. Your idea seems to be that you want to see digitalRead(button) == LOW twice. And that you use buttonPressCount to determine when that had happened.

    Okay so clicking a button twice would look like:

    HHHHHHHHLHHHHLHHHHHHHH  (L means LOW, H means HIGH)
            ^    ^
            1st  2nd
             click
    

    When you see the first LOW you increment buttonPressCount, i.e. it will become 1. So far so good.

    Then you see a H (HIGH) so your code takes the else path and do:

    buttonPressCount = 0;
    

    Ups... You just cleared the first click! So you can't detect the button being clicked twice!

    Your current function can only do that if two consecutive executions both read the value low.

    But the you have a 800ms limit also... As I assume the function would run many, many times within 800ms, it means that many, many consecutive executions must read the value low in order to avoid going into the else path and clear the counter.

    So to trigger the code for "clicked twice" you need to hold the button down for a long time, i.e. more than 2x800ms.

    So you need to redesign the function.

    I'll suggest that you start out testing it without the present of other functions so that you have a more simple setup. For instance like:

    void loop()
    {
      /////////////// pulseRedLed();
      handleButtonPress();
    
      if (programRunning)
      {
        ///////////// runResetTask();
        turn_on_green_and_turn_off_red();
      }
      else
      {
        ///////////// runFirstTask();
        turn_off_green_and_turn_on_red();
      }
    }
    

    Once your "clicked twice" detection works, you should see the LEDs toggling between green and red when you click twice.

    ohh... and consider adding checks for digitalRead(button) == HIGH. To me a click is a sequence of two events. First you detect a LOW and then you must detect a HIGH. That's a click. Also you may add an upper limit for the time between two clicks in order to consider it a double-click.

    Further...

    Your functions runResetTask and runFirstTask also check the button state and - if LOW - starts executing something that takes seconds!

    That's a bad design. How can you know whether the first LOW is detected by e.g. runResetTask or handleButtonPress? You can't!

    All handling of button state must be within a single function.