Final proper version on Arduino Playground - AvoidDelay this was a draft
Introduction
Why not to use delay(), and how to get two "delays" run independent of each other.
We know:
1: The loop()
runs endlessly and very fast.
2: digitalRead will look at the input at the time it is called only, likewise digitalWrite and output.
3. delay()
will wait - it stops everything (except interrupts) while we count the milliseconds
Attempt 1: When button is pushed, turn a LED ON for 15 secs (15000 millis)
(Note: This is pseudo code, ignoring the syntax of "{" and so on, concentrating on
the flow of the code. Also we are assuming a perfect button without contact bounce.
As it happens, most examples here will work with a "noisy" button,
as the LED timing hides the bounce.)
if digitalread==Down
then digitalWrite ON , delay 15sec , digitalWrite OFF
Results: It works, yes, but looking closely:
The button can change up or down as much as you like in the 15 seconds - it simply is not looked at.
The delay starts counting when button is pushed. If the button is still down when 15 seconds expire, the LED will blink Off for a very short time (the time it takes loop to start again)
Attempt 2: The same with two buttons and two LEDs
if digitalreadA==Down
then digitalWriteA ON , delay 15 sec , digitalWriteA OFF
if digitalreadB==Down
then digitalWriteB ON , delay 15 sec , digitalWriteB OFF
Result: This does NOT WORK the way it usually is intended - whilst A or B is on, we are totally ignoring the other button. This is why delay()
is fatal and very wrong if you need to do more than one and only one thing at a time.
Example 1: Implementing a simple timer
unsigned long Timer ALWAYS use unsigned long for timers, not int
(variable declaration outside setup and loop, of course)
if digitalRead==Down
then "set timer" Timer = millis , digitalWrite ON
if "timer expired" millis-Timer >= 15000UL
then digitalWrite Off
(the "UL" after the number is a syntax detail that is important when
dealing with large numbers in millis and micros, therefore it
is shown although this is pseudo code)
Result: It works just as well as the delay version, but slightly differently:
The timer is constantly reset as long as the button is pushed. Thus only when releasing does it start "count" the 15 seconds. If you push shortly inside the 15 seconds period, it starts counting from anew. Notice in particular digitalRead is looking at the input as fast as loop() runs.
We are redundantly doing digitalWrite OFF,even when the LED is not lit for every loop() pass, and likewise digitalWrite ON as long as the button is pushed. This has no ill effects.
Example 2: as 1, but With two buttons and LEDs: Just "repeat" the above.
unsigned long TimerA ALWAYS use unsigned long for timers.
unsigned long TimerB
if digitalReadA==Down
then TimerA= millis() , digitalWriteA ON
if millis()-TimerA >= 15000UL
then digitalWriteA Off
if digitalReadB==Down
then TimerB= millis() , digitalWriteB ON
if millis()-TimerB >= 15000UL
then digitalWriteB Off
Result: This works! Two seperate "timers start and stop independently.
As shown in the single button/LED case, we are done with handling the A case in just a few tens of microseconds, whether we are counting seconds or not, and thus free to handle the B case. We thus examine and react to either buttons hundreds of times each millisecond.
Interesting sideissue: The 4 if statements can be freely rearranged - the end effect is the same. (There are some subtle differences only active for the few microseconds until loop() goes round again.)
The effect that the timer is to start on button push or button release, or if a button should be ignored once the timer has started may be a requirement. Timer code can be made to handle all variations by storing the desired state and skipping digitalRead. This will work fine with two buttons, too.
Example 3: Timer with State change
boolean LEDon = false
unsigned long Timer ALWAYS use unsigned long
if not LEDon and digitalRead==Down
then "set timer" Timer= millis , digitalWrite ON , LEDon = true
if LEDon and millis-Timer >= 15000UL
then digitalWrite Off , LEDon = false
Result: This will not do digitalRead or reset the timer, once the LED has turned on, ie we are starting the timer on button push (like attempt 1 earlier). Likewise it only tests the timer if the LED is on, and only turns it off once (which make very little difference, so I would normally omit the LEDon test in the 2nd part)
Example 4: One button, one LED - each with their own timer; A more usefull(?) example of doing two timing things "at once"
boolean LEDon = false
boolean buttondown = false
unsigned long LEDtimer = 0 ALWAYS use unsigned long. Not int.
unsigned long buttontimer = 0
if millis - buttontimer >= 5UL "debounce 5 millisec"
and not buttondown and digitalReadButton==Down
then buttondown = true , buttontimer = millis
if millis - buttontimer >= 5UL
and buttondown and digitalReadButton==Up
then buttondown = false , buttontimer = millis
if not LEDon and buttondown "can use: not buttondown"
then LEDtimer = millis , digitalWrite ON , ledon = true
if millis - LEDtimer >= 15000UL
then digitalWrite Off , LEDon = false
Result: The button timer will purposfully skip digitalRead of the button for 5 millisec after it has changed. This filters away the "noise" in any mechanical button. The Led on/off code can be set to trigger on button down or up.
A challenge
If you use a previousbuttonup
indicator instead of LEDon, you can ensure that the button has to be released before a new timer cycle can start - but that is left as an exercise, and is more about state transition than about timers.
So, this was the last time you used delay, right? (there will be exceptions, of course)