Help with delay programming?

I think this is a delay problem. You can see my code is a 6 digit password with buttons to turn on a relay and 3 digits to turn relay off. It works but with quirks.

I started with a 250 delay and I noticed it would not work and accept my password unless I changed the delay to 1000 and I pressed the buttons slowly waiting about a second between presses. otherwise it would not work if I pushed the buttons faster.

So obviously there is some kind of quirk with the delay and reading my button pushes etc
My other question is using pullup, my buttons should be NO or NC when they are at rest?

const int ButtonCount = 6;
const byte ButtonPins[ButtonCount] = {2, 3, 4, 5, 6, 7}; //first button is on pin 2

const int relay1 = 8; //relay1 signal is pin 8

int TurnOnCode[] = {6, 5, 5, 4, 3, 2};
const int TurnOnCodeCount = sizeof TurnOnCode / sizeof TurnOnCode[0];
int TurnOnCorrectCount = 0; // How many have been entered

int TurnOffCode[] = {1, 1, 1};
const int TurnOffCodeCount = sizeof TurnOffCode / sizeof TurnOffCode[0];
int TurnOffCorrectCount = 0; // How many have been entered

void setup()  //run once at sketch startup
{
  Serial.begin(9600); //begin Serial

  for (int i = 0; i < ButtonCount; i++)
    pinMode(ButtonPins[i], INPUT_PULLUP);

  pinMode(relay1, OUTPUT); // relay1 is an output

  Serial.print("Turn On code: ");
  for (int i = 0; i < TurnOnCodeCount; i++)
  {
    Serial.print(TurnOnCode[i]); //print each digit of the code
  }
  Serial.println();

  Serial.print("Turn Off code: ");
  for (int i = 0; i < TurnOffCodeCount; i++)
  {
    Serial.print(TurnOffCode[i]); //print each digit of the code
  }
  Serial.println();
}

void loop()  //run repeatedly
{
  // Check each button
  for (int i = 0; i < ButtonCount; i++)
  {
    if (digitalRead(ButtonPins[i]) == LOW)  //if button is pressed
    {
      // Button is pressed
      checkEntered(i + 1); //call checkEntered and pass it the button number

      delay(1000);  //wait, needed for correct functioning, otherwise
      //buttons are deemed to be pressed more than once
    }
  }
}

void checkEntered(int button)  //check the button press
{
  // Check against turn-on code
  if (button == TurnOnCode[TurnOnCorrectCount])
  {
    TurnOnCorrectCount++;
    if (TurnOnCorrectCount == TurnOnCodeCount)
    {
      // Success!
      digitalWrite(relay1, HIGH); //turn the relay1 on
      // Start over
      TurnOnCorrectCount = 0;
    }
  }
  else // Not the correct button
  {
    TurnOnCorrectCount = 0; // Start over
  }

  // Check against turn-off code
  if (button == TurnOffCode[TurnOffCorrectCount])
  {
    TurnOffCorrectCount++;
    if (TurnOffCorrectCount == TurnOffCodeCount)
    {
      // Success!
      digitalWrite(relay1, LOW); //turn the relay1 off
      // Start over
      TurnOffCorrectCount = 0;
    }
  }
  else // Not the correct button
  {
    TurnOffCorrectCount = 0; // Start over
  }
}

There is no quirk with delay... it does what it says.. it delays. It stops your program and waits.

What you are experiencing with your buttons is something called "bounce". When the button changes state it is not a perfect transition... it bounces between states rapidly before it settles in the new state. You need to allow for this, either via hardware or software.

Have a look a this example.

1 Like

No quirk your delays are working as they should. Have you ever come across something called contact bounce? You have now.

It really doesn't matter. NO are normally used, but using NC means you look for a HIGH when pressed.

If you use INPUT_PULLUP then you connect the other side of the button to GND, and look for a LOW when it is pressed for a Normally Open (NO) button (which most will be). Normally Closed (NC) just means that it will by default connected to GND so you need to look for HIGH when pressed.

Some people like to avoid the potential confusion in their code by substituting with something that reads better in the code... like

#define PRESSED LOW

and then using

if (digitalRead(button) == PRESSED)

@red_car @Grumpy_Mike thanks. My buttons have both NO and NC option on them so I guess I was just clarifying if one configuration is better to use than the other in my scenario but it sounds like it doesnt matter, thanks

And I will do some reading on the debounce code and how to write that in, thanks guys

Don't think it really matters. Most are NO, so I'd probably stick with that as it is less likely to confuse the next guy that stumbles across your build.

For a noisy situation, an NC switch (going to GND) is a better configuration.

However, both wiring options can be made to work.

Best practice here is to use TWO millis() 'timers'
FIRST for the debounce functionality, and the
SECOND for a keypress 'timeout'.

Allow a decent timeout period e.g. 3000 msecs, and if the expected/next button isn't pressed within that 3 secs, the input buffer is cleared, and program returned to the default operating state.

1 Like

You were using delay in place of State Change Detection. Without the delay, you were acting on the state of the button hundreds or thousands of times per second. That made it nearly impossible to press and release the buttons fast enough to only act as one press. Adding the 250 milliseconds delay after a press was registered gave you 1/4 second to release the button before it was checked again. Apparently, you had trouble pressing the button for less than 250 milliseconds so, to keep the buttons from registering multiple presses, you increased the delay to 1000 milliseconds. Now you have reduced the sample rate to once a second so you can't register more than one press per second.

You should use State Change Detection and Debounce. Here is an example that does debounce and state change detection on multiple buttons:

const byte ButtonCount = 3;
const byte ButtonPins[ButtonCount] = {2, 3, 4};
const unsigned long DebounceTime = 30;

boolean ButtonWasPressed[ButtonCount];  // Defaults to 'false'
unsigned long ButtonStateChangeTime = 0; // Debounce timer common to all buttons

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

  for (byte i = 0; i < ButtonCount; i++)
  {
    pinMode (ButtonPins[i], INPUT_PULLUP);  // Button between Pin and Ground
  }
}

void loop()
{
  checkButtons();
}

void checkButtons()
{
  unsigned long currentTime = millis();

  // Update the buttons
  for (byte i = 0; i < ButtonCount; i++)
  {
    boolean buttonIsPressed = digitalRead(ButtonPins[i]) == LOW;  // Active LOW

    // Check for button state change and do debounce
    if (buttonIsPressed != ButtonWasPressed[i] &&
        currentTime - ButtonStateChangeTime > DebounceTime)
    {
      // Button state has changed
      ButtonStateChangeTime = currentTime;
      ButtonWasPressed[i] = buttonIsPressed;
      if (ButtonWasPressed[i])
      {
        // Button i was just pressed
      }
      else
      {
        // Button i was just released
      }
    }
  }
}
1 Like

SOLVED thanks here is my finished code and it functions perfectly 6 digit combination to unlock and 3 digits to lock again. Oh sorry ignore the relay2 that is being removed and is not actually used

#include <JC_Button.h>

const int ButtonCount = 6;
const byte ButtonPins[ButtonCount] = {2, 3, 4, 5, 6, 7}; //first button is on pin 2

const int relay1 = 8; //relay1 signal is pin 8
const int relay2 = 12; //relay2 signal is pin 12

int TurnOnCode[] = {6, 5, 5, 4, 3, 2};
const int TurnOnCodeCount = sizeof TurnOnCode / sizeof TurnOnCode[0];
int TurnOnCorrectCount = 0; // How many have been entered

int TurnOffCode[] = {1, 1, 1};
const int TurnOffCodeCount = sizeof TurnOffCode / sizeof TurnOffCode[0];
int TurnOffCorrectCount = 0; // How many have been entered

boolean ButtonWasPressed[ButtonCount];  // Defaults to 'false'
unsigned long ButtonStateChangeTime = 0; // Debounce timer common to all buttons
const unsigned long DebounceTime = 30;

void setup()  //run once at sketch startup
{
  Serial.begin(9600); //begin Serial

  for (int i = 0; i < ButtonCount; i++)
    pinMode(ButtonPins[i], INPUT_PULLUP);

  pinMode(relay1, OUTPUT); // relay1 is an output
  pinMode(relay2, OUTPUT); // relay2 is an output
  
  Serial.print("Turn On code: ");
  for (int i = 0; i < TurnOnCodeCount; i++)
  {
    Serial.print(TurnOnCode[i]); //print each digit of the code
  }
  Serial.println();

  Serial.print("Turn Off code: ");
  for (int i = 0; i < TurnOffCodeCount; i++)
  {
    Serial.print(TurnOffCode[i]); //print each digit of the code
  }
  Serial.println();
}

void loop()  //run repeatedly
{
  unsigned long currentTime = millis();

  // Check each button
  for (int i = 0; i < ButtonCount; i++)
  {
    boolean buttonIsPressed = digitalRead(ButtonPins[i]) == LOW;
    boolean buttonHasBounced = currentTime - ButtonStateChangeTime > DebounceTime;
    
    if (buttonIsPressed != ButtonWasPressed[i] && buttonHasBounced)
    {
      ButtonStateChangeTime = currentTime;
      ButtonWasPressed[i] = buttonIsPressed;

      if (ButtonWasPressed[i])
      {
        checkEntered(i + 1); //call checkEntered and pass it the button number
      }
    }
  }
}

void checkEntered(int button)  //check the button press
{
  // Check against turn-on code
  if (button == TurnOnCode[TurnOnCorrectCount])
  {
    TurnOnCorrectCount++;
    if (TurnOnCorrectCount == TurnOnCodeCount)
    {
      // Success!
      digitalWrite(relay1, HIGH); //turn relay1 on
      digitalWrite(relay2, HIGH); //turn relay2 on
      // Start over
      TurnOnCorrectCount = 0;
    }
  }
  else // Not the correct button
  {
    TurnOnCorrectCount = 0; // Start over
  }

  // Check against turn-off code
  if (button == TurnOffCode[TurnOffCorrectCount])
  {
    TurnOffCorrectCount++;
    if (TurnOffCorrectCount == TurnOffCodeCount)
    {
      // Success!
      digitalWrite(relay1, LOW); //turn relay1 off
      digitalWrite(relay2, LOW); //turn relay2 off
      // Start over
      TurnOffCorrectCount = 0;
    }
  }
  else // Not the correct button
  {
    TurnOffCorrectCount = 0; // Start over
  }
}

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