Learning Goals 5 min
Cluster H's capstone: take one of your Cluster A-G sensor / motor wrappers and turn it into a polished, distributable Arduino library. By the end of this lesson you will:
- Pick a wrapper worth packaging (SmoothedSensor, Motor class, RangeFinder, etc.).
- Refactor into a proper library structure with API polish (defaults, doxygen, const-correctness).
- Optionally publish to GitHub and submit to the Library Registry.
Warm-Up 10 min
Look back at your reusable code so far:
- Motor (L03-08, L03-41) — wraps L298N IN1/IN2/EN signals into
setSpeed(int). - SmoothedSensor (L03-41) — analog sensor with running-average smoothing.
- RangeFinder (L03-43) — HC-SR04 wrapper returning cm.
- PID (L04-24) — generic PID controller.
- Blinker (L03-42, L04-41) — non-blocking blink.
- ShiftReg (L03-21) — 74HC595 helper.
Pick one. Today we polish it end-to-end and (optionally) ship.
New Concept · From wrapper to library 25 min
The polish pass
- Split into .h + .cpp (or .h only if header-only is fine).
- Apply API design principles from L04-42: defaults, const, doxygen, private internals.
- Add 2-3 examples showing common uses.
- Write README with quick start.
- Test on multiple boards: UNO, Nano, ESP32. Use #ifdef for board differences.
- Version it 0.1.0 until you're confident → 1.0.0 when stable.
- License: pick MIT or similar.
- Optionally publish via the L04-43 flow.
Example: turn Motor into a library
Start with the L03-09 Motor struct + functions. Convert to a class library.
Motor.h
#ifndef MOTOR_H
#define MOTOR_H
#include <Arduino.h>
/**
* @brief L298N-driven DC motor with signed PWM speed.
*
* Each instance handles one motor channel of an L298N H-bridge.
* Connect IN1, IN2, and ENA (or IN3/IN4/ENB) to digital pins.
* Positive duty = forward; negative = reverse; zero = brake.
*/
class Motor {
public:
/**
* @param in1 Direction pin 1.
* @param in2 Direction pin 2.
* @param en PWM-capable enable pin.
*/
Motor(int in1, int in2, int en);
/// Configure pins as OUTPUT. Call in setup().
void begin();
/// Set signed duty cycle: -255..+255.
void setSpeed(int duty);
/// Stop with both IN pins LOW (active brake).
void brake();
/// Disconnect motor (coast). Requires PWM pin to support 0.
void coast();
/// Last commanded signed duty.
int getSpeed() const { return lastDuty_; }
private:
int in1_, in2_, en_;
int lastDuty_;
};
#endifMotor.cpp
#include "Motor.h"
Motor::Motor(int in1, int in2, int en)
: in1_(in1), in2_(in2), en_(en), lastDuty_(0) {}
void Motor::begin() {
pinMode(in1_, OUTPUT);
pinMode(in2_, OUTPUT);
pinMode(en_, OUTPUT);
brake();
}
void Motor::setSpeed(int duty) {
duty = constrain(duty, -255, 255);
if (duty > 0) {
digitalWrite(in1_, HIGH);
digitalWrite(in2_, LOW);
analogWrite(en_, duty);
} else if (duty < 0) {
digitalWrite(in1_, LOW);
digitalWrite(in2_, HIGH);
analogWrite(en_, -duty);
} else {
brake();
}
lastDuty_ = duty;
}
void Motor::brake() {
digitalWrite(in1_, LOW);
digitalWrite(in2_, LOW);
analogWrite(en_, 0);
lastDuty_ = 0;
}
void Motor::coast() {
analogWrite(en_, 0);
lastDuty_ = 0;
}Example sketch
#include <Motor.h>
Motor leftWheel(4, 7, 5);
Motor rightWheel(8, 9, 6);
void setup() {
leftWheel.begin();
rightWheel.begin();
}
void loop() {
leftWheel.setSpeed(200);
rightWheel.setSpeed(200);
delay(2000);
leftWheel.setSpeed(-200);
rightWheel.setSpeed(-200);
delay(2000);
}Cross-board support
void Motor::setSpeed(int duty) {
// ...
#if defined(ESP32) || defined(ESP8266)
// ESP boards use a different PWM resolution by default (10-bit)
duty = (duty * 1023) / 255;
#endif
// ...
}Or call analogWriteResolution(8) in begin() to standardise.
Worked Example · Ship one of your libraries 30 min
Pick
Pick the most polished of your wrappers. SmoothedSensor is a great choice — small, useful, no hardware-specific.
Refactor
Apply all of L04-42's principles. Split files. Add defaults. Add doxygen. Add 2 examples.
Test
Compile + run on UNO. Compile + run on ESP. Note any divergence; fix or document.
Document
README with quickstart. License. Optional: HTML docs from Doxygen.
Publish (optional)
Push to GitHub. Tag 1.0.0. Submit PR to Library Registry.
Celebrate
If you submitted: your code is now installable by anyone in the world. Real software shipping.
Cluster H done. Final cluster (exam prep) starts tomorrow.
Basic 5 min
Goal: Make a second library from a different wrapper. Apply the same polish pass.
Challenge 1 5 min
Goal: Combine two of your libraries into one (e.g. Motor + RangeFinder + a tiny "Chassis" helper). The kind of higher-level "robot car" library you'd find on the Arduino store.
Challenge 2 5 min
Goal: Submit a library to the Arduino Library Registry. Get it actually listed. Watch the install count climb.
Challenge 3 · Fix the packaged driver 10 min
A classmate packaged the Motor driver, but changed a few things. One wheel is on an L298N with ENA on D2. There are four mistakes: one stops it building, and three show up on the bench.
Motor.h:
#ifndef MOTOR_H
#define MOTOR_H
#include <Arduino.h>
int clampDuty(int duty) {
return constrain(duty, -255, 255);
}
class Motor {
public:
Motor(int in1, int in2, int en);
void begin();
void setSpeed(int duty);
void brake();
int getSpeed() const { return lastDuty_; }
private:
int in1_;
int in2_;
int en_;
int lastDuty_;
};
#endifMotor.cpp:
#include "Motor.h"
Motor::Motor(int in1, int in2, int en)
: in1_(in1), in2_(in2), en_(en), lastDuty_(0) {}
void Motor::begin() {
pinMode(in1_, OUTPUT);
pinMode(in2_, OUTPUT);
pinMode(en_, OUTPUT);
brake();
}
void Motor::setSpeed(int duty) {
duty = clampDuty(duty);
if (duty > 0) {
digitalWrite(in1_, HIGH);
digitalWrite(in2_, LOW);
analogWrite(en_, duty);
} else if (duty < 0) {
digitalWrite(in1_, LOW);
digitalWrite(in2_, HIGH);
analogWrite(en_, duty);
} else {
brake();
}
}
void Motor::brake() {
digitalWrite(in1_, LOW);
digitalWrite(in2_, LOW);
analogWrite(en_, 0);
lastDuty_ = 0;
}Example sketch:
#include <Motor.h>
Motor leftWheel(4, 7, 2);
void setup() {
Serial.begin(9600);
leftWheel.begin();
}
void loop() {
leftWheel.setSpeed(120);
Serial.println(leftWheel.getSpeed());
delay(2000);
leftWheel.setSpeed(-120);
Serial.println(leftWheel.getSpeed());
delay(2000);
}Find each mistake, say what you would see, and fix it. Move one wire if you need to.
It works if the wheel turns at the same gentle speed both ways, and the Serial Monitor alternates 120 and -120.
Recap 5 min
Library = struct/class wrapper + polished API + examples + README + license + Git tag + PR to registry. Once shipped, others install with one click. Cluster H done. Final cluster: Arduino Certification Exam preparation.
- Driver library
- A reusable wrapper for a hardware component (sensor, motor, display). Hides protocol / wiring details behind a clean API.
- Polish pass
- The conversion from "works in my sketch" to "ready for everyone's sketches": defaults, docs, examples, license.
- Header-only library
- Library with everything in .h files (no .cpp). Simpler for small utilities; means definitions inline.
- Multi-architecture support
- Library that works on UNO, ESP, Nano 33 BLE. Use
#ifdeffor board-specific bits. - Portfolio piece
- A published project you can show to others — university applications, internships, engineering portfolios. Open source libraries are excellent portfolio fodder.
Extra Mission 5 min
Part 1 — Plan a portfolio piece
A library is worth more when people can see it solve a real problem. On paper, plan a short portfolio piece around one library you have built.
Your plan must include:
- The problem the library solves, in one sentence.
- A real use for it in a new sketch, such as a parking sensor or a robot that drives a square.
- A quick usage example of five lines or fewer.
- Two things you would add in version 2.
Part 2 — Make it
Build the new sketch from your plan, using your library through #include <…> only. Do not copy the class code into the sketch. Record a short video of it working.
Bring back next class: your sketch, your library folder and the video.