Countdown timer not waiting for start button

Hi everybody

I've built a large-scale Head-to-Head Perfection game. Perfection game

I'm having difficulty with the timer. When I press Reset (RS) the timer starts counting down automatically. I'm trying to get the countdown to start when I press Start (ST). I've tried moving the timer to the loop that only activates when the start button is pushed. All the other components of the circuit cooperate. The P1 and P2 buttons don't work until start is pressed. The LED's don't display until start is pressed. But the countdown has already begun. I've tried adding the number of milliseconds from the time the game was reset to the (startTym) to the duration of the game (endTym) but it hasn't helped.
The way I want it to work is to be able to press Reset, push the board halves down, then press Start. The players should have the full amount of time to play. IRL the players are penalized by the amount of time it takes for me to press the Start button.

Any help appreciated.

Code snippet. Full code follows.

void loop() {

b4State =digitalRead(start); 
 
if(b4State ==0) // Start button pressed
{    flag =1; 
 delay(200);  // debounce
} 

if(1==flag) 
{ 

if (flag2 == 0 && flag == 1)
    {
    startTym = millis();  //initial start time
    endTym = (startTym + endTym);
    flag2 = 1; // makes sure not to grab the start time more than once
    } 

currentTym = millis();  //get the current "time" (actually the number of milliseconds since the program started)
    if (currentTym >= endTym) //check if the game time is up
  {

//www.ijprems.com editor@ijprems.com Vol. 04, Issue 11, November 2024, pp : 1638-1642 
//e-ISSN : 2583-1062 Impact Factor : 7.001 

// https://github.com/adafruit/Adafruit_NeoPixel 

// https://forum.arduino.cc/t/using-millis-for-timing-a-beginners-guide/483573

// https://wokwi.com/   World's most advanced ESP32 simulator

// Not shown is an 4 relay shield. Pins 4, 5, 6, 7 operate relays. 
// LED's R1, R2 represent 12v Soleniods
// Ticker represents a 12v relay that provides sound.

// Press Start (ST). This Starts the timer and the Neopixels
// Pressing P1 triggers R1 and freezes the timer
// Pressing P2 triggers R2 and freezes the timer
// Press Reset (RS) to restart the game
// This program is the merger of a button sketch and a timer sketch

// from buttons 
#define player1  9 
#define player2  3 
#define start  12 
#define relay1   5 
#define relay2   6 
#define uint8 unsigned char 
uint8 flag =0; uint8 b1State,b2State,b3State,b4State =0; 
int flag2 =0;
float tym =0; float eltym =0;

// from timer
#include <Adafruit_NeoPixel.h>
#define NEOPIXEL1_PIN 10          
#define NEOPIXEL1_NUM 18   
unsigned long startTym;  //some global variables available anywhere in the program
unsigned long currentTym;
const unsigned long onTym = 500;  //the value is a number of milliseconds
unsigned long offTym = 500;  //the value is a number of milliseconds
unsigned long cycleTym = 1000;  //the value is a number of milliseconds
unsigned long endTym = 200000;  //the value is a number of milliseconds total game time
const byte ticker1 = 7;    //relay sound
const byte ticker2 = 8;    //relay sound

Adafruit_NeoPixel pixels(NEOPIXEL1_NUM, NEOPIXEL1_PIN, NEO_GRB + NEO_KHZ800);

void setup() {

pinMode(relay1  ,OUTPUT); 
pinMode(relay2  ,OUTPUT); 
pinMode(player1 ,INPUT_PULLUP); 
pinMode(player2 ,INPUT_PULLUP); 
pinMode(start ,INPUT_PULLUP); 
digitalWrite(relay1 ,HIGH); 
digitalWrite(relay2 ,HIGH); 

// timer section
  
pixels.begin(); // Initialize NeoPixel strip
pixels.clear(); // Clears previous NeoPixel settings
pixels.show();  // Actually shows the cleared settings
pinMode(ticker1, OUTPUT);
pinMode(ticker2, OUTPUT);
//startTym = millis();  //initial start time
digitalWrite(ticker1, LOW);
digitalWrite(ticker2, LOW);
}

void loop() {


b4State =digitalRead(start); 
 
if(b4State ==0) // Start button pressed
{    flag =1; 
 delay(200);  // debounce
} 


if(1==flag) 
{ 

if (flag2 == 0 && flag == 1)
    {
    startTym = millis();  //initial start time
    endTym = (startTym + endTym);
    flag2 = 1; // makes sure not to grab the start time more than once
    } 

currentTym = millis();  //get the current "time" (actually the number of milliseconds since the program started)
    if (currentTym >= endTym) //check if the game time is up
  {
    digitalWrite(relay1,LOW); 
    digitalWrite(relay2,LOW); 
    digitalWrite(ticker1, LOW);
    digitalWrite(ticker2, LOW);
    delay(120000);
  }

b1State =digitalRead(player1  ); 
b2State =digitalRead(player2  ); 

if(b1State==0) // Player 1 pressed the button
{    flag =0; 
digitalWrite(relay1 ,LOW);  // Release the magnet holding one side down
digitalWrite(relay2 ,HIGH); // Keep the winning side down
digitalWrite(ticker1, LOW);  // stop the ticker
digitalWrite(ticker2, LOW);  // stop the ticker
delay(120000);              // Wait 2 min for selfies.
while(digitalRead(start ));
} 

if(b2State==0) // Player 2 pressed the button
{    flag =0; 
digitalWrite(ticker1, LOW);  // stop the ticker
digitalWrite(ticker2, LOW);  // stop the ticker
digitalWrite(relay1 ,HIGH); // Keep the winning side down
digitalWrite(relay2 ,LOW);  // Release the magnet holding one side down
delay(120000);              // Wait 2 min for selfies.
while(digitalRead(start ));} 

// this section causes the relays to click like a slowing down spring
  else if (currentTym - startTym >= onTym)  //test whether the onTym has elapsed
    {
    digitalWrite(ticker1, LOW);
    digitalWrite(ticker2, LOW);
     if (currentTym - startTym >= cycleTym)  //test whether the cycleTym has elapsed
    {
      digitalWrite(ticker1, HIGH);
      digitalWrite(ticker2, HIGH);
      offTym = offTym + sq(offTym)/200000; //This is my formula for making the off time longer while keeping the on time the same
      cycleTym = onTym + offTym;
      startTym = currentTym;  //IMPORTANT to save the start time of the current LED state.

 // this section counts down the LED string   
  if (endTym-currentTym >= endTym * .01) { pixels.setPixelColor(0, pixels.Color(255, 0, 255)); // Purple on pixel 0
    }
    else { pixels.setPixelColor(0, pixels.Color(0, 0, 0));} // pixel off
  
  if (endTym-currentTym >= endTym * .10) {  pixels.setPixelColor(1, pixels.Color(255, 0, 0)); // Red on pixel 1
    }  
    else { pixels.setPixelColor(1, pixels.Color(0, 0, 0));}  // pixel off   

  if (endTym-currentTym >= endTym * .16) {  pixels.setPixelColor(2, pixels.Color(255, 0, 0)); // Red on pixel 2
    }  
    else { pixels.setPixelColor(2, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .21){   pixels.setPixelColor(3, pixels.Color(255, 165, 0)); // Orange on pixel 3
    } 
    else { pixels.setPixelColor(3, pixels.Color(0, 0, 0));}  // pixel off

  if (endTym-currentTym >= endTym * .26){   pixels.setPixelColor(4, pixels.Color(255, 165, 0)); // Orange on pixel 4
    } 
    else { pixels.setPixelColor(4, pixels.Color(0, 0, 0));}   // pixel off 

  if (endTym-currentTym >= endTym * .31){   pixels.setPixelColor(5, pixels.Color(255, 165, 0)); // Orange on pixel 5
    } 
    else { pixels.setPixelColor(5, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .37){   pixels.setPixelColor(6, pixels.Color(255, 255, 0)); // yellow on pixel 6
    } 
    else { pixels.setPixelColor(6, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .42){   pixels.setPixelColor(7, pixels.Color(255, 255, 0)); // yellow on pixel 7
    } 
    else { pixels.setPixelColor(7, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .47){   pixels.setPixelColor(8, pixels.Color(255, 255, 0)); // yellow on pixel 8
    } 
    else { pixels.setPixelColor(8, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .52){   pixels.setPixelColor(9, pixels.Color(255, 255, 0)); // yellow on pixel 9
    } 
    else { pixels.setPixelColor(9, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .58){   pixels.setPixelColor(10, pixels.Color(96, 164, 114)); // Green on pixel 10
    } 
    else { pixels.setPixelColor(10, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .63){   pixels.setPixelColor(11, pixels.Color(96, 164, 114)); // Green on pixel 11
    } 
    else { pixels.setPixelColor(11, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .68){   pixels.setPixelColor(12, pixels.Color(96, 164, 114)); // Green on pixel 12
    } 
    else { pixels.setPixelColor(12, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .73){   pixels.setPixelColor(13, pixels.Color(96, 164, 114)); // Green on pixel 13
    } 
    else { pixels.setPixelColor(13, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .79){   pixels.setPixelColor(14, pixels.Color(96, 164, 114)); // Green on pixel 14
    } 
    else { pixels.setPixelColor(14, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .84){   pixels.setPixelColor(15, pixels.Color(96, 164, 114)); // Green on pixel 15
    } 
    else { pixels.setPixelColor(15, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .89){   pixels.setPixelColor(16, pixels.Color(96, 164, 114)); // Green on pixel 16
    } 
    else { pixels.setPixelColor(16, pixels.Color(0, 0, 0));} // pixel off

  if (endTym-currentTym >= endTym * .94){   pixels.setPixelColor(17, pixels.Color(96, 164, 114)); // Green on pixel 17
    } 
    else { pixels.setPixelColor(17, pixels.Color(0, 0, 0));} // pixel off

   pixels.show();
  }
  }
  }
 }

  • First things first, 28 pixels will probably be too much for your Arduino.
  • Format your code, use <Ctrl><T>
  • Suggest you sample your switches every 50ms look for a change in state, not the switch level.
  • No no no no no !
    delay(120000);

I don't see any button library, so there is no bounce control, and button performance will be random. The Button2 library has a lot of good examples of how buttons can be used.
You likely will need to provide a separate PSU for the LEDS, the UNO is not a PSU.

Hi Larry. There are 18 LED's on each strip. They are powered from a separate 5v power supply (not shown) and controlled by PIN 10. I am having some issues where they don't behave exactly as the Wokwi simulator would suggest. For instance the LED's turn off IRL when one player wins where the simulator shows them staying on.

Sorry about the formatting, It is a work in progress. At least it has lots of comments.

The Delay 120000 was so people could pose for selfies. Without the delay the timer continues counting down. When the timer runs out the winners side of the game would also release.

The 200ms delay after pressing Start is all the bounce control I've needed. There hasn't been an issue with the P1 and P2 buttons. I will look for this library and see where it leads me.

  • You might want to look at using a State Machine

  • Time to learn how to make non-blocking TIMERs using millis().

2 Likes
  • Just poll the switches every 20-50ms.

I've included the Button2.h library. The buttons aren't the problem. The buttons work in the simulator and IRL. My problem is the countdown begins before the start button is pressed. How do I get the timer to count 200000ms after the start button, not 200000ms after the reset?

For sure, you could even set a shorter delay for debouncing (when I don't use hw debouce, I use 100ms or less, up to 50 seems to work in real life).

I think if you share the link to the WokWi project we could load it in our account and test.

when the start button gets pressed store the current millis() in a variable called previousMillis.
Check if the difference between your current millis() and previousMillis is larger than 200000.

if (millis() - previousMillis > 200000) {
   Serial.println(F("do something"));
}

think about an additional flag when your task has finished or even better - make a finite state machine.

P.S.: arrays would save a ton of lines of code.

Why not like below:?

if (millis() - previousMillis > 200000ul) {

@GolamMostafa make a short demo sketch - try it - come back to us.
When you can proof that there is something wrong in my snippet I'm happy to correct it.

It is just my curiosity to know why ul is not needed to append after 200000 when some programmers (as I have seen in Forum) do so.

check again when they are doing so
... I assume they will do it when there is a calculation with integers which could exceed the size of int ... but when you have a literal > int it is already > int ...

The millis() function returns 32-bit unsigned number and previousMillis is also and unsigned number. The result of subtraction of two unsigned numbers is being compared with 200000 (0x30D40) which the Compiler will treat as 16-bit signed number in the context of 8-Bit UNO Board. Here, I see a type mismatch. As a result, the expression if(millis() - previousMillis > 200000){
might be evaluated as if(millis() - previousMillis > 3392){ producing wrong result.

So, 200000ul will ensure that 200000 is an unsigned number and accrdingly appropriate sized unsigned buffer would be allocated to store 200000 during processing. Any number greater tan 32767 must bear ul suffix.

may be this is a false assumption.

Please try this on an 8bit controller:


enum class State {WAIT, DO};  // the steps/states for the finite state machine
State state = State::WAIT;    // a variable to store the current state of the FSM
uint32_t previousMillis;      // time management like done in "Blink Without Delay"

void runFsm() {
  uint32_t currentMillis = millis();
  switch (state) {
    case State::WAIT :
      if (currentMillis - previousMillis > 200000) {
        state = State::DO;     // go to next step
      }
      break;
    case State::DO :
      Serial.println(F("do something from FSM"));  // do something
      previousMillis = millis();          // reset the timer
      state = State::WAIT;
      break;
  }
}

void runTimer() {
  uint32_t currentMillis = millis();
  static uint32_t previousMillis = 0;       // this local variable should be available also for the next iteration, hence it is declared static
  if (currentMillis - previousMillis >= 1000) {
    previousMillis = currentMillis;
    Serial.print(currentMillis);
    Serial.print('\t');
    Serial.println((int)state);
  }
}

constexpr int factor = 35; // try values >= 33 
void runDemo() {
  uint32_t currentMillis = millis();
  static uint32_t previousMillis = 0;       // this local variable should be available also for the next iteration, hence it is declared static
  if (currentMillis - previousMillis >= factor * 1000UL) {  // here it makes sense to ensure that we have an unsigned long calculation!
    previousMillis = currentMillis;
    Serial.print(currentMillis);
    Serial.print('\t');
    Serial.println(F("runDemo"));
  }
}

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println(F("started - please wait for a while "));
}

void loop() {
  runFsm();
  runTimer();
  runDemo();
}
//

Please tell me,
a) will it fire roughly every 200 seconds out of runFSM?
b) will it fire roughly every 35 seconds out of runDemo?
c) can runDemo break when you change 1000UL to 1000?
d) can you now confirm that my snippet is OK like stated in #11 and used in runFSM?

The defualt processing buffer size of Arduino UNO is int (16-bit). The number 200000 stands as 0x30D40 in hex. So, the processing buffer will hold only 0D40 (3392). Am I correct?

no. Try my code and take your time to answer my questions.

I'm not sure about that. IMO the compiler should "know" the 200000 value can't fit the default standard "int" range, and using it as a long integer value. Thus the comparison will be between two "unsinged long" values without any problem.

If your assumption was true, this code:

  Serial.print(200000);Serial.print(" ");
  Serial.print((unsigned int)200000);Serial.print(" ");
  Serial.println(200000UL);

would show:

3392 3392 200000

instead it shows:

200000 3392 200000

To make things clearer, a simple code to check it is:

unsigned long t0 = millis();

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print(millis() - t0);Serial.print(" ");Serial.println(200000);
  if (millis() - t0 > 200000) {
    Serial.println("TICK!");
    t0 = millis();
  }
  delay(1000);
}

Its (real, I tested it!) output is:

...
199112 200000
200114 200000
TICK!
1000 200000

demonstrating the compiler always converts constants into the proper data type, and the "TICK" is shown after 200000 ms. Adding "UL" won't change the code behaviour, so it isn't an error, obviously... :wink:

Let me be clear: the only thing that isn't true is the general "int" automatic conversion made by the compiler on numeric constants. Instead, the explicit casting you suggested it's a good thing, to always be kept in mind because it's often needed on expressions where you need to make sure the compiler converts it into the correct data type, avoiding unwanted "mix" of types generating anomalous results.

1 Like