Stepper Motors
Driving stepper motors from an ESP32 for precise cockpit gauge needles, VOR indicators, and motorised controls.
A stepper motor moves in discrete steps rather than a continuous sweep. Each pulse to the driver advances the motor by one step, so position is tracked by counting steps — no position feedback sensor is needed for open-loop control.
| Feature | Stepper motor | Servo |
|---|---|---|
| Position accuracy | Precise (open-loop) | Good (closed-loop internally) |
| Holding torque | Excellent (energised) | Good |
| Speed | Moderate | Fast |
| Wiring complexity | 4–6 wires + driver board | 3 wires |
| Power consumption | Always drawing current when holding | Only when moving under load |
The 28BYJ-48 is the most common starter stepper motor. It runs on 5 V, has an internal gear reduction of ~64:1, and comes bundled with a ULN2003A driver board that accepts 3.3 V logic directly from the ESP32.
| Specification | Value |
|---|---|
| Supply voltage | 5 V DC |
| Phase resistance | ~200 Ω |
| Steps per revolution (half-step) | 4 096 (64 elec. steps × ~64 gear ratio) |
| No-load speed | ~15 RPM at 5 V |
| Stall torque | ~300–350 g·cm |
// 28BYJ-48 with ULN2003 — Library Manager → "AccelStepper" by Mike McCauley
#include <AccelStepper.h>
// HALF4WIRE (8-step) mode: IN1=23, IN3=21, IN2=22, IN4=19
// Note: AccelStepper expects (pin1, pin2, pin3, pin4) = (IN1, IN3, IN2, IN4)
AccelStepper stepper(AccelStepper::HALF4WIRE, 23, 21, 22, 19);
// 28BYJ-48: 64 electrical steps × gear ratio ≈ 64 = 4096 steps/rev (half-step)
const int STEPS_PER_REV = 4096;
void setup() {
stepper.setMaxSpeed(1000); // steps/second
stepper.setAcceleration(500); // steps/second²
}
void loop() {
stepper.moveTo(STEPS_PER_REV); // one full clockwise revolution
while (stepper.distanceToGo() != 0) {
stepper.run();
}
delay(500);
stepper.moveTo(0); // return to home position
while (stepper.distanceToGo() != 0) {
stepper.run();
}
delay(500);
}NEMA 17 is the industry-standard bipolar stepper motor for 3D printers and CNC machines. It delivers significantly more torque than the 28BYJ-48 and supports up to 1/16 microstepping for smooth motion. The A4988 driver requires a separate high-voltage motor supply (8–35 V) and a 3.3 V logic supply.
| A4988 Pin | Connect to | Notes |
|---|---|---|
| STEP | GPIO 23 | One pulse = one step |
| DIR | GPIO 22 | HIGH / LOW sets rotation direction |
| EN | GPIO 21 | Active LOW — drive LOW to enable, HIGH to coast |
| RST, SLP | 3.3 V (VDD) | Tie together and pull HIGH to keep driver active |
| VDD | 3.3 V | Logic supply from ESP32 |
| VMOT | 8–35 V external supply | Place 100 µF cap across VMOT / GND |
| MS1, MS2, MS3 | GND (full step) or see table | 000=full, 100=half, 010=¼, 110=⅛, 111=1/16 |
| 1A, 1B / 2A, 2B | Motor coil A / Motor coil B | 4-wire bipolar motor |
// NEMA 17 with A4988 driver — STEP / DIR interface
#include <AccelStepper.h>
const int STEP_PIN = 23;
const int DIR_PIN = 22;
const int EN_PIN = 21; // active LOW — pull LOW to enable
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
// NEMA 17 standard: 200 full steps/rev (1.8° per step)
// With 1/16 microstepping on A4988: 3200 steps/rev
const int STEPS_PER_REV = 200;
void setup() {
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW); // enable the driver
stepper.setMaxSpeed(800);
stepper.setAcceleration(400);
}
void loop() {
stepper.moveTo(STEPS_PER_REV);
while (stepper.distanceToGo() != 0) { stepper.run(); }
delay(500);
stepper.moveTo(0);
while (stepper.distanceToGo() != 0) { stepper.run(); }
delay(500);
}| Application | Motor choice | Notes |
|---|---|---|
| Compass / heading indicator | 28BYJ-48 | High gear ratio gives fine resolution for slow rotation |
| Altimeter drum counter | 28BYJ-48 | Drives rotating digit drums via shaft coupler |
| VOR / ILS needle | 28BYJ-48 / NEMA 17 | Map deviation value (−2.5 to +2.5 dots) to steps |
| Throttle / mixture lever | NEMA 17 | Requires feedback force; consider holding-torque spec |
| Motorised trim wheel | NEMA 17 | Continuously tracks simulator trim dataref |