Introduction
A common question is to ask how to flash 2 (or more) LEDs at the same time. Typically the person asking will be using delay and will be experiencing the main problem with delay: it blocks and stops anything else from happening.
This tutorial contains several different approaches to the problem of flashing 2 or more LEDs at the same time with independent timing for each LED. The basic principles for each can all be adapted for other situations requiring multiple timers working independently without blocking. All the ideas presented demonstrate the principles of using millis() for timing, and for collaborative, multi-tasking code.
Thanks
In addition to the people who contributed directly to this tutorial the following also gave helpful advice and comments: @killzone_kid , @Koepel , @dlloyd , @anon57585045 , @dougp. Thank you all for your contributions and interest.
C style code
My code below flashes 2 LEDs, one connected to pin 2 and one to Pin 3, each with a suitable resistor (220 Ohms or thereabouts) to 0V. Each LED flashes independently of the other.
Notes
I have used separate functions called from the loop function to illustrate putting code into functions rather than trying to do everything in the loop function.
There are 2 different flash functions, each uses millis() independently for timing. The first, flashEqualOnOffPin2(), flashes the LED on pin 2 with equal on and off times. The second, flashUnequalOnOffPin3(), flashes the LED on pin 3 and allows the on and off times to be different.
Having 2 functions illustrates having non-blocking code called from the loop function. At no point does any part of any function wait for anything, it just checks to see if it is time to change the state of a pin and if it is then it changes the pin then returns, otherwise it just returns having done nothing.
To flash more LEDs copy the relevant flash function, change its name and the pin number, paste it into your code and call it from the loop function.
There is a lot that can be done to improve this code, such as having an array for the pin numbers, having an array for the on and off times for each pin, having one function for flashing everything and passing the required pin number, on and off times to it. Each of these improvements would be worth exploring but each would also complicate the code. My aim was to make the code simple for beginners.
Tested on a Nano Every, should work on most if not all Arduino boards.
If the comments in the code do not adequately explain it then please ask.
Complete sketch
const int LED_PIN2 = 2;
const int LED_PIN3 = 3;
#define LED_ON HIGH
#define LED_OFF LOW
void setup() {
Serial.begin(115200);
pinMode(LED_PIN2, OUTPUT);
pinMode(LED_PIN3, OUTPUT);
digitalWrite(LED_PIN2, LED_OFF); // Give the output pins a definite, known state at start up
digitalWrite(LED_PIN3, LED_OFF);
Serial.println("Setup complete");
}
void loop() {
flashEqualOnOffPin2();
flashUnequalOnOffPin3();
}
void flashEqualOnOffPin2() {
uint32_t currentMillis = millis();
static uint32_t lastMillis;
const uint32_t onOffTime = 500; //Duration for LED to be on or off before changing state, in miliseconds
if (currentMillis - lastMillis >= onOffTime) {
lastMillis += onOffTime;
if (digitalRead(LED_PIN2) == LED_ON) {
digitalWrite(LED_PIN2, LED_OFF);
} else {
digitalWrite(LED_PIN2, LED_ON);
}
}
}
void flashUnequalOnOffPin3() {
uint32_t currentMillis = millis();
static uint32_t lastMillis;
const uint32_t onTime = 1750; //Duration for LED to be on, in miliseconds
const uint32_t offTime = 350; //Duration for LED to be off, in miliseconds
if (digitalRead(LED_PIN3) == LED_ON) { // Read the current state of pin 3
if (currentMillis - lastMillis >= onTime) { // Pin 3 is high (LED on), check on time and turn LED off if time has expired
lastMillis += onTime; // Add the value of onTime to lastMillis ready to start the next timing interval
digitalWrite(LED_PIN3, LED_OFF); // Turn LED off
}
} else {
if (currentMillis - lastMillis >= offTime) { // Pin 3 is low (LED off), check off time and turn LED on if time has expired
lastMillis += offTime; // Add the value of offTime to lastMillis ready to start the next timing interval
digitalWrite(LED_PIN3, LED_ON); // Turn LED on
}
}
}
Simulation
Edit: Code modified for clarity, with thanks to @camsysca for suggested improvements.
Here is a basic entry level C++ like class based version
class FlashingLed {
private:
byte ledPin;
unsigned long onTime, offTime, lastMillis;
public:
FlashingLed(const byte pin, const unsigned long on, const unsigned long off) : ledPin(pin), onTime(on), offTime(off) {}
void begin() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH); // Turn LED on
lastMillis = millis(); // remember when we did so
}
void flashWhenNeeded() {
if (digitalRead(ledPin) == HIGH) { // if the led is ON a it's the right time, turn it off (HIGH is on)
if (millis() - lastMillis >= onTime) { // check on time and turn LED off if time has expired
lastMillis += onTime; // Add the value of onTime to lastMillis ready to start the next timing interval
digitalWrite(ledPin, LOW); // Turn LED off
}
} else { // LED is off, if it's the right time, turn it on
if (millis() - lastMillis >= offTime) { // check off time and turn LED on if time has expired
lastMillis += offTime; // Add the value of offTime to lastMillis ready to start the next timing interval
digitalWrite(ledPin, HIGH); // Turn LED on
}
}
}
};
FlashingLed leds[] = {
{2, 500, 500}, // pin 2, 500ms ON and 500 ms OFF
{3, 1750, 350} // pin 3, 1750ms ON and 350ms OFF
};
void setup() {
Serial.begin(115200);
Serial.println(F(__FILE__));
for (auto &l : leds) l.begin();
Serial.println("Setup complete");
}
void loop() {
for (auto &l : leds) l.flashWhenNeeded();
}
An example similar to Perry's, just a bit different:
//LarryD
//********************************************^************************************************
// Version YY/MM/DD Description
// 1.00 22/11/22 Running sketch
//
//
//A Sketch to toggle two LEDs using the timing below
//wired so +5V turns LED ON LED
#define LEDon HIGH //[Output Pin]---[220R]---[A->|-K]---GND
#define LEDoff LOW
const byte LED_PIN2 = 2;
const byte LED_PIN3 = 3;
//*********************
//Timing stuff
unsigned long LED2Time;
unsigned long LED3Time;
const unsigned long LED2OnOffInterval = 500;
const unsigned long LED3OnTimeInterval = 1750;
const unsigned long LED3OffTimeInterval = 350;
//at powerup, LED3 is OFF
unsigned long LED3Interval = LED3OnTimeInterval;
//********************************************^************************************************
void setup()
{
Serial.begin(115200);
pinMode(LED_PIN2, OUTPUT);
pinMode(LED_PIN3, OUTPUT);
//power up conditions
digitalWrite(LED_PIN2, LEDon);
digitalWrite(LED_PIN3, LEDon);
//initialize our 2 TIMERs
LED2Time = millis();
LED3Time = millis();
} //END of setup()
//********************************************^************************************************
void loop()
{
checkLED2();
checkLED3();
//****************************************
// Other non blocking code goes here
//****************************************
} //END of loop()
//********************************************^************************************************
void checkLED2()
{
//**************************************** LED2 T I M E R
//is it time to toggle LED2 ?
if (millis() - LED2Time >= LED2OnOffInterval)
{
//restart the TIMER
LED2Time = millis();
//current state of our LED
byte LED2State = digitalRead(LED_PIN2);
//toggle the LED
digitalWrite(LED_PIN2, !LED2State);
} //END of this TIMER
} //END of checkLED2()
//********************************************^************************************************
void checkLED3()
{
//**************************************** LED3 T I M E R
//is it time to toggle LED3 ?
if (millis() - LED3Time >= LED3Interval)
{
//restart the TIMER
LED3Time = millis();
//current state of our LED
byte LED3State = digitalRead(LED_PIN3);
//toggle the LED
digitalWrite(LED_PIN3, !LED3State);
//*****************
//is the LED currently ON ?
if (!LED3State == LEDon)
{
//set this TIMER to the LED ON duration
LED3Interval = LED3OnTimeInterval;
}
//*****************
//LED is currently OFF
else
{
//set this TIMER to the LED OFF duration
LED3Interval = LED3OffTimeInterval;
}
} //END of this TIMER
} //END of checkLED3()
//********************************************^************************************************
//TIMERs are in milliseconds or microseconds
//LarryD
//********************************************^************************************************
// Version YY/MM/DD Description
// 2.00 22/11/12 Running sketch
// 2.50 22/11/12 Changed some variable names
//
//wired so +5V turns LED ON LED
#define LEDon HIGH //[Output Pin]---[220R]---[A->|-K]---GND
#define LEDoff LOW
const byte redLEDpin = 2;
const byte greenLEDpin = 3;
const unsigned long redLEDonOffInterval = 500;
const unsigned long greenLEDonInterval = 1750;
const unsigned long greenLEDoffInterval = 350;
//********************************************^************************************************
//a structure that creates TIMER objects
struct makeTimer
{
//previousTime = the time this TIMER was (re)started
//waitInterval = delay time (ms/us)we are looking for
//restart = do we start this TIMER again and again
//enableFlag = is this TIMER enabled/allowed to be accessed
//timeType = true = millis(), false = micros()
//
//**********************
//For each TIMER object we need to define it:
//Example:
// makeTimer myTimer = //give the TIMER a name "myTimer"
// {
// 0, 200UL, true, true, true //previousTime, waitInterval, restart, enableFlag, timeType
// };
// You have access to:
// Variables: myTimer.previousTime, myTimer.waitInterval, myTimer.restart, myTimer.enableFlag, myTimer.timeType,
// Functions: myTimer.checkTime(), myTimer.enableTimer(), myTimer.expireTimer()
//**********************
unsigned long previousTime;
unsigned long waitInterval;
bool restart;
bool enableFlag;
bool timeType;
unsigned long currentTime;
//******************************************
//Function to check if this TIMER has expired ex: myTimer.checkTime();
bool checkTime()
{
if (timeType == true)
{
currentTime = millis();
}
else
{
currentTime = micros();
}
//has this TIMER expired ?
if (enableFlag == true && currentTime - previousTime >= waitInterval)
//Note: if delays of < 2 millis are needed, use micros() and adjust waitInterval as needed
{
//should this TIMER start again?
if (restart)
{
//get ready for the next iteration
previousTime = currentTime;
}
//TIMER has expired
return true;
}
//TIMER has not expired or is disabled
return false;
} //END of checkTime()
//******************************************
//Function to enable and initialize this TIMER, ex: myTimer.enableTimer();
void enableTimer()
{
enableFlag = true;
//initialize previousTime to current millis() or micros()
if (timeType == true)
{
previousTime = millis();
}
else
{
previousTime = micros();
}
} //END of enableTimer()
//******************************************
//Function to force this TIMER to expire ex: myTimer.expireTimer();
void expireTimer()
{
//force this TIMER to expire now
previousTime = currentTime - waitInterval;
} //END of expireTimer()
}; //END of structure “makeTimer”
//********************************************^************************************************
//Let's create and and initialize 2 "makeTimer" objects
//***************************
makeTimer redLEDtimer = //create redLEDtimer
{
0, redLEDonOffInterval, true, true, true //previousTime, waitInterval, restart, enableFlag, true=millis/false=micros
};
//***************************
makeTimer greenLEDtimer = //create greenLEDtimer
{
0, greenLEDoffInterval, true, true, true //previousTime, waitInterval, restart, enableFlag, true=millis/false=micros
};
//********************************************^************************************************
void setup()
{
Serial.begin(115200);
pinMode(redLEDpin, OUTPUT);
pinMode(greenLEDpin, OUTPUT);
//power up conditions
digitalWrite(redLEDpin, LEDoff);
digitalWrite(greenLEDpin, LEDoff);
//initialize our 2 TIMERs
redLEDtimer.expireTimer();
greenLEDtimer.expireTimer();
} //END of setup()
//********************************************^************************************************
void loop()
{
//***************************
//is it time to toggle the red LED ?
if (redLEDtimer.checkTime())
{
serviceRedLED();
}
//***************************
//is it time to toggle the green LED ?
if (greenLEDtimer.checkTime())
{
serviceGreenLED();
}
//**********************************
//Put other non-blocking code here
//**********************************
} //END of loop()
//********************************************^************************************************
void serviceRedLED()
{
//current state of our red LED
byte redLEDstate = digitalRead(redLEDpin);
//is the LED ON ?
if (redLEDstate == LEDon)
{
digitalWrite(redLEDpin, LEDoff);
}
else
{
digitalWrite(redLEDpin, LEDon);
}
} //END of serviceRedLED()
//********************************************^************************************************
void serviceGreenLED()
{
//current state of our green LED
byte greenLEDstate = digitalRead(greenLEDpin);
//is the LED ON ?
if (greenLEDstate == LEDon)
{
digitalWrite(greenLEDpin, LEDoff);
//set this TIMER to the LED OFF duration
greenLEDtimer.waitInterval = greenLEDoffInterval;
}
else
{
digitalWrite(greenLEDpin, LEDon);
//set this TIMER to the LED ON duration
greenLEDtimer.waitInterval = greenLEDonInterval;
}
} //END of serviceGreenLED()
//********************************************^************************************************
I think the ideas in this thread are worthy of a new example in the IDE, per
Here's my effort:
/*
BlinkWithoutDelayMore -- Blink without Delay for more LEDs, using Functions
Turns on and off a light emitting diode (LED) connected to a digital pin,
without using the delay() function. This means that other code can run at the
same time without being interrupted by the LED code.
State diagram: tinyurl.com/BWODMdiag
The circuit:
- Use the onboard LED.
- Note: Most Arduinos have an on-board LED you can control. On the UNO, MEGA
and ZERO it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN
is set to the correct LED pin independent of which board is used.
If you want to know what pin the on-board LED is connected to on your
Arduino model, check the Technical Specs of your board at:
https://www.arduino.cc/en/Main/Products
created 2005
by David A. Mellis
modified 8 Feb 2010
by Paul Stoffregen
modified 11 Nov 2013
by Scott Fitzgerald
modified 9 Jan 2017
by Arturo Guadalupi
modified by 7 Mar 2023
by David Forrest
This example code is in the public domain.
For a tutorial on how this works, please read:
https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay
Simulation: https://wokwi.com/projects/358554786853847041
This was written for the discussion at
https://forum.arduino.cc/t/the-fact-that-something-has-been-used-for-a-long-time-is-in-no-way-an-argument-for-making-it-the-same-for-another-100-years/1097951/24
on improving the BlinkWithoutDelay example.
For other ways of doing multiple things at once please read:
https://forum.arduino.cc/t/flashing-multiple-leds-at-the-same-time/1065564/
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/now-for-two-at-once
*/
// constants won't change. Used here to set a pin number:
const int ledPin = LED_BUILTIN; // the number of the LED pin
const int ledPin2 = 3; // the number of the second LED pin
// Variables will change:
int ledState = LOW; // ledState used to set the LED
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time LED was updated
// constants won't change:
const long interval = 1000; // interval at which to blink (milliseconds)
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
// here is where you'd put code that needs to be running all the time.
checkIfItIsTimeToBlinkAndDoIt();
// ^^ this function separates the BlinkWithoutDelay core
// into a function
checkIfItIsTimeToBlinkAndDoIt_variable(500); // function handles a separate LED
}
///////
void checkIfItIsTimeToBlinkAndDoIt() {
// check to see if it's time to blink the LED; that is, if the difference
// between the current time and last time you blinked the LED is bigger than
// the interval at which you want to blink the LED.
// save the current time
unsigned long currentMillis = millis();
// test if it is time yet
if (currentMillis - previousMillis >= interval) {
// save the last time you changed the LED
previousMillis = currentMillis;
// Toggle the ledState variable:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED output to match the ledState variable:
digitalWrite(ledPin, ledState);
} // end interval check
}
void checkIfItIsTimeToBlinkAndDoIt_variable(unsigned long interval) {
// check to see if it's time to blink the LED; that is, if the difference
// between the current time and last time you blinked the LED is bigger than
// the interval at which you want to blink the LED.
// If it is not yet time it immediately returns control back to loop()
// These "local scope" variables mask variables of the same name in other scopes
// this helps keep the global namespace cleaner and easier to maintain
const int ledPin = ledPin2 ; // copy from global constant
// use static for private state variables that will persist across calls
static bool initialized = false;
static int ledState = LOW;
static unsigned long previousMillis ;
// save the current time
unsigned long currentMillis = millis();
if (initialized == false) { //
ledState = LOW;
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, ledState);
initialized = true;
previousMillis = millis();
}
// test if it is time yet
if (currentMillis - previousMillis >= interval) {
// save the last time you changed the LED
previousMillis = currentMillis;
// Toggle the ledState variable:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED output to match the ledState variable:
digitalWrite(ledPin, ledState);
} // end interval check
}
(I reserve the right to edit this code to follow the sim in that Wokwi link. )
In contrast with the other example, my schematic follows BWOD by using LED_BUILTIN on 13, while the other is on pin 3.
What I like about my code is that it builds directly from the venerable BWOD, giving a clear path forward from doing one thing without delay to DoingSeveralThingsAtTheSameTime without delay. I think it would be good to highlight this thread, and the other good tutorials on how to solve the same problem in the code's comments, because comparing different ways to do the same thing helps with learning.
Also pointing at more advanced multitasking tutorials would be helpful: