💡 Electron Parade
← Back to Academy

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

Let’s be honest: we’ve all been there. You start a new project with the best of intentions. “I’ll keep my code clean this time,” you lie to yourself. Three days, four sensors, and 1,200 lines of void loop() later, you have a monstrous file of spaghetti code that looks like a cat walked across your keyboard. You try to reuse that cool blinking LED sequence in another project, only to realize it’s hopelessly tangled with your servo control logic. You try to untangle it, accidentally delete a curly brace, and suddenly your Arduino thinks it’s a toaster. The magic blue smoke is practically begging to be released.

It’s time to stop the madness. It’s time to learn Object-Oriented Programming (OOP) for Arduino.

In this lesson, we’re going to rescue your sanity by teaching you how to encapsulate your messy code into neat, reusable objects and libraries. By the end, your code will be so clean, you could eat off it (though we still recommend a plate).

Arduino Uno R3 The trusty Arduino Uno R3 - a perfect canvas for your new clean code.


Why Object-Oriented Programming (OOP)?

When you write procedural code (the default Arduino way), everything lives in one big file. Global variables run wild, and functions step on each other’s toes.

Object-Oriented Programming allows you to package variables and the functions that use them into a single “Object”. Think of an Object like a physical component. If you have an LED, it has a pin number (variable), a state (variable), and actions like turnOn() or blink() (functions).

The Benefits:

  1. Reusability: Write it once, use it in 50 different projects.
  2. Modularity: Need three LEDs to blink at different rates? Just create three instances of your LED object. No need for led1Pin, led2Pin, led3Pin variables cluttering the top of your sketch.
  3. Clean Main File: Your void loop() should read like a story, not a phonebook.

Step 1: The Anatomy of a Class

A Class is the blueprint for an Object. We define it in two parts (usually in separate files when making a true library, but we can do it right in the Arduino IDE for now):

  1. Attributes (Variables): What the object knows (e.g., pin number, interval time).
  2. Methods (Functions): What the object does (e.g., update(), turnOn()).

Let’s build a simple Flasher class that blinks an LED without using the dreaded delay() function. (Remember Lesson 156: millis() vs delay()?)

class Flasher {
  // Class Member Variables
  // These are initialized at startup
  int ledPin;      
  long OnTime;     
  long OffTime;    

  // These maintain the current state
  int ledState;                 
  unsigned long previousMillis;   

  public:
  // Constructor - creates a Flasher 
  // and initializes the member variables and state
  Flasher(int pin, long on, long off) {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }

  // Method to update the LED state
  void Update() {
    // check to see if it's time to change the state of the LED
    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
    }
  }
};

Step 2: Instantiating the Objects

Now that we have our blueprint, using it in our main sketch is incredibly satisfying.

// Instantiate two Flasher objects, passing in the pin, on-time, and off-time
Flasher led1(12, 100, 400); // Pin 12, On 100ms, Off 400ms
Flasher led2(13, 350, 350); // Pin 13, On 350ms, Off 350ms

void setup() {
  // Nothing to do here! The constructor handles pinMode setup.
}

void loop() {
  // Our loop is totally clean and non-blocking!
  led1.Update();
  led2.Update();
}

Look at that void loop(). It brings a tear to the eye. No delay(), no overlapping timer logic. Just two clean, independent objects minding their own business.

Step 3: Moving to a Proper Library (.h and .cpp)

To truly write your own library that you can share or easily drop into other projects, you need to split this class into two files:

By placing these in a folder named Flasher inside your Arduino libraries folder, you can simply #include <Flasher.h> in any future project!

If you’re testing multiple components with OOP, you’ll need a solid breadboard setup. Don’t fry your board because your wires crossed.

Conclusion

Moving to Object-Oriented Programming on Arduino takes a slight mental shift, but once you make the leap, you’ll never want to write giant procedural sketches again. Start small, encapsulate your most repetitive code, and soon you’ll be writing modular libraries like a pro!

Related reading: Brush up on the basics of non-blocking code in Lesson 156: Battery Super-Saver or check out our Arduino Uno vs. Nano Showdown if you need a smaller board for your new clean code.