Learning Goals 5 min
Yesterday you wrote the four-pattern sequence by hand. Today you delete it — Arduino's built-in Stepper library does the same job in three method calls. The hardware is identical; the sketch is half the length. By the end of this lesson you will be able to:
- Include the built-in
Stepperlibrary, create aStepperobject with the correct pin order for a 28BYJ-48 + ULN2003, and command rotations in degrees rather than raw steps. - Set rotational speed in RPM with
setSpeed()and explain why values above ~15 RPM cause the 28BYJ-48 to skip steps. - Reverse direction by passing a negative number to
step()— and explain why this approach is cleaner than the "run the sequence backwards" manual code from L03-12.
Warm-Up 10 min
Keep yesterday's wiring intact: 28BYJ-48 plugged into the ULN2003 board, control wires on Arduino D8–D11, separate 5 V supply for the motor, common ground. Yesterday's sketch should still be in your IDE if you saved it as stepper-manual.ino.
Quick count
Open yesterday's manual sketch and count its lines, ignoring blank lines and comments. You should get about 25. Hold that number in your head — we'll compare it with the library version soon.
The big idea before we open the library
What you wrote yesterday: 4 lines for the sequence table, a writePhase() helper, a stepOnce() helper, and a loop counting steps. That code is generic — it would work for any 4-coil unipolar stepper, not just the 28BYJ-48. The Arduino team noticed the same thing in ~2008, wrote it once, and shipped it as the Stepper library. Including it means you never write that code again.
New Concept · The three method calls 25 min
Step 1 · Include the library
#include <Stepper.h>Built in — no install needed. Same family as Servo.h.
Step 2 · Create a Stepper object
The constructor takes the steps-per-revolution and four pin numbers:
const int STEPS_PER_REV = 2048; // 28BYJ-48 with internal gearbox
Stepper motor(STEPS_PER_REV, 8, 10, 9, 11);Note the pin order: 8, 10, 9, 11 — not 8, 9, 10, 11. This is the classic gotcha. The library expects the pins for the two coils of a bipolar stepper, in the order they fire. For the 28BYJ-48 + ULN2003, that comes out as IN1, IN3, IN2, IN4 on the driver. Get this wrong and the motor will buzz and vibrate but not turn smoothly — or it will turn at a quarter of the expected speed. Try both orders if your motor refuses to rotate properly.
Step 3 · setSpeed(rpm)
Sets the rotation speed in RPM (revolutions per minute):
motor.setSpeed(12); // 12 RPM — safely inside the 28BYJ-48's reliable rangeThe library will time its step delays to hit that RPM, given the steps-per-revolution you specified. Set 60 RPM on the 28BYJ-48 and the motor will not move — you're asking for 4× the reliable maximum, and the rotor can't physically keep up. Stay at or below 15 RPM.
Step 4 · step(numSteps)
Steps the motor by the given number of steps (not degrees). Positive = one direction, negative = the other. Blocks until done.
motor.step(1024); // 1024 steps = half revolution on a 28BYJ-48
motor.step(-512); // 512 steps backward = quarter revThe full minimum-viable sketch
Here's yesterday's "one full rotation" rewritten with the library:
#include <Stepper.h>
const int STEPS_PER_REV = 2048;
Stepper motor(STEPS_PER_REV, 8, 10, 9, 11);
void setup() {
motor.setSpeed(12);
motor.step(STEPS_PER_REV); // one full revolution forward
motor.step(-STEPS_PER_REV); // one full revolution backward
}
void loop() { }Total: 10 lines vs yesterday's ~25. The library hides the sequence table, the phase counter, and the per-step delay — you specify what you want, not how to do it.
A degrees-not-steps helper
You usually think in degrees, not raw steps. Wrap the conversion:
int degToSteps(float deg) {
return (int)(deg * STEPS_PER_REV / 360.0);
}
// usage:
motor.step(degToSteps(90)); // quarter turn forward
motor.step(degToSteps(-45)); // 1/8 turn backFrom here on, code reads as physical motion: "step 90 degrees" rather than "step 512".
The big drawback — step() blocks
The library's step() doesn't return until the motion is finished. Commanding step(2048) at 12 RPM takes 5 seconds — and during those 5 seconds your sketch can do nothing else. No sensor reads, no button checks, nothing.
If you need non-blocking stepper motion, the third-party AccelStepper library is the answer. We'll mention it in §5 but stick with the built-in Stepper for L03 — the simplicity is worth the blocking on most builds.
Worked Example · A "quarter-turn-on-command" rig 25 min
You'll command quarter turns and reversals from the Serial Monitor — exactly the kind of thing the rotating display platform in L03-14 will dispatch to the motor.
Step 1 — verify wiring
If you've unplugged from yesterday, re-wire: 28BYJ-48 plug → ULN2003 socket, ULN2003 '+ −' → 4 × AA pack, common ground to Arduino GND, IN1–IN4 to D8–D11. No new components today.
Step 2 — the sketch
// L03-13 · Stepper library — quarter-turn-on-command
#include <Stepper.h>
const int STEPS_PER_REV = 2048;
Stepper motor(STEPS_PER_REV, 8, 10, 9, 11);
const int COIL_PINS[4] = {8, 9, 10, 11};
int degToSteps(float deg) {
return (int)(deg * STEPS_PER_REV / 360.0);
}
void releaseCoils() {
for (int i = 0; i < 4; i++) digitalWrite(COIL_PINS[i], LOW);
}
void setup() {
Serial.begin(9600);
motor.setSpeed(12);
releaseCoils();
Serial.println("# Stepper ready. r=+90 deg l=-90 deg h=+180 deg x=release coils");
}
void loop() {
if (Serial.available() == 0) return;
char cmd = Serial.read();
switch (cmd) {
case 'r': case 'R':
Serial.println(">> +90 deg");
motor.step(degToSteps(90));
releaseCoils();
break;
case 'l': case 'L':
Serial.println(">> -90 deg");
motor.step(degToSteps(-90));
releaseCoils();
break;
case 'h': case 'H':
Serial.println(">> +180 deg");
motor.step(degToSteps(180));
releaseCoils();
break;
case 'x': case 'X':
Serial.println(">> coils off");
releaseCoils();
break;
default:
return;
}
}Step 3 — upload and play
Open Serial Monitor at 9600 baud. Type:
r+ Enter → motor advances 90° clockwise, coils release.rthree more times → back to start.l→ 90° anti-clockwise.h→ 180° clockwise.
After each move the coils release (current off) — the motor freewheels until the next command. Hold the shaft between moves; it has no holding torque while released. This is the "low-heat" pattern: only energise when commanding motion.
Step 4 — measure how long each move takes
Add a timer around the step call:
unsigned long t0 = millis();
motor.step(degToSteps(90));
unsigned long elapsed = millis() - t0;
Serial.print(">> +90 deg took ");
Serial.print(elapsed);
Serial.println(" ms");You should see ~1250 ms for a 90° move at 12 RPM (one quarter rev = 5 s ÷ 4 = 1.25 s). This is the headline reason step() blocks — it can't return earlier because the motor physically isn't there yet.
Step 5 — speed sweep test
In a small standalone sketch, call setSpeed with values 5, 10, 15, 20, 25 RPM, then command one full revolution at each. You'll feel:
- 5 RPM: smooth and slow, no skipped steps.
- 10 RPM: smooth and faster.
- 15 RPM: about as fast as it'll reliably go. Slight whine.
- 20 RPM: motor buzzes but may skip steps — ends at a different position than expected.
- 25 RPM: buzz, no rotation. Far above the rotor's ability to keep up.
Confirms yesterday's "~15 RPM is the limit" finding.
Step 6 — preview AccelStepper (no install today)
The third-party AccelStepper library (available via Sketch → Include Library → Manage Libraries) supports acceleration profiles, non-blocking motion, and synchronised multi-motor moves. Same wiring, smarter library. For most school builds the built-in Stepper is enough; for advanced robotics (Level 4's self-balancer needs encoders, not steppers, but the same non-blocking pattern), AccelStepper is the upgrade.
Basic 5 min
Goal: Add a fifth command f that does one full revolution forward, releases the coils. Then add F (capital) that does one full revolution reverse.
Challenge 1 5 min
Goal: Read a number typed into the Serial Monitor as the rotation in degrees, then step the motor by that many degrees. So typing 45 rotates the shaft 45°. Negative numbers work too.
Challenge 2 5 min
Goal: Add an "adjustable speed" pot on A0: each command reads the pot just before stepping and calls setSpeed() with a value 5–15 RPM scaled from the pot. So users can throttle the rotation live.
Challenge 3 · Fix the stubborn stepper 10 min
A classmate cut the Worked Example down to two commands. It uploads, but the motor misbehaves. There are four mistakes.
#include <Stepper.h>
const int STEPS_PER_REV = 2048;
const int COIL_PINS[4] = {8, 9, 10, 11};
Stepper motor(STEPS_PER_REV, 8, 9, 10, 11);
int degToSteps(int deg) {
return deg * STEPS_PER_REV / 360;
}
void releaseCoils() {
for (int i = 0; i < 4; i++) {
digitalWrite(COIL_PINS[i], LOW);
}
}
void setup() {
Serial.begin(9600);
motor.setSpeed(30);
releaseCoils();
}
void loop() {
if (Serial.available() == 0) {
return;
}
char cmd = Serial.read();
if (cmd == 'r') {
motor.step(degToSteps(90));
}
if (cmd == 'l') {
motor.step(degToSteps(-90));
releaseCoils();
}
}Hint for one of them: on an UNO an int holds only −32 768 to 32 767. Work out 90 * 2048.
For each mistake, write down the line, what the motor does because of it, and the fix. Then fix the sketch and test it.
It works if r turns the shaft exactly 90° clockwise in about 1.25 s. Four rs must bring it back to the start. All four driver LEDs must go dark after every move.
Recap 5 min
The Stepper library is the "Servo library" equivalent for steppers — bundled with the IDE, three method calls, removes the sequence table from your sketch. Pin-order gotcha: Stepper motor(steps, 8, 10, 9, 11), not 8, 9, 10, 11 — because the library expects the bipolar-coil pin order. setSpeed(rpm) sets rotation rate; step(n) commands a relative move (positive or negative). Stays under ~15 RPM on the 28BYJ-48. The library's only downside: step() blocks until the move finishes — for non-blocking stepper motion, reach for AccelStepper. Tomorrow you turn the "quarter-turn-on-command" rig into a finished product: a rotating display platform.
Stepperlibrary- Built-in Arduino library that hides the coil-firing sequence behind
setSpeedandstep. Works for both unipolar (with ULN2003) and bipolar (with H-bridge) steppers. - Constructor
- A function called when you create an object (e.g.
Stepper motor(...)). ForStepperit takes the steps-per-revolution and four pin numbers in coil-pair order. setSpeed(rpm)- Sets the rotation rate in revolutions per minute. The library calculates the required step delay from this and the steps-per-revolution.
step(n)- Commands a relative move by
nsteps. Positive forward, negative reverse. Blocks until done. - Blocking call
- A function that doesn't return until its work is finished. The opposite of non-blocking. For a stepper move of 1 revolution at 12 RPM, that's a 5-second pause.
- AccelStepper
- Third-party library for non-blocking stepper motion with acceleration profiles. Install from the Library Manager. Useful for synchronised multi-motor builds.
- Pin-order swap
- The 28BYJ-48's common-wisdom pin order for the
Stepperlibrary: D8, D10, D9, D11 instead of D8, D9, D10, D11. Get it wrong and the motor vibrates without turning. - Coil release
- Writing LOW to all four control pins after a move so the motor isn't continuously energised. Saves power and heat at the cost of holding torque.
Extra Mission 5 min
Part 1 — Design a rotating display
Next lesson the stepper becomes a turntable. Decide what yours will show off: a model you built, a trophy, or a phone playing a video. Design how it should move.
Your design must include:
- The object, and roughly how heavy it is.
- The number of stop positions per turn, and the angle between them.
- The speed in RPM, and how long each move takes (show the sum).
- The Serial Monitor keys a user presses, and what each one does.
Part 2 — Make it
Write the control sketch with the Stepper library. Use three keys: n for next stop, p for previous stop, and 0 for the start. Use degToSteps() and call releaseCoils() after every move. Print how long each move took, as in Step 4 of the Worked Example.
It works if the printed times match your sums. 0 must always return the shaft to where it began.
Bring back next class: your design, your uploaded sketch, and a Serial Monitor screenshot of your timings. Also bring a cardboard or wooden disc about 10 cm across for the turntable.