Arduino: digitalWrite go high at switch press, go low after a set duration

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Hi guys
I am going crazy over this. I need to have a switch, that when pressed turn on a LED (digitalWrite). Then after
a set duration (like 100 ms), it goes low. This should occur, even if the button stays pressed (or is released)

I have tried a thousand things, this is the latest. I really hope somebody can help me out :)

C:
// constants won't change. Used here to set a pin number :
const int ledPin =  13;
const int button = 21;
        
unsigned short timer = 0;   //Create a variable with range 0-65535        
        
        
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(button, INPUT);
}

void loop() {

  delay(1);           //1 millisecond delay so you know how often this loop runs (appx 1000Hz)

  // here is where you'd put code that needs to be running all the time.

  if (digitalRead(button) == HIGH)
  {
    digitalWrite(ledPin, HIGH);       //Turn on LED
    timer = 1000;                     //Turn it off after 1 second

  }
  if (timer) { //Is timer active? (more than 0)
    timer--;    //Decrement by 1
    if (timer == 0) {                  //Did timer run out?
      digitalWrite(ledPin, LOW);      //Turn off LED
    }
  }

}
Moderators note : used code tags
 
Not going to pontificate on "better* ways to do what you want, but try this: (and note that if the button is pressed after timer==0, it will turn on the LED again)

C:
if (digitalRead(button) == HIGH)
  {
    digitalWrite(ledPin, HIGH);       //Turn on LED
    timer = 1000;                     //Turn it off after 1 second
    while (timer--);                   // stay here until timer= 0
   digitalWrite(ledPin, LOW);  //Turn off LED
  }
 

danadak

Joined Mar 10, 2018
4,057
In the future consider that the way the code is written in post 2,
the UP is functionally disabled waiting for the timeout period to
expire. Except for interrupt activity.

Using an interrupt routine on a timer or a software counter for the
timeout period would allow the processor to do "other things",
execute other code.

Just a thought, processor cycles are precious.....:)

The problem with your code in post 1 is you keep re initializing the timer
to 1000 as long as the button is high, therefore timer never completes
its timeout. Use a flag or the actual timer value before writing initialize to
it. If timer value is 0 write initialize to it, otherwise leave it alone, no more
writes to it.

Also your method good to keep the processor doing other tasks, but
its delay time is a function of clock rate/code cycle count. That may or may
not be a consideration for you. How precise do you want the delay ....?

Regards, Dana.
 
Last edited:

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Thanks! I need to be running other stuff at the same time, so any kind of delay() is not good :) I need at least two outputs (digitalWrite).

How would you implement the flag? I am totally zoning out
 

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Thanks, I will try that! Regarding your precision-question, it really doesn't matter. The 100ms could 50ms or more off in both directions.
 
One way of doing this sort of thing which is typical with Arduino is to make use of millis(). To illustrate (and I have not tested this, so I could have made a blunder):

millis() will return a running timer value (and yes it will reset if your program runs for a very, very long time and you should know that). Once you have the "stop watch" value of when a press occurred, you can add a known quantity (the minimum delay, if you will). Subsequently you can simply check the value of millis() and see if the approriate amount of time has elapsed. To keep things clean, you want a flag to know when you have an "active" press to deal with.

C:
const int ledPin =  13;
const int button = 21;
unsigned long starttime;
unsigned long deltime=1000; // 1000 ms delay  
bool switchflag;
  
void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(button, INPUT);
  switchflag=0;
}
void loop() {

  if ((digitalRead(button) == HIGH & (!switchflag) ) ) {
  starttime=millis();
  switchflag=1;
  digitalWrite(ledPin, HIGH);
  }

  if((switchflag==1) & (millis()>(starttime+deltime))){
  digitalWrite(ledPin, LOW);  
  switchflag=0;
  }

/* put additional code lines here */
}
Using this scheme, you are free to continue to execute code as long as you check if the duration is up (at a frequency that you need) with the code lines below. Additionally, if it is tolerable to exceed the exact duration, this approach can work well. Since you are operating within the loop(), it will come around to check each time through and if the time has elapsed, it will take the appropriate action..

C:
  if((switchflag==1) & (millis()>(starttime+deltime))){
  digitalWrite(ledPin, LOW);  
  switchflag=0;
  }
One reason that I did not want to pontificate (and I guess I am now) is because it is more important, I think, that you learn how to accurately follow the code and you were missing that in your first attempt.

Using any kind of blocking call (like delay(xxx)) has the characteristic of halting further program execution until it concludes. This is a distinct disadvantage if the program could and should be executing other code while waiting for an event to conclude.

That being said, if your program does not need to do anything until the event concludes, it is fine to use.

The other perspective goes something like...learn how to do it the best way first because even if the easy way works this time, it might not do so in another project. There is truth to that, but beginning programming can be daunting sometimes.

It is not so much that delay(xxx) is wrong as much as it may not be right for what you want to do. The key is knowing what the code does and being able to follow it through.

[pontification=off]
 

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Hi, thanks for you post. I went through the code, and just tried it. Am I missing something? If the switch is held (stay closed),
the digitalWrite is never set to low.

I really wish the Arduino had a duration-setting in digitalWrite, like it does in tone() - I just can't get it to shut down the LED while the
button is held.

Thanks,
Nicholas
 
Hi, thanks for you post. I went through the code, and just tried it. Am I missing something? If the switch is held (stay closed),
the digitalWrite is never set to low.

I really wish the Arduino had a duration-setting in digitalWrite, like it does in tone() - I just can't get it to shut down the LED while the
button is held.

Thanks,
Nicholas
Nope, you are not missing something. If you press and hold the button down, the code will detect the press, light the LED and 1 sec later, turn the LED off BUT it will also go check the button again and since you are holding it down, it will light the LED again - all of that happens so quickly that you are never seeing the LED go out.

So, what to do about that? Well, one way is to make sure that the button has been released before you check again if it has been pressed. In other words, define a press as a press and release. But, it is not that simple because one human press actually produces a series of switch opening and closing- usually referred to as bounce. So, enter the debounce routine :)

An *easy* way, would be to add something like this:
C:
  if((switchflag==1) & (millis()>(starttime+deltime))){
  digitalWrite(ledPin, LOW);  
  switchflag=0;
  while(digitalRead(button) == HIGH); // <---wait right here until the press is released
  }
Now, when time is up, the LED is turned off, but it will stay right there until the button is released, if it is still pressed. Of course if you have a bad switch or a bad connection such that digitalRead(button) is never LOW, then it will stay on that while line forever.....and now we are back to blocking concerns....and all this just to detect a little button press - who said programming was easy? Actually it is pretty easy, it is the thinking part that is sometimes difficult, at least for me.
 

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Wow, it works!! Thanks for that

I am curious about the while(digitalRead(button)== HIGH); line. Why does it wait there, shouldn't it be a kind
of while-loop or while this then that?

I wonder, will this work for two buttons as well, and will it need seperate booleans?
 
Wow, it works!! Thanks for that

I am curious about the while(digitalRead(button)== HIGH); line. Why does it wait there, shouldn't it be a kind
of while-loop or while this then that?

I wonder, will this work for two buttons as well, and will it need seperate booleans?
You may be used to seeing a while loop formatted differently like below - it's the same command.

C:
while (digitalRead(button==HIGH){
// normally, do stuff here but in this case
// just do nothing until the button is low
// then leave
};
Sounds like you are making progress (...they don't have to be boolean variables, they could just as easily be bytes with different bits representing different buttons).

Good luck and let us know how it goes - and BTW: what is the project that this is for? :)
 

Thread Starter

Nicholas

Joined Mar 24, 2005
139
Hey Raymond

Well, I am toying with the thought of having some shelves/boxes on the wall in the outhouse. I am using a coils to
allow for opening, when a button (or opto/magnetic) a pushed. So, I am afraid that the coil will overheat, thus the
timeout :)

Which actually brings me to the next problem; this will be the last, I will let you off the hook after that :) I tried 'doubling' the
code, to allow for two inputs with two outputs. Works great, but..if one button is held, the other one cannot be pressed :eek:

Edit: is it because the while (button pressed) stalls the loop?

Code:
const int ledPin =  4;
const int ledPin2 =  3;
const int button = 21;
const int button2 = 20;

unsigned long starttime;
unsigned long starttime_2;
unsigned long delay_time = 1000; // 1000 ms delay


bool switchflag_1;
bool switchflag_2;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(button, INPUT);
  pinMode(button2, INPUT);
  switchflag_1=0;
  switchflag_2=0;
}
void loop() {
  if ((digitalRead(button) == HIGH & (!switchflag_1) ) ) {
  starttime=millis();
  switchflag_1=1;
  digitalWrite(ledPin, HIGH);
  }
  if((switchflag_1==1) & (millis()>(starttime+delay_time))){
  digitalWrite(ledPin, LOW);
  while(digitalRead(button) == HIGH);
  switchflag_1=0;
  }

  if ((digitalRead(button2) == HIGH & (!switchflag_2) ) ) {
  starttime_2=millis();
  switchflag_2=1;
  digitalWrite(ledPin2, HIGH);
  }
  if((switchflag_2==1) & (millis()>(starttime_2+delay_time))){
  digitalWrite(ledPin2, LOW);
  while(digitalRead(button2) == HIGH);
  switchflag_2=0;
  }
/* put additional code lines here */
}
 
Last edited:
Opinion->There is some danger in designing the programming sequentially as things tend to get sloppy and unreadable quickly. The better alternative is to design the entire program first and then develop and test segments sequentially.

Nevertheless, here is one approach: Consider that a button can exist in three and only three states:

Eligible to be pressed. It has never been pressed or it has been pressed and released after the delay time, flag=0.

Pressed or released during the delay time, flag=1

Not released after an eligible press and the delay time, flag=2


Try this approach below (excuse any blunders as I am not testing this code):

C:
const int ledPin_1 =  4;
const int ledPin_2 =  3;
const int button_1 = 21;
const int button_2 = 20;

unsigned long starttime_1, starttime_2;
unsigned long delay_time = 1000; // ms delay, could make two of these

// switchflag = 0 not active / =1 active press / 2= pressed and not active but not released
byte switchflag_1, switchflag_2;

void setup() {
  pinMode(ledPin_1, OUTPUT);
  pinMode(ledPin_2, OUTPUT);
  pinMode(button_1, INPUT);
  pinMode(button_2, INPUT);
  switchflag_1 = 0;
  switchflag_2 = 0;
}
void loop() {

  // if there is a new button_1 press capture the time and light ledpin_1
  if ((digitalRead(button_1) == HIGH & (switchflag_1 == 0) ) ) {
  starttime_1 = millis();
  switchflag_1 = 1; // pressed but no release detected
  digitalWrite(ledPin_1, HIGH);
  }

  // if there is a new button_2 press capture the time and light ledpin_2
  if ((digitalRead(button_2) == HIGH & (switchflag_2 == 0) ) ) {
  starttime_2 = millis();
  switchflag_2 = 1;
  digitalWrite(ledPin_2, HIGH);
  }

  // check for delay up after press button_1
  if (switchflag_1 == 1) {
  if (millis() > (starttime_1 + delay_time)) {
  digitalWrite(ledPin_1, LOW);
  switchflag_1 = 2; // no release detected
  }
  }

  // check for release after press on button_1 and after delay (so no debounce needed)
  if (switchflag_1 == 2) {
  if (digitalRead(button_1) == LOW) {
  switchflag_1 = 0;
  }
  }

  // check for delay up after press button_2
  if (switchflag_2 == 1) {
  if (millis() > (starttime_2 + delay_time)) {
  digitalWrite(ledPin_2, LOW);
  switchflag_2 = 2;
  }
  }

  // check for release after press on button_2 and after delay (so no debounce needed)
  if (switchflag_2 == 2) {
  if (digitalRead(button_2) == LOW) {
  switchflag_2 = 0;
  }
  }

  /* put additional code lines here */
}
 

danadak

Joined Mar 10, 2018
4,057
1) Each switch must initialize its own starttime in case >1 button pressed
at roughly the same time. Or multiple button sequential pushes.

2) millsi(0 overflows in 50 days approx. If you do not detect for rollover
you could have, with high values starttimes, a long term non functiuonal
result of a button push. You need to detect if startttime > millis() and correct
for that case in your test for time duration. So the button always produces
the same result.

Regards, Dana.
 
1) Each switch must initialize its own starttime in case >1 button pressed
at roughly the same time. Or multiple button sequential pushes.

2) millsi(0 overflows in 50 days approx. If you do not detect for rollover
you could have, with high values starttimes, a long term non functiuonal
result of a button push. You need to detect if startttime > millis() and correct
for that case in your test for time duration. So the button always produces
the same result.

Regards, Dana.
Yeah, I mentioned the rollover in post #7, but it is not a problem at all if you write it correctly (which I did not do in my example in post #13).

Instead of:
C:
if (millis() > (starttime_1 + delay_time))
The correct way of writing it is:
C:
if( (unsigned long)millis()-starttime_1) >= delaytime)
Written this way you don't need to worry about millis() rollover ever (you don't need the type declaration in the listing in post #13, but I am including it for emphasis).

Sorry @Nicholas, silly blunder.
 

MrSoftware

Joined Oct 29, 2013
2,273
Here's one basic way to do it. I haven't compiled the code so it might have bugs, but you will get the idea. This can definitely be improved upon, but hopefully it will give you the general idea of how you can can use an interrupt and counter to control your LED without slowing down the rest of your code much. Maybe the first thing to improve it would be switching the loop counter to instead be a timeout value using millis(). Also it's not the most efficient to digitalWrite() on every loop, but it will get the job done. You can add more logic to not allow the var to reset until the button has been released. Be aware that the mechanical button will have some bounce, which can cause multiple rapid interrupts. So if you see funny things happening, this could be the cause. The best way to debounce is in the physical circuit, but you can also do it in code. Google button debouncing and you'll get a ton of info. Anyway, I hope this helps.

Code:
#define MY_BUTTON_PIN   99
#define MY_LED_PIN      13
#define NUMBER_LOOPS_TO_KEEP_LED_ON  1000

volatile int iLEDOnCount = 0;  /* Always use volatile for vars that are set in interrupt routines */

/* You will get here every time the button state changes according to the mode set in attachInterrupt() */
myInterruptHandler
{
  if( iLEDOnCount <= 0 )  // Don't reset the var if it's already set
  {
    iLEDOnCount = NUMBER_LOOPS_TO_KEEP_LED_ON;
  }
}

setup()
{
  pinMode(MY_BUTTON_PIN, INPUT);
  digitalWrite(MY_BUTTON_PIN, HIGH);  // Use internal pullup, pull pin low when button pressed
  pinMode(MY_LED_PIN, OUTPUT);
  digitalWrite(MY_LED_PIN, LOW);      // Start with LED off

  attachInterrupt(digitalPinToInterrupt(MY_BUTTON_PIN), myInterruptHandler, FALLING);  /* see help for attachInterrupt to choose the best mode */

}

loop()
{
  if( iLEDOnCount > 0 )
  {
    digitalWrite(MY_LED_PIN, HIGH);
    iLEDOnCount--;
  }
  else
  {
    digitalWrite(MY_LED_PIN, LOW);
  }

  /* All the rest of your code keeps executing here, the loop doesn't stop..*/

}
 
Last edited:

Phil-S

Joined Dec 4, 2015
241
Mechanical switches cause more problems than their simplicity disguises. Whole libraries have been written for debouncing.
The Bald Engineer (James Lewis) covers all this and using millis() for non-blocking code. Forward Computing also covers debouncing quite well
When all else fails, the example included in the Arduino IDE bundle, StateChangeDetection, has proved reliable, as have CMOS logic solutions. There are plenty of old examples for using CMOS for all sorts of switch functions, momentary, latched etc.
 
Top