Overview
The ULN2003 5V Stepper Motor Driver is a compact controller board paired with the popular 28BYJ-48 4-phase stepper motor. The board uses the ULN2003AN Darlington array to switch the motor's coils based on signals from a microcontroller, while four onboard LEDs (A, B, C, D) light up in sequence so you can visually confirm the motor is stepping.
This kit is the most affordable way to learn stepper motor control. It's perfect for slow, precise movement projects: clocks, smart curtains, robotic arms, model trains, automated dispensers, and CNC plotters. The 28BYJ-48 motor's 64:1 internal gearbox produces 4096 steps per full revolution in half-step mode, giving you very fine angular control without complex code.
The driver works with Arduino, ESP32, Raspberry Pi, and Pico — anything with four GPIO pins to spare. Inputs are 3.3V/5V tolerant and the included Stepper.h Arduino library makes basic motion a few lines of code. A jumper near the screw terminals enables or disables motor power without unplugging the board.
At a Glance
Specifications
| Parameter | Value |
| Driver IC | ULN2003AN (7-channel Darlington array) |
| Motor Power Input | 5V DC (typical), 5-12V supported |
| Logic Input Voltage | 3.3V / 5V compatible |
| Input Pins | IN1, IN2, IN3, IN4 |
| Motor Connector | 5-pin JST (matches 28BYJ-48 cable) |
| Indicator LEDs | 4 (one per coil channel) |
| Power Jumper | On/Off, removes motor power |
| 28BYJ-48 Step Angle | 5.625° / 64 (geared output) |
| Steps per Revolution (full step) | 2048 (with 64:1 gearbox) |
| Steps per Revolution (half step) | 4096 (with 64:1 gearbox) |
| 28BYJ-48 Coil Resistance | ~50 ohms per phase |
| 28BYJ-48 Pull-in Torque | ~300 g·cm typical |
| Motor Cable | 5-wire unipolar (red common, +4 phases) |
Pinout Diagram
Connect IN1 through IN4 to four GPIO pins on your microcontroller — these are the coil control signals. The white 5-pin connector at the top accepts the 28BYJ-48 motor's keyed cable in only one orientation. Power the motor through the screw terminals labeled + and - (5-12V DC), and use the on/off jumper to cut motor power without disconnecting wires. The four LEDs labeled A, B, C, D light up in sequence as IN1-IN4 are driven, giving you instant visual feedback that your stepping pattern is working.
Wiring Guide
Arduino Wiring
The Arduino's 5V output can power the ULN2003 driver and 28BYJ-48 motor for light-duty projects, but for reliable performance use a separate 5V supply on the board's screw terminals and share grounds.
| Driver Pin | Arduino Pin |
|---|---|
| IN1 | Digital Pin 8 |
| IN2 | Digital Pin 9 |
| IN3 | Digital Pin 10 |
| IN4 | Digital Pin 11 |
| + (motor power) | 5V (or external 5V supply) |
| - (motor ground) | GND |
ESP32 Wiring
The ULN2003 inputs are 3.3V tolerant, so the ESP32 drives them directly. Power the motor side from a separate 5V source — the ESP32's onboard regulator can't supply enough current for the stepper coils.
| Driver Pin | ESP32 Pin | Details |
|---|---|---|
| IN1 | GPIO 14 | |
| IN2 | GPIO 27 | |
| IN3 | GPIO 26 | |
| IN4 | GPIO 25 | |
| + (motor power) | External 5V | Wall adapter or USB power bank |
| - (motor ground) | GND | Tie ESP32 GND and supply GND together |
Raspberry Pi Wiring
The Pi's 3.3V GPIO drives the ULN2003 inputs without level shifting. Don't try to power the motor from the Pi's 5V rail; use an external supply on the driver's screw terminals and share grounds.
| Driver Pin | Raspberry Pi Pin | Details |
|---|---|---|
| IN1 | Pin 11 (GPIO 17) | |
| IN2 | Pin 13 (GPIO 27) | |
| IN3 | Pin 15 (GPIO 22) | |
| IN4 | Pin 16 (GPIO 23) | |
| + (motor power) | External 5V supply | Do NOT use Pi 5V |
| - (motor ground) | Pin 6 (GND) | Common ground with supply GND |
Raspberry Pi Pico Wiring
The Pico's 3.3V GPIO is fully compatible with the ULN2003 inputs. The Pico's VBUS (USB 5V) can power small steppers in short bursts, but a separate 5V supply is more reliable for continuous motion.
| Driver Pin | Pico Pin | Details |
|---|---|---|
| IN1 | GP2 | |
| IN2 | GP3 | |
| IN3 | GP4 | |
| IN4 | GP5 | |
| + (motor power) | VBUS or external 5V | VBUS = 5V from USB cable |
| - (motor ground) | GND | Common ground required |
Code Examples
Arduino (Stepper.h library)
// 28BYJ-48 + ULN2003 - Arduino full revolution example
// IN1->D8, IN2->D9, IN3->D10, IN4->D11
// NOTE: Stepper.h pin order is (IN1, IN3, IN2, IN4) — not 1,2,3,4!
#include
// Steps per revolution for the 28BYJ-48 (full step with 64:1 gearbox)
const int STEPS_PER_REV = 2048;
// Stepper(steps, pin1, pin3, pin2, pin4) — note the swapped order
Stepper motor(STEPS_PER_REV, 8, 10, 9, 11);
void setup() {
Serial.begin(9600);
motor.setSpeed(10); // RPM (max ~15 RPM for 28BYJ-48)
}
void loop() {
Serial.println("One revolution clockwise");
motor.step(STEPS_PER_REV);
delay(1000);
Serial.println("One revolution counter-clockwise");
motor.step(-STEPS_PER_REV);
delay(1000);
}
Raspberry Pi (Python)
#!/usr/bin/env python3
# 28BYJ-48 + ULN2003 - Raspberry Pi half-step example
# IN1->GPIO 17, IN2->GPIO 27, IN3->GPIO 22, IN4->GPIO 23
import RPi.GPIO as GPIO
import time
PINS = [17, 27, 22, 23] # IN1, IN2, IN3, IN4
# 8-step half-stepping sequence for smoother motion
HALF_STEP = [
[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],
]
GPIO.setmode(GPIO.BCM)
for pin in PINS:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, 0)
def step(direction=1, steps=4096, delay_s=0.001):
seq = HALF_STEP if direction > 0 else list(reversed(HALF_STEP))
for i in range(steps):
for pin, val in zip(PINS, seq[i % 8]):
GPIO.output(pin, val)
time.sleep(delay_s)
try:
print("Rotating one full revolution clockwise...")
step(direction=1, steps=4096) # 4096 = one revolution in half-step mode
time.sleep(1)
print("Rotating one full revolution counter-clockwise...")
step(direction=-1, steps=4096)
finally:
for pin in PINS:
GPIO.output(pin, 0)
GPIO.cleanup()
Raspberry Pi Pico (MicroPython)
# 28BYJ-48 + ULN2003 - Pico MicroPython half-step example
# IN1->GP2, IN2->GP3, IN3->GP4, IN4->GP5
from machine import Pin
import time
pins = [Pin(2, Pin.OUT), Pin(3, Pin.OUT), Pin(4, Pin.OUT), Pin(5, Pin.OUT)]
HALF_STEP = [
(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 step(direction=1, steps=4096, delay_ms=2):
seq = HALF_STEP if direction > 0 else tuple(reversed(HALF_STEP))
for i in range(steps):
pattern = seq[i % 8]
for pin, val in zip(pins, pattern):
pin.value(val)
time.sleep_ms(delay_ms)
try:
print("One full revolution clockwise")
step(direction=1, steps=4096)
time.sleep(1)
print("One full revolution counter-clockwise")
step(direction=-1, steps=4096)
finally:
for p in pins:
p.value(0)
ESP32 (MicroPython)
# 28BYJ-48 + ULN2003 - ESP32 MicroPython example
# IN1->GPIO 14, IN2->GPIO 27, IN3->GPIO 26, IN4->GPIO 25
from machine import Pin
import time
pins = [Pin(14, Pin.OUT), Pin(27, Pin.OUT), Pin(26, Pin.OUT), Pin(25, Pin.OUT)]
# Full-step sequence (faster, slightly less smooth than half-step)
FULL_STEP = [
(1, 1, 0, 0),
(0, 1, 1, 0),
(0, 0, 1, 1),
(1, 0, 0, 1),
]
def step(direction=1, steps=2048, delay_ms=3):
seq = FULL_STEP if direction > 0 else tuple(reversed(FULL_STEP))
for i in range(steps):
pattern = seq[i % 4]
for pin, val in zip(pins, pattern):
pin.value(val)
time.sleep_ms(delay_ms)
try:
while True:
print("CW one revolution")
step(direction=1, steps=2048) # 2048 = one revolution in full-step mode
time.sleep(1)
print("CCW one revolution")
step(direction=-1, steps=2048)
time.sleep(1)
except KeyboardInterrupt:
for p in pins:
p.value(0)