We broke free from delay() using Finite State Machines. But if you tried to add five blinking LEDs, you probably noticed your code turned into a massive, tangled mess of variables.
This is Spaghetti Code. It’s unreadable, undebuggable, and embarrassing. The solution? Object-Oriented Programming (OOP). It’s time to organize your code before it gains sentience and attacks you.
At its core, Object-Oriented Programming is just a way of organizing code so that it mimics real-world objects.
Imagine a car. A car has properties (color, top speed, current fuel level) and methods (accelerate, brake, honk the horn). In OOP, we define a blueprint for a car (called a Class), and then we can build as many actual cars (called Objects) as we want from that blueprint.
In our Arduino world, an LED is an object. It has properties (which pin it’s plugged into, how fast it should blink, its current on/off state) and methods (update its state).
Instead of writing 15 separate variables for 5 LEDs, we can write one Class blueprint called Flasher. Then, we just tell the Arduino: “Create 5 Flasher objects, and put them on pins 2 through 6.”
Flasher ClassTo keep our main sketch ultra-clean, we are going to write a custom library. An Arduino library (written in C++) consists of two files:
.h): This is the blueprint. It lists all the properties and methods the object will have, but doesn’t contain the actual logic..cpp): This contains the actual code (the guts) of the methods defined in the header.Let’s create a library to handle our millis() blinking logic.
Flasher.h)Create a new tab in the Arduino IDE and name it Flasher.h.
// Flasher.h
#ifndef Flasher_h
#define Flasher_h
#include "Arduino.h" // We need this to use standard Arduino commands like digitalWrite
class Flasher {
public:
// The Constructor: This is called once when we create the object
Flasher(int pin, long onTime, long offTime);
// The Update method: This will be called in the main loop()
void update();
private:
// These variables are hidden inside the object
int ledPin;
long OnTime;
long OffTime;
int ledState;
unsigned long previousMillis;
};
#endif
Flasher.cpp)Create another tab named Flasher.cpp. This is where we write what those functions actually do.
// Flasher.cpp
#include "Flasher.h"
// The Constructor implementation
Flasher::Flasher(int pin, long onTime, long offTime) {
ledPin = pin;
pinMode(ledPin, OUTPUT);
OnTime = onTime;
OffTime = offTime;
ledState = LOW;
previousMillis = 0;
}
// The Update method implementation
void Flasher::update() {
unsigned long currentMillis = millis();
if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) {
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) {
ledState = HIGH; // Turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
Now for the magic. Go back to your main .ino sketch. Because all the messy millis() logic is hidden away in our custom library, look at how incredibly clean and readable our main program becomes:
#include "Flasher.h"
// Create three Flasher objects from our blueprint!
// Parameters: (Pin Number, On Time, Off Time)
Flasher led1(12, 100, 400); // Quick blink
Flasher led2(13, 350, 350); // Steady flash
Flasher led3(14, 1000, 2000); // Long pulse
void setup() {
// We don't even need pinMode() here!
// The Flasher constructor handles it automatically.
}
void loop() {
// Update all three LEDs continuously
led1.update();
led2.update();
led3.update();
// You can still add other non-blocking code here!
}
Look at that loop()! There is no math. There are no timestamp variables. It reads like plain English.
If you want to add a fourth LED, you just add one single line of code at the top to create the object, and one line in the loop to update it. You have effectively created a modular, reusable piece of code. You can even zip up your .h and .cpp files, send them to a friend, and they can use your Flasher library in their own projects.
This is the exact same way libraries like Servo.h and Wire.h work under the hood. You’ve just leveled up from an Arduino scripter to a C++ software architect.
In our next lesson, we’ll dive into memory management and how to keep your Arduino from crashing when your sketches get truly massive!