Using a Timer Interrupt to stop an action after a certain amount of time

Hello!

I currently have written some code that generates a square wave of a given frequency after a button is pushed. When the button is pushed a second time the square wave output stops, but a third press will turn it back on, etc.

Instead of needing a second button press to turn the output off, I would like to have a timer interrupt that turns it off after 300 ms, so that the second button press simply turns it back on again.

Could anyone point me in the direction of some resources to figure out how to do this? I have also included my current working code below.

 /*square wave when button pushed w/ debounce */

// pin setup
const int squarePin = 10;
const int buttonPin = 2;

// other variables
boolean squareOn = false;
boolean stillPushed = false;
long lastUpdateTime = 0;
const long debounce = 200;
int timeCount = 0;
int squareState = 0;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(squarePin, OUTPUT);
}

void loop() {
  // check for button pushed (on/off control).
  boolean buttonPushed = (digitalRead(buttonPin) == LOW);  
 
  // debounce button
  if (buttonPushed && millis() - lastUpdateTime > debounce) {  
    squareOn = !squareOn;
    lastUpdateTime = millis();
  }
  
  if (squareOn) {
     while(timeCount < 200){                   
        if(squareState == 0){   
           digitalWrite(squarePin, LOW);
           squareState = 1;
           timeCount ++;
        }
        else{
           digitalWrite(squarePin, HIGH);
           squareState = 0;
        }
        delay(200); //this makes freq = ~2.5 Hz
        }
  }else{
    digitalWrite(squarePin, 0);  // output GND when button hasnt been pushed
  }
 
}

For long time periods such as 300 millisecs there is no need to use an interrupt. Just use millis() or micros().

The demo Several Things at a Time illustrates the use of millis() to manage timing. It may help with understanding the technique.

Have a look at Using millis() for timing. A beginners guide if you need more explanation.

For more precise timing micros() can be used in the same way as millis()

...R

sciencechick123:
I currently have written some code that generates a square wave of a given frequency after a button is pushed. When the button is pushed a second time the square wave output stops, but a third press will turn it back on, etc.

Instead of needing a second button press to turn the output off, I would like to have a timer interrupt that turns it off after 300 ms, so that the second button press simply turns it back on again.

Robin mentioned the usage of millis() etc. That's when you purposely get the software to mark (remember) down the arduino's internal 'time' at the moment it starts generating the square wave. Eg. start_time ..... it is just some value that the arduino will dish out when you use a variable called 'start_time' .... eg. start_time = millis();

Could also have another variable .... to be used as a 'flag' that allows you to quickly check on the status of the waveform .... ie. whether it is 'on' or 'off. The flag could be 'status_flag' ... initially, could be set to '0'.

And whenever you turn on the waveform, your code can then make that flag become '1' (instead of 0). So whenever you set the status_flag to '1' - which is simply for our own convenience - (which means waveform is being produced), then get the software to keep looking at the difference between the arduino time (which keeps running) and the reference time..... that is:

millis() - start_time.

If the above value (ie. a difference) becomes greater or equal to 300 millisec, then use a code to set status_flag back to '0', and also disable the waveform.

The other thing you need to think about is ....... if you press the button to start the waveform, and you then press the button again before the 300 millisec timer completes its time-out function. Or if you use 'lightning speed' to push the same button a few times before that 300 millisec duration runs out. So this just means...... make sure you have all bases (all cases) covered, in case it is important, which is also to avoid glitches or unexpected things - due to not having covered all cases.

Thank you very much for your help. I read over the resources that Robin suggested and I tried to implement Southpark's solution, however, my square wave still only turns off when I press the button a second time and I'm not entirely sure why. I suspect it has something to do with the way I'm checking the elapsed time, but everything that I try to change this fails to change the program's behavior.

/* square wave when button pushed w/ debounce */

// pin setup
const int squarePin = 10;
const int buttonPin = 2;

// other variables
boolean squareOn = false;
long lastUpdateTime = 0;
unsigned long startTime;
unsigned long currentTime;
const unsigned long period = 300;  //the value is a number of milliseconds
int statusFlag = 0;
const long debounce = 200;
int squareState = 0;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(squarePin, OUTPUT);
}

void loop() {
  // check for button pushed (on/off control). 
  boolean buttonPushed = (digitalRead(buttonPin) == LOW);  
 
  // debounce button
  if (buttonPushed && millis() - lastUpdateTime > debounce) {  
    squareOn = !squareOn;
    lastUpdateTime = millis();
  }
  
  if (squareOn){
     startTime = millis();
     statusFlag = 1;
        if(squareState == 0){   
           digitalWrite(squarePin, LOW);
           squareState = 1;
        }
        else{
           digitalWrite(squarePin, HIGH);
           squareState = 0;
        }
  }else{
    digitalWrite(squarePin, 0);  // output GND when button hasnt been pushed
  }
  
  if(statusFlag == 1){
    currentTime = millis();
    if((currentTime - startTime)>= period){
         squareOn = false;
         statusFlag = 0; 
         startTime = currentTime;  
    }
  }
}

You have all these variables and I don't understand the role of each of them
squareOn
statusFlag
squareState

I wonder if this piece

  if(statusFlag == 1){
    currentTime = millis();
    if((currentTime - startTime)>= period){
         squareOn = false;
         statusFlag = 0;
         startTime = currentTime; 
    }
  }

can't be simplified to this

    if((millis() - startTime)>= period){
         squareOn = false;
         statusFlag = 0;
    }

and I wonder if this line in this piece is the cause of your problem
startTime = currentTime;

...R

Thank you for your help, but unfortunately even with your suggestions I can only get the square wave to turn off via a second button press. I have added more comments to my code in the hopes of explaining what I'm trying to use each variable for.

/* square wave when button pushed w/ debounce */

// pin setup
const int squarePin = 10;
const int buttonPin = 2;

// other variables
boolean squareOn = false;         //checks if button has been pressed
long lastUpdateTime = 0;          //for button debouncing
unsigned long startTime;          //time that square wave begins
unsigned long currentTime;
const unsigned long period = 300;  //miliseconds until square wave turns off
int statusFlag = 0;               //when raised, a time check is needed   
const long debounce = 200;       //for debouncing button
int squareState = 0;             //toggles square between high and low

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(squarePin, OUTPUT);
}

void loop() {
  // check for button pushed (on/off control). white noise stops when button pushed again.
  boolean buttonPushed = (digitalRead(buttonPin) == LOW);  
 
  // debounce button
  if (buttonPushed && millis() - lastUpdateTime > debounce) {  
    squareOn = !squareOn;
    lastUpdateTime = millis();
  }
  
  if (squareOn){
     startTime = millis();
     statusFlag = 1;
        if(squareState == 0){   
           digitalWrite(squarePin, LOW);
           squareState = 1;
        }
        else{
           digitalWrite(squarePin, HIGH);
           squareState = 0;
        }
  }else{
    digitalWrite(squarePin, 0);  // output GND when button hasnt been pushed
  }
  
  if((millis() - startTime)>= period){
     squareOn = false;
     statusFlag = 0;
  }
}

Move startTime = millis() to the place where you detect the button press. When it is in the if(squareon) block it is constanly being reset and you won't time out.

if (buttonPushed && millis() - lastUpdateTime > debounce) {  
    squareOn = !squareOn;
    lastUpdateTime = millis();
    startTime = millis();
  }
  
  if (squareOn){
     //startTime = millis();
     statusFlag = 1;

sciencechick123:
Thank you for your help, but unfortunately ...

You did not respond to my request for an explanation of your 3 variables.

And my suggestions were just things I wanted you to think about - because you know better than I what you are trying to do.

...R

I made some changes to eliminate some of my variables as, upon further consideration, I think some of them may have been unnecessary.

Additionally, I have modified my code to function more like that shown in the Demonstration code for doing several things at the same time as I thought this might make it easier for me to catch whatever logical error I'm still making.

Yet, despite all of these changes, my code still only turns the square wave output off when I press the button a second time and I am not sure why. Any further help about what my logical error might be would be greatly appreciated.

// pin setup
const int squarePin = 10;
const int buttonPin = 2;

// other variables
boolean squareOn = false;             //TRUE for valid (debounced) button press
unsigned long startTime = 0;          //time that square wave begins, also used for debouncing
unsigned long currentTime = 0;
const unsigned long period = 300;     //miliseconds until square wave turns off
const long debounce = 200;            //for debouncing button
int squareState = 0;                  //toggles square wave between high and low

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(squarePin, OUTPUT);
}

void loop() {

  readButton();
  
  if(squareOn){   //button pressed
    currentTime = millis();
    if((currentTime - startTime)>= period){
      digitalWrite(squarePin, 0);  // output GND after 300ms have passed
    }else{
      squareWaveOn();
    }
  }else{    //button not pressed
    digitalWrite(squarePin, 0);  // output GND when button hasnt been pushed      
  }
}

void readButton() {
 
  // check for button pushed (on/off control)
  boolean buttonPushed = (digitalRead(buttonPin) == LOW); 
 
  if (buttonPushed && millis() - startTime > debounce) {
    if (digitalRead(buttonPin) == LOW) {
      squareOn = !squareOn;
      startTime = millis();
    }
  }
}

void squareWaveOn() {
  if(squareState == 0){   
     digitalWrite(squarePin, LOW);
     squareState = 1;
  }else{
     digitalWrite(squarePin, HIGH);
     squareState = 0;
 }
}

For some reason you have avoided (twice) responding to my request to explain some of your variables and I suspect if you had done so you would have figured out the problem yourself. I am tempted to ask you again. However, have a look at this version - it compiles but I have not tested it. To my mind the logic is easier to see.

I can't figure if your readButton() function does what you think - but I have not changed it.

// pin setup
const int squarePin = 10;
const int buttonPin = 2;

// other variables
boolean generatePulses = false;             //TRUE for valid (debounced) button press
unsigned long startTime = 0;
unsigned long currentTime = 0;
const unsigned long period = 300;     //miliseconds until square wave turns off
const unsigned long debounce = 200;            //for debouncing button
byte squareState = 0;                  //toggles square wave between high and low

void setup() {
    pinMode(buttonPin, INPUT_PULLUP);
    pinMode(squarePin, OUTPUT);
}

void loop() {
    currentTime = millis();
    readButton();
    makePulses();
    checkForTimeout();
}

void checkForTimeout() {

    if((currentTime - startTime)>= period){
        generatePulses = false;
    }

}

void readButton() {

    // check for button pushed (on/off control)
    boolean buttonPushed = (digitalRead(buttonPin) == LOW);

    if (buttonPushed && millis() - startTime > debounce) {
        if (digitalRead(buttonPin) == LOW) {
            generatePulses = true;
            startTime = millis();
        }
    }
}

void makePulses() {
    if (generatePulses == true) {
        if(squareState == 0){
            digitalWrite(squarePin, LOW);
            squareState = 1;
        }else{
            digitalWrite(squarePin, HIGH);
            squareState = 0;
        }
    }
    else {
        digitalWrite(squarePin, LOW); // make sure it is off
    }
}

...R

Wait, wait, wait! There a logic issue with this program! Here's the sequence:

Press a button
Start a LED flashing at 2.5Hz (period = 400 ms: on-off-on)
Turn the flashing off after 300 ms.

The time the "square wave" is on is less than a single period! When the program runs correctly, the LED will come on for 1/5th of a second, then turn off.

Perhaps the specification is incorrect. Let's assume 'output the square wave for 3 seconds' and perhaps 'flash at 25Hz'. That way, you can see it flash for a few seconds.

This code below does that. It's just about the same as Robin's (but I leave out button debouncing, cause that's actually a hardware thing.)

const byte inputPin = 2;
const byte outputPin = 10;

boolean inputState;

boolean ledState = false;
boolean isBlinking = false;

unsigned long startWaitMillis = millis();
const long waitMillis = 3000L; // one second

unsigned long lastBlinkMillis = millis();
const long blinkMillis = 40UL;

void setup() {
  pinMode(outputPin, OUTPUT);  // pin --> LED --> resistor --> gnd
  pinMode(inputPin, INPUT_PULLUP);  // pin <-- button <-- gnd

  startWaitMillis = millis();
  lastBlinkMillis = millis();
}

void getInputState() {  // debounce with a capacitor if necessary
  inputState = !digitalRead(inputPin);
}

void loop() {
  getInputState();

  // if the button is pressed (additional presses are suppressed while blinking)
  if (inputState and !isBlinking) {
      isBlinking = true;            // flag to blink LED
      startWaitMillis = millis();   // start timing
  }

  // if it's blinking, turn it on and off every blinkMillis
  if ((millis() - lastBlinkMillis >= blinkMillis) and isBlinking) {
    lastBlinkMillis += blinkMillis;

    ledState = !ledState;           // toggle the LED
    digitalWrite(outputPin, ledState);
  }

  // after waitMillis turn the blinking off
  if (millis() - startWaitMillis >= waitMillis) {
    isBlinking = false;             // when the time is up, stop blinking
    digitalWrite(outputPin, LOW);   // turn the LED off
  }
}

ChrisTenone:
It's just about the same as Robin's

Yours is different because you have everything in loop() whereas I have deliberately put the different parts into functions in the hope that it is clearer.

I do agree that the actual blink timing is missing from the OP's code - I had been thinking we could deal with that when the ON/OFF interval had been sorted out.

...R

  if(squareOn){   //button pressed
    currentTime = millis();
    if((currentTime - startTime)>= period){
      digitalWrite(squarePin, 0);  // output GND after 300ms have passed
    }else{
      squareWaveOn();
    }
  }else{    //button not pressed
    digitalWrite(squarePin, 0);  // output GND when button hasnt been pushed     
  }

Here is (at least part of) your problem. Several errors.

  1. you only check for timeout as the button IS PRESSED. You should check for this all the time, regardless of the button. The button just sets the time.
  2. you set currentTime to millis(), then immediately after check for the time to have passed??

If you don't want a second button press to switch off the pulse (300 ms is very quick anyway) then you don't need to bother with debouncing, just detect the button going down and set the start time. You may be a few ms off due to bounce.

Try this (produces pulses for 300 ms from button being pressed - regardless of when it's released - active LOW):

unsigned long period = 300;
unsigned long startTime;
bool buttonState = HIGH;
void loop() {
  if (digitalRead(buttonPin) == LOW && buttonState == HIGH) { // button goes down
    currentTime = millis(); //  record when this happened.
    buttonState = LOW; // record the state.
  }
  else {
    buttonState = HIGH;
  }

  if (millis() - startTime < period) {  // It's less than 300 ms since the latest button press.
    squareWaveOn();
  }
  else {
    squareWaveOff();
  }
}