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.

ActuatorPositionTorqueAccuracy
Standard servo0-180°lowmedium
DC motornonegoodnone (without an encoder)
Stepper motorstep by stepmediumexcellent

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:

ULN2003Pico
IN1GP2
IN2GP3
IN3GP4
IN4GP5
+5 V (VBUS) or an external supply
−GND

The ULN2003 (Darlington) inputs accept the Pico’s 3.3 V.

Wiring a 28BYJ-48 through its ULN2003 board on the Pico: IN1-IN4 on GP2-GP5, a 5 V supply (VBUS or external) on "+", the motor plugged into the board, shared ground.|640

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 = speed

The 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: dernier marks the moment the raw reading changes (and restarts the stopwatch), etat_stable holds 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 AccelStepper style with ramps), installable through mip (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