Drive a 4-phase stepper motor through a ULN2003 driver board, rotating it one full turn clockwise, then one full turn counter-clockwise, on a continuous loop.
Unlike a DC motor, which just spins as fast as its voltage allows, a stepper motor moves in discrete, precise increments (“steps”) — and its position depends only on how many pulses it’s been sent, not on load (within its torque limits).

Inside, the stator has coils arranged in 4 phases (A, B, C, D). Energizing them in sequence — A→B→C→D→A→… — pulls the rotor around one step at a time; reversing the sequence reverses the direction. This kit’s motor has 32 magnetic poles (32 steps per revolution at the rotor), geared down 1:64 — so the output shaft needs 32 × 64 = 2048 steps for one full revolution.
A stepper motor’s coils need more current than a GPIO pin can supply directly. The ULN2003 board sits between the ESP32-S3 and the motor, amplifying 4 weak digital signals (IN1–IN4) into the stronger signals (A–D) needed to energize each coil — with 4 onboard LEDs showing which phase is currently active.

A stepper motor uses an open loop position control circuit. Open loop control circuits do not have any feedback so the stepper motor doesn’t know if it didn’t move because something is blocking it’s movement.

The stepper motor runs on 5V — power the breadboard independently and share ground with the ESP32-S3.
Disconnect all power before building the circuit. Reconnect once verified.

Connections:

File: 04_output/code/Stepping_Motor.py
Module: 04_output/code/stepmotor.py
from stepmotor import mystepmotor
import time
myStepMotor=mystepmotor(14,13,12,11)
try:
while True:
myStepMotor.moveSteps(1,32*64,2000)
myStepMotor.stop()
time.sleep_ms(1000)
myStepMotor.moveSteps(0,32*64,2000)
myStepMotor.stop()
time.sleep_ms(1000)
except:
pass
04_output/code/.stepmotor.py → Upload to / — wait for it to finish uploading to the ESP32-S3.Stepping_Motor.py.myStepMotor=mystepmotor(14,13,12,11)
Associates the 4 phase-control pins (A, B, C, D) with GPIO14, 13, 12, and 11.
myStepMotor.moveSteps(1,32*64,2000)
myStepMotor.stop()
moveSteps(direction, steps, us) energizes the coils in sequence for the given number of steps, pausing us microseconds between each — 32*64 steps is exactly one full revolution of the output shaft. stop() de-energizes all coils afterward so the motor doesn’t keep drawing current while idle.
myStepMotor.moveSteps(0,32*64,2000)
Calling moveSteps() with direction=0 instead of 1 reverses the coil-energizing sequence, spinning the shaft the opposite way.
See class mystepmotor for the full API reference (the reference document for this chapter’s mystepmotor class is filed there).
moveAngle() to rotate to a specific angle instead of a full revolution.us delay between steps — notice how too short a delay can cause the motor to skip steps or stall.Adapted from Python_Tutorial.pdf Project 19.1