Lesson 8: Rate Blinking LEDs
Table of Contents
- Writing code
- Multi-rate blinking: an initial approach
- Multi-rate blinking: an object-oriented approach
- Multi-rate blinking: using an external class
- Next Lesson
In this lesson, we will learn how to blink multiple LEDs at different rates and build our first C/C++ class, which will greatly simplify our code and, as an added bonus, reduce its size by eliminating code redundancy!
As with our previous lesson on crossfading RGB LEDs, this lesson involves simple circuits but comparatively complex code. Often, when using microcontrollers, our code is the magic sauce—the circuits are straightforward but the code can be complicated.
The canonical and beloved first Arduino sketch, Blink, enables beginners to quickly build and write code for a circuit. The code looks something like this, which we covered in our own Blink lesson:
Blink is easy. It’s gratifying. But… it sets up a flawed mental model about how to structure programs and when/how to use
What if you want to blink multiple LEDs at different rates? How would you do this with
delay()? Well, you can’t. While in a
delay(), your program is literally doing nothing (well, it’s stuck in a
while loop waiting for the delay period to finish but that’s essentially nothing).
So, what should we do instead? We eliminate all delays and track time and state using state machines. Alternatively, we could use interrupts (but we’ll cover that later).
You’ll need three LEDs—we’ll use red, blue, and yellow but you can use whatever LEDs you want—along with current limiting resistors, a breadboard, and an Arduino.
|Breadboard||Arduino||Three LEDs||Three Resistors|
|Breadboard||Arduino Uno, Leonardo, or similar||Three LEDs (We’ll use a red, blue, and yellow)||Three 220Ω Resistors|
The circuit is the same as our basic LED blink lesson but we duplicate it three times—once for each LED. We could use any of the Arduino’s GPIO pins, but we chose Pins 2, 5, and 9 simply to space out the circuit and make it easier to read.
By now, this circuit and wiring should feel familiar. Indeed, you may not even need a wiring diagram like this to help!
Recall that each LED color has a unique forward voltage Vf. In this case, our red LED has a Vf between 2.0-2.4V, our blue LED between 3.0-3.4V, and our yellow LED between 2.0-2.4V. To simplify things, we’ll use the same value resistor for each LED circuit (220Ω); however, you could use different resistors for each LED to try and balance brightness levels.
The LED assorted pack from Sparkfun.com, which shows the Vdrop (or Vf) for each LED.
We are going to implement two multi-rate blinking approaches:
- The first introduces the overall idea of using state tracking variables and state change timestamps to control timing output without
- The second will use the same approach but simplified using object-oriented programming. Here, we’ll also show you how to make and use a
C++class in Arduino.
Multi-rate blinking: an initial approach
For our initial approach, we need four things for each LED:
- Pin number: An integer value specifying the output pin.
- Blink interval: A per-LED interval that controls how long to turn on (
HIGH) and off (
LOW) the LED.
- Toggle timestamp: The last time the LED was toggled from
- Current LED state: The current LED state (either
LOW), which is toggled every blink interval.
For the blink interval, we’ll use
const variables like
Toggle timestamps and LED states
For the toggle timestamps and LED states, we’ll use variables like
_led1State. We can toggle
ledState simply by:
ledState = !ledState.
To capture timestamps, we’ll use Arduino’s
millis() function, which returns “the number of milliseconds passed since the Arduino board began running the current program” as an
On the Arduino, the
unsigned long data type is 32 bits (4 bytes), which ranges from
4,294,967,295 (2^32 - 1). Thus,
millis() will overflow—go back to zero and begin again—after
4,294,967,295 milliseconds (or approximately 50 days). If you need more precise timing, you could use
micros(), which provides microsecond resolution rather than millisecond resolution but
micros() overflows every ~70 minutes.
Blinking without delays logic
We then use the same general logic as the “blinking without delays” covered previously for each LED:
Tracking timestamps and overflow
This sub-section provides more background on tracking timestamps and overflow. Feel free to skip this if you’d like.
An important question to ask is: what happens if my Arduino needs to run for more than ~50 days and I’m relying on
millis() to track time? Will the subtraction math (
currentTimestampMs - _lastToggledTimestampMs >= LED_BLINK_INTERVAL_MS) still work? Even during an overflow condition?
Great question! And yes, it will still work! And the reason is because we are using
unsigned data types, which inform the compiler that these values can never be
< 0. This is crucial.
For example, imagine that
0xFFFFFFFA in hexadecimal (32 bits), which is 5 milliseconds from overflow. And then imagine that
millis() overflows (returns back to 0) and
currentTimestampMs becomes, say,
0x00000001. So, our subtraction is then:
0x00000001 - 0xFFFFFFFA. There is
7 milliseconds difference between these two numbers, so we’d like the subtraction to result in
1. 0xFFFFFFFA 2. 0xFFFFFFFB 3. 0xFFFFFFFC 4. 0xFFFFFFFD 5. 0xFFFFFFFE -. (0xFFFFFFFF) <-- overflow 6. 0x00000000 7. 0x00000001
And this is what we get! Feel free to experiment with this yourself by running the code below on you Arduino. We also suggest this article by James Lewis about
millis(), overflow, and arithmetic for more background.
The full code for multi-rate blinking
OK, now back to the code. So, our full code for multi-rate blinking is below:
This source code is on GitHub.
Multi-rate blinking: an object-oriented approach
Given the amount of code redundancy and shared logic and structure, the above solution is a strong candidate for refactoring into functions or classes. So, let’s do it!
We’re going to define a new class, called
Blinker, which will greatly simplify our code, decrease redundancy (and the potential for human error), and even make our compiled code smaller (from 1,118 to 1,042 bytes of program storage space).
Once we’ve made the
Blinker class, our main code reduces to:
But we have to make the
Blinker class first, which we do below!
Making the Blinker class
If you’re familiar with object-oriented programming and declaring and using classes in
Python, and even, to some extent,
C++ classes will feel familiar (but certainly have their own peculiarities).
C++ classes have a class name, member variables, member functions, and like
Java, access specifiers (e.g., private, public). For a quick tutorial, see these links (link1, link2).
To build our Blinker class, recall that we need four things per LED:
- Pin Number: An integer value specifying the output pin.
- Blink Interval: A blink interval that controls how long to turn on (
HIGH) and off (
LOW) each LED.
- Toggle Timestamp: The last time the LED was toggled from
- Current LED State: The current LED state (either
LOW), which is toggled every blink interval.
Blinker class, we are simply going to convert these four things into member variables:
Finally, we need two functions: a
update()—the latter which handles our core logic and toggling code and is intended to be called once per
loop() iteration. We are going to declare these in the class definition itself:
In order to use the
Blinker class (as shown above), it needs to be defined within your
.ino sketch at the top of the file (before you try to instantiate a Blinker object). Later, we’ll also show how to create a class that exists in its own
Full Blinker code
So, the entire code looks like this:
This source code is on GitHub.
Multi-rate blinking: using an external class
C++, you declare member variables and function signatures in a
.h file and the function implementations in a
.cpp file. This is often a cleaner solution than embedding classes within the
Indeed, if we move
Blinker into separate
.cpp files, then the full
.ino sketch simply looks like:
See the code in our GitHub repository.
Want to go further? Here are some design challenges to help improve your skills:
- Dynamically changing intervals. What if we wanted to support dynamically changing blink intervals—that is, once the Blinker object was instantiated. How would you do this?
- Morse code. Try adapting the Blinker class to support a sequence of on and off intervals like Morse code
- Fade. What about fading the LEDs rather than flashing them. How would you do this? Stuck? Nick Gammon wrote a class for this on his blog called LedFader but don’t look at his solution until you’ve tried your own!
Some additional references:
- Multi-tasking the Arduino: Part 1 - Blinking without delays, Adafruit Learn
- Multi-tasking the Arduino: Part 2 - Using interrupts, Adafruit Learn
- Multi-tasking the Arduino: Part 3, Adafruit Learn
- How to do multiple things at once, Nick Gammon
We did it! This completes our Intro to Output series. Let’s now begin Intro to Input to learn about buttons, sensors, voltage dividers, and more!