Posted by
| Nick Gammon
Australia (23,122 posts) Bio
Forum Administrator |
Message
|
This question comes up practically every day on the Arduino forum - "how do I blink two LEDs at different rates?" or "how do I turn on and off two motors at different times?".
One of the problems is that beginners look at the "blink" tutorial program, which is:
void setup()
{
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(13, HIGH); // set the LED on
delay(1000); // wait for a second
digitalWrite(13, LOW); // set the LED off
delay(1000); // wait for a second
}
Now this works fine, to blink one LED. But to blink two LEDs you run into problems. It's OK if you want to blink them both at once:
void setup()
{
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(12, HIGH); // set the first LED on
digitalWrite(13, HIGH); // set the second LED on
delay(1000); // wait for a second
digitalWrite(12, LOW); // set the first LED off
digitalWrite(13, LOW); // set the second LED off
delay(1000); // wait for a second
}
Or, if you want to do one after the other:
void setup()
{
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(12, HIGH); // set the first LED on
delay(1000); // wait for a second
digitalWrite(12, LOW); // set the first LED off
delay(1000); // wait for a second
digitalWrite(13, HIGH); // set the second LED on
delay(1000); // wait for a second
digitalWrite(13, LOW); // set the second LED off
delay(1000); // wait for a second
}
But what if you want to blink the two LEDs at different rates? Like, once a second for LED 1 and twice a second for LED 2?
This is where the delay function doesn't really help.
Let's look at an analogy. Say you want to cook breakfast. You need to cook:
- Coffee - takes 1 minute
- Bacon - takes 2 minutes
- Eggs - takes 3 minutes
Now a seasoned cook would NOT do this:
- Put coffee on. Stare at watch until 1 minute has elapsed. Pour coffee.
- Cook bacon. Stare at watch until 2 minutes have elapsed. Serve bacon.
- Fry eggs. Stare at watch until 3 minutes have elapsed. Serve eggs.
The flaw in this is that whichever way you do it, something is going to be cooked too early (and get cold).
In computer terminology this is blocking. That is, you don't do anything else until the one task at hand is over.
What you are likely to do is this:
- Start frying eggs. Look at watch and note the time.
- Glance at watch from time to time. When one minute is up then ...
- Start cooking bacon. Look at watch and note the time.
- Glance at watch from time to time. When another minute is up then ...
- Put coffee on. Look at watch and note the time.
- When 3 minutes are up, everything is cooked. Serve it all up.
In computer terminology this is non-blocking. That is, keep doing other things while you wait for time to be up.
So, adapting this idea to blinking two LEDs at different rates, we can't afford to use:
... because this blocks. That is, you can't be doing anything else during those 1000 milliseconds.
Instead we note the time, like this:
unsigned long when_eggs_started; // declare variable
...
when_eggs_started = millis (); // note the time now
Later on we can see if 3 minutes are up by subtracting when we started from the time now. This, by the way, works even if the millis () function call wraps around after approximately 50 days.
if ((millis () - when_eggs_started) >= (1000UL * 60 * 3))
{
// eggs are cooked
}
The "UL" after 1000 is to force the compiler to make an "unsigned long" literal, which can hold a larger number than the default of an int (integer) which can only hold -32768 to +32767.
Note that millis () returns milliseconds, thus 1000 milliseconds are a single second. So 1000 milliseconds * 60 * 3 is three minutes.
The full sketch that blinks two LEDs at different rates is:
// Which pins are connected to which LED
const byte greenLED = 12;
const byte redLED = 13;
// Time periods of blinks in milliseconds (1000 to a second).
const unsigned long greenLEDinterval = 500;
const unsigned long redLEDinterval = 1000;
// Variable holding the timer value so far. One for each "Timer"
unsigned long greenLEDtimer;
unsigned long redLEDtimer;
void setup ()
{
pinMode (greenLED, OUTPUT);
pinMode (redLED, OUTPUT);
greenLEDtimer = millis ();
redLEDtimer = millis ();
} // end of setup
void toggleGreenLED ()
{
if (digitalRead (greenLED) == LOW)
digitalWrite (greenLED, HIGH);
else
digitalWrite (greenLED, LOW);
// remember when we toggled it
greenLEDtimer = millis ();
} // end of toggleGreenLED
void toggleRedLED ()
{
if (digitalRead (redLED) == LOW)
digitalWrite (redLED, HIGH);
else
digitalWrite (redLED, LOW);
// remember when we toggled it
redLEDtimer = millis ();
} // end of toggleRedLED
void loop ()
{
// Handling the blink of one LED.
if ( (millis () - greenLEDtimer) >= greenLEDinterval)
toggleGreenLED ();
// The other LED is controlled the same way. Repeat for more LEDs
if ( (millis () - redLEDtimer) >= redLEDinterval)
toggleRedLED ();
/* Other code that needs to execute goes here.
It will be called many thousand times per second because the above code
does not wait for the LED blink interval to finish. */
} // end of loop
Of course, you don't have to blink LEDs. The functions toggleGreenLED and toggleRedLED could be turning on and off motors, taking temperature readings, whatever you want to do.
Also you can do more than two things. You can extend this idea to do as many things as you want. At the part where the comment "Other code that needs to execute goes here" is, you could be doing other things like testing if a switch button is pressed. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|