← Back to Academy

Lesson 179: Object-Oriented Arduino (Writing Your Own Libraries)

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.

The Basics of OOP (Don’t Panic)

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.”


Building Your First Library: The Flasher Class

To keep our main sketch ultra-clean, we are going to write a custom library. An Arduino library (written in C++) consists of two files:

  1. The Header File (.h): This is the blueprint. It lists all the properties and methods the object will have, but doesn’t contain the actual logic.
  2. The Source File (.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.

Step 1: The Header File (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

Step 2: The Source File (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
  }
}

The Clean Sketch

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!
}

Why This is a Superpower

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!