How to use millis as a timer after a button press

Hi everyone,

I'm a beginner with arduino code and having trouble with a project. The code is supposed to run a motor for 5 seconds after pressing a button. Here is the code I have:

int directionPin = 12;
int pwmPin = 3;
int brakePin = 9;
const int buttonPin = 2;
int buttonState = 0;
int startTime;


//boolean to switch direction
bool directionState;
void setup() {
  
//define pins
pinMode(directionPin, OUTPUT);
pinMode(pwmPin, OUTPUT);
pinMode(brakePin, OUTPUT);

// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);

}


void loop() {

// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);

// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if (buttonState) {
  startTime =millis();
  digitalWrite(directionPin, HIGH);   //sets it to go forward 
  while(millis()-startTime<=5000)       //runs motor full speed for 5 s
  {
    analogWrite(pwmPin, 255);
  }

  
}

}

My thought was to set startTime as whatever millis() is when the button is pressed, then subtract by that amount in the while loop so it's effectively zero. However, the motor doesn't stop running, so I think what's happening is that the startTime variable is incrementing along with millis(). How would I change this so it stays as one value? (Or a different, easier way to have a timer). Apologies if this is a simple task that I'm making more complicated. Thanks in advance!

it is because nothing to stop it after the timer elapsed.

And take the note, that your "millis while loop" is the same blocking as a delay. If you don't want to block the program while timeout - do not put the millis inside a while()

1 Like

Try to compare your code with the code below:

void loop() {

// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);

// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
 if (buttonState) {
  startTime =millis();
  digitalWrite(directionPin, HIGH);   //sets it to go forward 
  analogWrite(pwmPin, 255);            //start motor full speed 
  }
  
if (millis()-startTime >5000)      
  {
    analogWrite(pwmPin, 0);   // stop the motor after 5s timeout
  }
}

Update : fix some typos

1 Like

Here's and example of using a button with millis() to provide a delayed on-off blink:

code
// DelayedLedButton -- blink a LED after a delay
// https://wokwi.com/projects/424083999010945025
// See more stat-change detection examples at:
//  https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/14
//
// This sketch demonstrates state change detection on several things:
//  inputs
//  variables
//  millis()-based timing variables
//

const int OnPin = 4;
const int LedPin = 12;
const uint32_t LedDelayTime = 1000, LedOnTime = 1000 ;
const bool DEBUG = false;

const uint32_t DebounceInterval = 10;

int lastButtonState;
uint32_t buttonChangeTime, buttonOnTime, ledOnTime;

enum LedStates {OFF, ARMED, ON} ledState = OFF, lastLedState = OFF;

void setup() {
  Serial.begin(115200);
  Serial.println(
    R"(DelayedLedButton:
  led goes on 1 second after button is pressed, 
  and let turns off 1 second after it turned on.
  Serial monitor shows ledState:)");
  pinMode(OnPin, INPUT_PULLUP);
  pinMode(LedPin, OUTPUT);
}

void loop() {
  // Input phase of https://en.wikipedia.org/wiki/IPO_model
  uint32_t now = millis();
  int buttonOn = digitalRead(OnPin) == LOW;

  // Processing phase of https://en.wikipedia.org/wiki/IPO_model

  // Detect the button rising and falling events using
  // state change detection and rate-limiting for debouncing:
  if (buttonOn != lastButtonState && now - buttonChangeTime > DebounceInterval) { // change detection
    buttonChangeTime = now;
    if (buttonOn) {
      buttonOnTime = millis();
      ledState = ARMED;
      if(DEBUG) Serial.print('v');
    }
    else
    {
      if(DEBUG) Serial.print("^");
    }
    lastButtonState = buttonOn;
  }

  // Manage the ledState according the state and the timers
  switch (ledState) {
    case OFF:
        // do nothing
      break;
    case ARMED:
      // state-change detection on timing threshold
      if (now - buttonOnTime > LedDelayTime) {
        ledState = ON;
        ledOnTime = now; // record time for turning off
      }
      break;
    case ON:
      // state-change detection on timing threshold
      if (now - ledOnTime > LedOnTime) {
        ledState = OFF;
      }
      break;
  }

  // Output phase of https://en.wikipedia.org/wiki/IPO_model

  // state change detection on ledState to update the LED pin only as needed
  if (ledState != lastLedState) {
    lastLedState = ledState;
    if (ledState == ON) {
      digitalWrite(LedPin, HIGH);
    }
    if (ledState == OFF) {
      digitalWrite(LedPin, LOW);
    }
  }
  
  // show the state changes on the serial monitor
  report();
}

void report() {
  // report on the state of the system
  static int lastLedState = 0;
  // state change detection on state variable to minimize output
  if (ledState != lastLedState) {
    lastLedState = ledState;
    Serial.print(ledState);
  }
}

It uses another millis() based timer to stop the LED, and another to debounce the button-presses.

Thank you so much! I never knew that detail about the millis() function inside a while loop.

your code was almost good (even if blocking - if you don't have anything else to do that might be OK), you just forgot to set the pwmPin back to 0 when the 5s were done.

In the "solution code", the loop will call that endlessly (well for each cycle of the loop)

when the first 5s have elapsed and until the next button press and you get an extra 5s if you press the button while the motor is running which might or might not be part of the requirements.

A state machine approach (Here is a small introduction to the topic: Yet another Finite State Machine introduction) is a typical way to address those needs.

here is a state machine example

const byte buttonPin = 2;
const byte ledPin = 13;
const unsigned long duration = 5000;

enum {WAITING_FOR_PRESS, RUNNING} state = WAITING_FOR_PRESS;
unsigned long startTime;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  switch (state) {
    case WAITING_FOR_PRESS:
      if (digitalRead(buttonPin) == LOW) {
        digitalWrite(ledPin, HIGH);
        Serial.println("STARTING THE MOTOR");
        startTime = millis();
        state = RUNNING;
      }
      break;

    case RUNNING:
      if (millis() - startTime >= duration) {
        digitalWrite(ledPin, LOW);
        Serial.println("STOPPING THE MOTOR");
        state = WAITING_FOR_PRESS;
      }
      break;
  }
}

if you wanted to be able to prolong the duration to the next 5s during the run, you would just add a new event in the RUNNING case to respond to.

      if (digitalRead(buttonPin) == LOW) startTime = millis(); // restart the chrono

this way the code exposes a clear vision of the state of the system (idle or running) and it's easy to extend the code to respond to specific events that might be specific to various states without making spaghetti code.

1 Like

side note: bouncing doe not matter much in your case but could be an issue depending on future changes to the way you handle the button

Using one of the numerous button library such as Button in easyRun or OneButton or Toggle or EasyButton or Bounce2, ... would help handle the button without having to worry about bouncing

1 Like

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