A stepper motor turns in discrete angular steps (often 200 steps per turn, 1.8° per step) under the sequential drive of several coils. The rotor position is therefore known by construction with no feedback sensor, hence its appeal for precise multi-turn positioning (3D printers, CNC machines). This page covers two drivers: the 28BYJ-48 plus ULN2003 (for teaching) and the NEMA17 plus A4988 (industrial). In MicroPython, with no dedicated library built in, you sequence the coils by hand, which shows exactly how a stepper advances.
What is it for?
Typical cases: precise angular positioning (with no sensor, from a known home), linear movement through a leadscrew (3D printer axes, CNC), very slow rotation with no jerks, multi-turn valves.
| Actuator | Position | Torque | Accuracy |
|---|---|---|---|
| Standard servo | 0-180° | low | medium |
| DC motor | none | good | none (without an encoder) |
| Stepper motor | step by step | medium | excellent |
Step by step
Four steps: choose the motor and driver pair, wire them, sequence, drive.
1. Choose the motor and the driver
- For teaching: 28BYJ-48 plus ULN2003 — a 5 V motor, a 1:64 gearbox (about 2048 steps per turn in full step, 4096 in half step), and a ULN2003 board with 4 LEDs that follow the phases.
- Industrial: NEMA17 plus A4988 — a bipolar 200 step per turn motor, a driver with 2 pins (
STEP,DIR), and microstepping. Used in 3D printers and CNC machines.
For a first try, the 28BYJ-48 plus ULN2003.
2. Wire it (28BYJ-48 plus ULN2003)
The motor plugs into the ULN2003 board. On the Pico side:
| ULN2003 | Pico |
|---|---|
| IN1 | GP2 |
| IN2 | GP3 |
| IN3 | GP4 |
| IN4 | GP5 |
+ | 5 V (VBUS) or an external supply |
− | GND |
The ULN2003 (Darlington) inputs accept the Pico’s 3.3 V.
3. Sequence the coils
You apply a sequence of patterns to the 4 pins. The half step sequence (8 steps) gives a finer and smoother movement:
from machine import Pin
from time import sleep_ms
broches = [Pin(p, Pin.OUT) for p in (2, 3, 4, 5)] # IN1..IN4
SEQ = [ # half step (8 steps)
[1,0,0,0], [1,1,0,0], [0,1,0,0], [0,1,1,0],
[0,0,1,0], [0,0,1,1], [0,0,0,1], [1,0,0,1],
]
def pas(nb, sens=1, delai=2):
seq = SEQ if sens > 0 else SEQ[::-1]
for _ in range(nb):
for motif in seq:
for broche, v in zip(broches, motif):
broche.value(v)
sleep_ms(delai)4. Drive it
PAS_PAR_TOUR = 4096 # 28BYJ-48 in half step
while True:
pas(PAS_PAR_TOUR, sens=1) # one clockwise turn
sleep_ms(1000)
pas(PAS_PAR_TOUR, sens=-1) # one counter-clockwise turn
sleep_ms(1000)The motor makes one turn, pauses, then sets off the other way. If the pin order is wrong, it vibrates without turning (see Pitfalls).
Variant NEMA17 plus A4988 (STEP/DIR)
The A4988 only asks for two logic pins:
from machine import Pin
from time import sleep_us, sleep_ms
step = Pin(2, Pin.OUT)
dir_ = Pin(3, Pin.OUT)
def pas_a4988(nb, sens=1, delai_us=800):
dir_.value(1 if sens > 0 else 0)
for _ in range(nb):
step.value(1); sleep_us(2) # STEP pulse
step.value(0); sleep_us(delai_us) # delay = speedThe A4988’s VMOT demands a 100 µF capacitor to GND (without it, destruction at power-up), a 12-24 V motor supply, and the current set on the driver’s trimmer.
Example — A quarter turn per button press
from machine import Pin
from time import sleep_ms, ticks_ms, ticks_diff
broches = [Pin(p, Pin.OUT) for p in (2,3,4,5)]
SEQ = [[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,1,1,0],
[0,0,1,0],[0,0,1,1],[0,0,0,1],[1,0,0,1]]
bouton = Pin(14, Pin.IN, Pin.PULL_UP)
PAS_PAR_TOUR = 4096
def pas(nb):
for _ in range(nb):
for motif in SEQ:
for b, v in zip(broches, motif): b.value(v)
sleep_ms(2)
etat_stable, dernier = 1, 1
chg = ticks_ms()
while True:
lect = bouton.value()
if lect != dernier:
chg = ticks_ms(); dernier = lect
if ticks_diff(ticks_ms(), chg) > 30 and lect != etat_stable:
etat_stable = lect
if etat_stable == 0: # edge: one quarter turn per press
pas(PAS_PAR_TOUR // 4)How to read this code
The non-blocking debouncing follows the same pattern as for the DC motor:
derniermarks the moment the raw reading changes (and restarts the stopwatch),etat_stableholds the state confirmed after 30 ms. The quarter turn is only triggered on a falling edge (PULL_UP→ pressed = 0). Watch out:pas()is blocking. During the quarter turn the loop is frozen and the button is not read, so a press during the movement is ignored. To react continuously, structure the code as non-blocking.
Pitfalls
Wrong phase order. The order of the 4 pins sets the sequencing. Get it wrong and the motor vibrates without turning. Check the IN1..IN4 wiring and the order inside broches.
Not enough current. A 28BYJ-48 draws about 200-300 mA, a NEMA17 1-1.7 A per coil. Powering from the Pico’s 3.3 V makes it reboot; a separate supply (at least for the NEMA17), shared GND.
An A4988 with no capacitor on VMOT. Near-instant destruction at power-up. A 100 µF capacitor between VMOT and GND, close to the driver.
Setting the A4988 current. Too low and steps are lost; too high and it overheats. Calibrate Vref with a multimeter, following the driver’s datasheet.
Lost steps under load. Too fast, or too heavy a load, and the motor skips steps, so the computed position drifts away. Slow it down (a larger delai), raise the torque.
Blocking sequencing. The pas() function is blocking: nothing else runs during the movement. For parallelism, structure the code as non-blocking (see non-blocking programming) or drive it from an interrupt or a timer.
Consumption at rest (28BYJ-48). Energised coils mean about 150 mA continuously, even at rest, and the ULN2003 heats up. Cut the supply (set the 4 pins to 0) between two movements if the position does not need to be held.
Special case — Libraries and microstepping
- MicroPython stepper libraries exist (for the 28BYJ-48, or
AccelStepperstyle with ramps), installable throughmip(see libraries) — they add acceleration and non-blocking behaviour. - The A4988, DRV8825 and TMC2209 drivers support microstepping (1/2 up to 1/256): a smoother and quieter movement, at the price of less effective torque at the intermediate positions.
Where it fits in the project
- Step 2 of the proof of concept phase — validating motor plus driver: a full turn both ways, top speed with no lost steps.
- Step 3 of the proof of concept phase — integration into a precise movement chain (an X axis, a dispenser).
- Step 4 of the concept phase — choosing between stepper, servo and DC motor plus encoder according to accuracy, torque and availability.
The stepper is the actuator of projects that aim at a precise repeatable movement with no sensor, and the results are spectacular in a demonstration.
See also
- MicroPython — the module hub
- Configuring MicroPython GPIO — prerequisite (sequencing the pins)
- Driving a DC motor · Servo — the alternatives
- Non-blocking programming — for non-blocking sequencing
- Interrupts — reading an end stop alongside the movement
- Powering the board — sizing with a power motor
- Driving a stepper motor (Arduino) — the C++ equivalent (
Stepper.h)