Learning Goals 5 min
Yesterday was theory. Today you wire and turn one. We'll drive the coils by hand from four Arduino pins to see the "walking lights" sequence on the driver board, then write the full-step sequence so the motor advances one careful rotation. Tomorrow (L03-13, the Stepper library) hides this work for you — but seeing it first matters. By the end of this lesson you will:
- Wire the 28BYJ-48 stepper to a ULN2003 driver board to a UNO with a separate 5 V supply for the motor, common ground, and four control signals.
- Read the four red LEDs on the ULN2003 board as a real-time display of which coils are energised — and use them to debug a sequence before you can even feel the motor move.
- Write a tiny sketch that walks through the 4-step full-step sequence by hand (no library), advancing the motor 2048 steps (one full output revolution) at a chosen step rate.
Warm-Up 10 min
Take the white plug at the end of the stepper's ribbon and push it into the matching socket on the ULN2003 board. The plug is keyed — it only fits one way. If it's reluctant, don't force it; check the alignment.
Identify the ULN2003 board pins
The classic version is a small green PCB with:
- A 16-pin DIP chip in the middle marked "ULN2003" (or sometimes a SMD equivalent).
- 4 red LEDs in a row near the chip — these light up when their associated coil is energised. Magic for debugging.
- A 4-pin header on one edge labelled
IN1 IN2 IN3 IN4— control inputs from the Arduino. - A 2-pin power header labelled
+ -— supplies the motor coils. 5 V from a separate source (4 × AA pack or USB power bank), not the Arduino 5V pin. - A jumper near the power pins — usually leave it installed; it enables power to the ULN2003 chip.
Question before wiring
Why do we need a separate 5 V supply for the motor coils, when the UNO is right there with its own 5 V pin?
Reveal
The 28BYJ-48 draws ~240 mA per coil, and at full-step the driver energises two coils simultaneously — total ~500 mA. The UNO's 5 V regulator is rated for ~500 mA total, so we'd be at the absolute limit before the Arduino itself draws anything. Brown-outs are likely. Use a 4 × AA pack (~6 V — close enough; the ULN2003 drops ~1 V across its transistors, leaving ~5 V at the coils) or a 5 V USB power bank.
New Concept · The ULN2003 as a stepper driver 20 min
What the ULN2003 chip does
The ULN2003 is an array of seven Darlington transistor pairs in one chip. Each pair is a beefed-up NPN that can sink ~500 mA when its input pin is driven HIGH. The motor coils sit between the +5 V supply and each Darlington's collector — when the input pin is HIGH, the Darlington conducts, the coil is energised, and the rotor pole snaps to align with that coil.
It also includes a flyback diode in every output (the cross-coupled diodes built into the chip's output stage) — so you don't need to add external diodes the way you would for a discrete transistor switch (L03-07).
| Pin | Function |
|---|---|
| IN1 → IN4 (input) | Logic-level inputs from the Arduino. HIGH = corresponding coil energised. |
| OUT1 → OUT4 (output) | The four coils of the 28BYJ-48, automatically wired via the keyed plug. |
| + / − (power) | 5 V for the motor coils from a separate supply. |
| 4 × LEDs | Each one lights with the corresponding IN pin — debug aid. |
Wiring it up
| Wire | From | To |
|---|---|---|
| Stepper plug | 28BYJ-48 | ULN2003 board's matching socket |
| Coil + supply | 4 × AA pack '+' | ULN2003 '+' |
| Coil − supply | 4 × AA pack '−' | ULN2003 '−' |
| Common ground | ULN2003 '−' | Arduino GND |
| IN1 | Arduino D8 | ULN2003 IN1 |
| IN2 | Arduino D9 | ULN2003 IN2 |
| IN3 | Arduino D10 | ULN2003 IN3 |
| IN4 | Arduino D11 | ULN2003 IN4 |
The pin-order convention (D8–D11 → IN1–IN4) matches the default that the Stepper library expects, so the same wiring carries over to tomorrow without rework.
The 4-step full-step sequence
This is the same table as yesterday, with the matching binary value you'd write to your four pins:
| Step | IN1 (D8) | IN2 (D9) | IN3 (D10) | IN4 (D11) | Coils energised |
|---|---|---|---|---|---|
| 0 | HIGH | HIGH | LOW | LOW | A + B |
| 1 | LOW | HIGH | HIGH | LOW | B + C |
| 2 | LOW | LOW | HIGH | HIGH | C + D |
| 3 | HIGH | LOW | LOW | HIGH | D + A |
| 4 = 0 | HIGH | HIGH | LOW | LOW | A + B |
Cycle through these four patterns over and over → the rotor advances one full step per pattern. 2048 patterns for one output-shaft revolution.
To reverse direction
Cycle backwards: 0 → 3 → 2 → 1 → 0. The rotor turns the other way. Same patterns, opposite order. No H-bridge involved.
Worked Example · One revolution by hand 25 min
Step 1 — wire as in §3
Plug the stepper into the ULN2003. Connect the 4 × AA pack to the + / − power pins. Common ground from ULN2003 − to Arduino GND. Four IN wires to D8–D11.
Step 2 — light test (slow walk)
Before commanding any actual motion, let's verify the wiring with a slow walk through the sequence so you can watch each LED light up. Upload:
// L03-12 · ULN2003 light walk (slow)
const int COIL[4] = {8, 9, 10, 11};
void setup() {
for (int i = 0; i < 4; i++) pinMode(COIL[i], OUTPUT);
}
void allOff() {
for (int i = 0; i < 4; i++) digitalWrite(COIL[i], LOW);
}
void loop() {
for (int i = 0; i < 4; i++) {
allOff();
digitalWrite(COIL[i], HIGH);
delay(500);
}
}One LED at a time should light up — IN1, then IN2, then IN3, then IN4, then back to IN1. If they walk in the wrong order, swap the wires you got wrong. (You can also feel the motor click round at a quarter of a degree per LED, but at 500 ms per step that's 8 minutes per revolution — too slow to watch.)
Step 3 — full-step sequence at proper speed
Now the production sketch. Walks through the table from §3 at a 2 ms-per-step rate, completing one output-shaft revolution (2048 steps) every ~4 seconds.
// L03-12 · Manual full-step drive — one revolution, by hand
const int COIL[4] = {8, 9, 10, 11};
// 4-state full-step sequence. Each row sets the four coil pins.
const byte SEQ[4][4] = {
{HIGH, HIGH, LOW, LOW }, // A + B
{LOW, HIGH, HIGH, LOW }, // B + C
{LOW, LOW, HIGH, HIGH}, // C + D
{HIGH, LOW, LOW, HIGH}, // D + A
};
const int STEPS_PER_REV = 2048;
const int STEP_DELAY_MS = 2;
int phase = 0;
void writePhase(int p) {
for (int i = 0; i < 4; i++) {
digitalWrite(COIL[i], SEQ[p][i]);
}
}
void allOff() {
for (int i = 0; i < 4; i++) digitalWrite(COIL[i], LOW);
}
void stepOnce(int dir) { // dir: +1 or -1
phase = (phase + dir + 4) % 4; // wrap 0..3
writePhase(phase);
}
void setup() {
for (int i = 0; i < 4; i++) pinMode(COIL[i], OUTPUT);
Serial.begin(9600);
Serial.println("# Advancing 2048 steps clockwise...");
for (int i = 0; i < STEPS_PER_REV; i++) {
stepOnce(+1);
delay(STEP_DELAY_MS);
}
allOff();
Serial.println("# Done. Coils off — motor freewheels.");
}
void loop() { }Step 4 — upload and watch
The four LEDs cycle in rotation, far too fast to see distinct flashes — they appear to all glow at half brightness because each is on half the time. The stepper's output shaft rotates once over ~4 seconds (2048 × 2 ms). At the end, all LEDs go off and the rotor freewheels — turn the shaft by hand to feel the detents without any holding torque.
Step 5 — reverse
Add a second loop in setup() that runs stepOnce(-1) for 2048 iterations. The motor returns to its starting position. Notice how the LEDs cycle the opposite direction — same physical states, opposite order.
Step 6 — try too-fast
Set STEP_DELAY_MS to 1 (one millisecond between steps → 30 RPM, twice the rated speed). Re-upload. The motor will likely whine, vibrate, and not advance — or advance unreliably with skipped steps. Restore STEP_DELAY_MS to 2 (or 3 for total safety).
Try It Yourself 15 min
Goal: Make the motor advance exactly 90° clockwise, pause 1 second, return 90° counter-clockwise. Then repeat forever in loop().
Hint
const int QUARTER_REV = 512; // 2048 / 4
void loop() {
for (int i = 0; i < QUARTER_REV; i++) { stepOnce(+1); delay(STEP_DELAY_MS); }
allOff();
delay(1000);
for (int i = 0; i < QUARTER_REV; i++) { stepOnce(-1); delay(STEP_DELAY_MS); }
allOff();
delay(1000);
}Turn the coils off (allOff()) during the 1-second pause to save power and heat. The rotor stays roughly where it is (detents hold it).
Goal: Make the step rate depend on a potentiometer on A0 — turn the pot to dial in step delays from 2 ms (fastest reliable) to 20 ms (slow and visible).
Hint
int pot = analogRead(A0);
int delayMs = map(pot, 0, 1023, 2, 20);
stepOnce(+1);
delay(delayMs);You're building the foundation of a stepper-driven clock or rotating display, where the rotation speed is settable by the user. Turn the pot to its slow extreme and feel the stepper progress one click at a time.
Goal: Convert to non-blocking timing — your motor steps independently of the rest of loop(). Add a heartbeat LED on D7 that blinks at 1 Hz, no matter how slow or fast the motor is stepping. Same payoff as the L03-03 servo demo: code feels alive.
Hint
Track lastStepTime and lastBlinkTime separately. In loop(), check each and act when its interval has passed. The step interval can come from the pot as in the medium task; the blink interval is fixed at 500 ms.
unsigned long lastStep = 0, lastBlink = 0;
bool ledOn = false;
int stepInterval = 2;
void loop() {
unsigned long now = millis();
stepInterval = map(analogRead(A0), 0, 1023, 2, 20);
if (now - lastStep >= (unsigned long)stepInterval) {
lastStep = now;
stepOnce(+1);
}
if (now - lastBlink >= 500) {
lastBlink = now;
ledOn = !ledOn;
digitalWrite(7, ledOn);
}
}Mini-Challenge · Calibrate one exact revolution 10 min
Specs say 2048 steps for one revolution. Reality often differs by a few steps because of internal gearbox tolerances. Today you measure.
- Mark the output shaft with a small piece of tape — choose a clear reference point on the body of the motor where the tape is aligned at the start.
- Run the "single revolution" sketch from §4. After the rotation, see how close the tape mark is to the starting reference.
- If the tape over-shoots, your motor took fewer steps than "true" 360° → reduce
STEPS_PER_REVin code by a few. If it under-shoots, increase it. - Iterate until one commanded "revolution" physically returns the tape to its starting position within ~1°. Record the calibrated value (commonly 2037 or 2048; some references quote 2038.4 for the "true" number — close enough for almost any project).
This is your motor's "steps per revolution" from now on. Save it as a top-of-file const in every stepper sketch you write. The Cluster C capstone (L03-14, Rotating Display Platform) depends on this number being right.
Recap 5 min
The 28BYJ-48 + ULN2003 pair is the classroom stepper. Power its coils from a separate 5 V supply (never the UNO's 5V pin). Common ground to the Arduino. Four control wires from D8–D11 to IN1–IN4 — match this convention and the next lesson's library will Just Work. Four LEDs on the driver board give you a real-time view of which coils are energised — irreplaceable debug aid. The full-step sequence walks two adjacent coils at a time around the four positions; cycle forward = clockwise, cycle backward = counter-clockwise. 2048 steps per output revolution, ~2 ms per step → ~4 s per revolution, max ~15 RPM reliable. Tomorrow we throw away the manual sequence table and call Stepper library functions instead — much shorter sketches, identical hardware.
- 28BYJ-48
- The classroom unipolar stepper: 5 V, 240 mA per coil, 2048 steps per revolution thanks to its 1:64 internal gearbox.
- ULN2003
- An array of 7 Darlington transistor pairs in one chip; in "ULN2003 driver board" form, the lower 4 are wired up for use with a unipolar stepper's coils. Cheap, robust, includes internal flyback diodes.
- Darlington pair
- Two NPN transistors stacked so the first one's amplified output drives the second's base. Net result: huge current gain (~1000) but a slightly larger saturation drop (~1 V) than a single transistor.
- Step delay
- Time waited between two coil-pattern writes. For the 28BYJ-48: 2 ms (fast reliable) to 50 ms (slow, very visible). Below 1.5 ms the rotor can't keep up.
- Walking lights
- The visual pattern of LEDs on the ULN2003 board cycling in rotation. Confirms wiring and lets you debug the sequence without seeing the motor move.
- Coil-off / freewheel
- Writing LOW to all four IN pins. Disconnects the stepper's coils — no holding torque, no current draw, motor can be moved by hand.
- Calibrated steps per revolution
- The actual measured step count for one full output-shaft turn. Usually 2037–2048 for a 28BYJ-48 depending on the gearbox sample.
Homework 5 min
- Save the working manual-stepper sketch as
stepper-manual.ino. We'll compare it to theStepper-library version tomorrow. - Record your calibrated steps-per-revolution number in your engineering notebook.
- Measure: how warm does the ULN2003 chip get after 5 minutes of continuous stepping? Touch it gently. If it's painful, your step delay is too short or your battery voltage is too high — drop the step delay back or check the supply.
- Read ahead to ARD-L03-13 (The Stepper Library). Note in your notebook one prediction: how many lines of code do you think tomorrow's sketch will be vs today's 25-ish?
Bring back next class:
- Your saved manual sketch.
- Your calibrated step count.
- Your temperature observation + prediction. The stepper + ULN2003 + 4 × AA pack — keep them wired together for tomorrow.