Documentation

28BYJ-48 5V 4-Phase Stepper Motor for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual
Documentation / 28BYJ-48 5V 4-Phase Stepper Motor for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

28BYJ-48 5V 4-Phase Stepper Motor for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

Overview

The 28BYJ-48 is the most-used stepper motor in the maker world: a 5V unipolar stepper with a built-in gearbox that trades speed for torque and precision. The motor itself steps 32 times per revolution, but an internal ~1:64 gear train multiplies that to roughly 2048 steps per output-shaft turn (4096 half-steps) — fine enough to point, index, and position mechanisms with no encoder at all.

Five colored wires leave the motor: red is the +5V center tap shared by both windings, and blue, pink, yellow, and orange are the four coil ends. Energize the coils in sequence and the shaft walks around; reverse the sequence and it walks back. Because each coil needs ~90 mA — more than a GPIO pin can source — the motor is always driven through a transistor array, almost universally the ULN2003 driver board it ships with in most kits (ShillehTek's motor+driver bundle includes it).

It's happiest in light-duty jobs: camera sliders and pan mounts, clock and gauge mechanisms, blinds and vent flaps, dispensers, and desktop automatons. It is quiet, cheap, runs straight off 5V, and — unlike a servo — rotates continuously with repeatable steps in both directions.

At a Glance

Type
Unipolar stepper, 5-wire
Steps / Revolution
~2048 (half-step 4096)
Gear Ratio
~1:64 internal
Supply Voltage
5V DC
Driver
ULN2003 board (IN1-IN4)
Max Speed
~15 RPM at output shaft

Specifications

Parameter Value
Motor Type Unipolar permanent-magnet gear stepper
Rated Voltage 5V DC
Phases 4 (common center tap on red wire)
Coil Resistance ~50 ohm per phase (~90 mA at 5V)
Stride Angle 5.625° / 64 per half-step (motor: 11.25°)
Steps per Output Revolution ~2048 full-step / ~4096 half-step
Gear Reduction ~1:63.68 (commonly quoted 1:64)
Holding Torque >34 mN·m (~300 g·cm)
Practical Speed ~10-15 RPM continuous
Wire Colors Red = +5V, Orange/Yellow/Pink/Blue = coils 1-4
Shaft 5 mm flatted output shaft

Pinout Diagram

The red wire is the shared +5V center tap; each of the other four wires grounds one coil through the ULN2003's darlington outputs. The connector keys into the driver board one way, so with the standard kit you can't miswire it. The coil order the driver sees is IN1 = blue, IN2 = pink, IN3 = yellow, IN4 = orange — the sequences in the code below assume exactly that.

28BYJ-48 5V stepper motor wiring diagram showing five wires with coil connections and +5V red center tap

Wiring Guide

All wiring below goes through the ULN2003 driver board: motor connector into the keyed socket, then four logic inputs plus power.

Arduino Wiring

ULN2003 Pin Arduino Pin Details
IN1 D8
IN2 D9
IN3 D10
IN4 D11
+ / - 5V / GND Motor power
Warning: The motor can pull ~250 mA with two coils energized. USB power handles one motor, but for anything more (or if your board resets), feed the ULN2003 from a separate 5V supply and tie the grounds together.

ESP32 Wiring

The ULN2003 inputs switch fine from 3.3V logic; motor power still comes from 5V (VIN/USB).

ULN2003 Pin ESP32 Pin Details
IN1 / IN2 / IN3 / IN4 GPIO 14 / 27 / 26 / 25 3.3V logic OK
+ VIN (5V) Motor supply
- GND Common ground

Raspberry Pi Wiring

ULN2003 Pin Pi Pin Details
IN1 GPIO 17 (pin 11)
IN2 GPIO 18 (pin 12)
IN3 GPIO 27 (pin 13)
IN4 GPIO 22 (pin 15)
+ / - Pin 2 (5V) / Pin 6 (GND) Motor power
Tip: One motor runs fine from the Pi's 5V rail; for multiple motors use an external 5V supply so you don't brown out the Pi.

Raspberry Pi Pico Wiring

ULN2003 Pin Pico Pin Details
IN1 / IN2 / IN3 / IN4 GP2 / GP3 / GP4 / GP5
+ VBUS (pin 40, 5V) USB 5V
- GND (pin 38)

Code Examples

Each example turns the shaft one full revolution clockwise, pauses, then returns counter-clockwise — the standard smoke test that verifies coil order, direction, and step count in one run.

Arduino

stepper28byj_arduino.ino
// 28BYJ-48 + ULN2003 - Arduino Example (built-in Stepper library)
// IN1->8, IN2->9, IN3->10, IN4->11

#include <Stepper.h>

const int STEPS_PER_REV = 2048;      // full-step mode

// Note the pin order: IN1, IN3, IN2, IN4 for correct sequencing
Stepper stepper(STEPS_PER_REV, 8, 10, 9, 11);

void setup() {
  Serial.begin(9600);
  stepper.setSpeed(12);              // RPM (10-15 is reliable)
}

void loop() {
  Serial.println("One revolution clockwise...");
  stepper.step(STEPS_PER_REV);
  delay(1000);

  Serial.println("One revolution counter-clockwise...");
  stepper.step(-STEPS_PER_REV);
  delay(1000);
}

ESP32 (MicroPython)

stepper28byj_esp32.py
# 28BYJ-48 + ULN2003 - ESP32 MicroPython Example (half-step)
# IN1->GPIO 14, IN2->GPIO 27, IN3->GPIO 26, IN4->GPIO 25

from machine import Pin
import time

pins = [Pin(p, Pin.OUT) for p in (14, 27, 26, 25)]

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),
]
STEPS_PER_REV = 4096                  # half-steps per output rev

def move(steps, delay_us=1200):
    seq = HALF_STEP if steps > 0 else HALF_STEP[::-1]
    for i in range(abs(steps)):
        for pin, v in zip(pins, seq[i % 8]):
            pin.value(v)
        time.sleep_us(delay_us)
    for pin in pins:                  # release coils (no heat)
        pin.value(0)

while True:
    print("One revolution clockwise...")
    move(STEPS_PER_REV)
    time.sleep(1)
    print("One revolution counter-clockwise...")
    move(-STEPS_PER_REV)
    time.sleep(1)

Raspberry Pi (Python)

stepper28byj_rpi.py
#!/usr/bin/env python3
# 28BYJ-48 + ULN2003 - Raspberry Pi Example (half-step)
# IN1->GPIO17, IN2->GPIO18, IN3->GPIO27, IN4->GPIO22

import RPi.GPIO as GPIO
import time

PINS = [17, 18, 27, 22]
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),
]
STEPS_PER_REV = 4096

GPIO.setmode(GPIO.BCM)
for p in PINS:
    GPIO.setup(p, GPIO.OUT, initial=0)

def move(steps, delay=0.0012):
    seq = HALF_STEP if steps > 0 else HALF_STEP[::-1]
    for i in range(abs(steps)):
        for pin, v in zip(PINS, seq[i % 8]):
            GPIO.output(pin, v)
        time.sleep(delay)
    for p in PINS:
        GPIO.output(p, 0)            # release coils

try:
    while True:
        print("One revolution clockwise...")
        move(STEPS_PER_REV)
        time.sleep(1)
        print("One revolution counter-clockwise...")
        move(-STEPS_PER_REV)
        time.sleep(1)
except KeyboardInterrupt:
    GPIO.cleanup()
    print("Stopped by user")

Raspberry Pi Pico (MicroPython)

stepper28byj_pico.py
# 28BYJ-48 + ULN2003 - Pico MicroPython Example (half-step)
# IN1->GP2, IN2->GP3, IN3->GP4, IN4->GP5, +->VBUS, -->GND

from machine import Pin
import time

pins = [Pin(p, Pin.OUT) for p in (2, 3, 4, 5)]

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),
]
STEPS_PER_REV = 4096

def move(steps, delay_us=1200):
    seq = HALF_STEP if steps > 0 else HALF_STEP[::-1]
    for i in range(abs(steps)):
        for pin, v in zip(pins, seq[i % 8]):
            pin.value(v)
        time.sleep_us(delay_us)
    for pin in pins:
        pin.value(0)

while True:
    print("One revolution clockwise...")
    move(STEPS_PER_REV)
    time.sleep(1)
    print("One revolution counter-clockwise...")
    move(-STEPS_PER_REV)
    time.sleep(1)

Frequently Asked Questions

The motor vibrates and buzzes but doesn't turn. What's wrong?
The coil sequence is out of order — usually IN2 and IN3 swapped, or a library given pins in straight 1-2-3-4 order when it expects 1-3-2-4 (note the Arduino example's Stepper(2048, 8, 10, 9, 11)). It can also be stepping too fast: drop to ~12 RPM or raise the per-step delay and it will pull in cleanly.
Is it really 2048 steps per revolution? I've seen 2038 and 4096.
The true gear ratio is about 63.68:1, giving ~2037.9 full steps per output revolution; 2048 is the convenient round number and the error (~0.5%) is invisible in most projects. Half-stepping doubles whichever number you use — hence 4096 in the MicroPython/Python examples. For a clock or anything cumulative, use 2038/4076 to stop the error accumulating.
How fast can it spin?
Torque collapses beyond roughly 15 RPM at 5V — the motor starts skipping steps and eventually just hums. Around 10-12 RPM is the sweet spot for reliable motion under load. If you need real speed, this is the wrong motor: look at a NEMA 17 with an A4988 driver (ShillehTek stocks both).
Why does the motor get warm when idle?
If you leave coils energized after moving, ~90 mA per active coil flows continuously and the can warms up. That current buys holding torque — useful if gravity would back-drive your mechanism — but if you don't need it, drive all four inputs low after each move (every example above does this) and the motor idles cold.
Half-step vs full-step — which should I use?
Half-stepping (the 8-state sequence) doubles resolution to ~4096 positions, runs noticeably smoother, and is the default in the MicroPython/Python examples. Full-step two-coil drive gives slightly more torque per step and matches the Arduino Stepper library. For most projects half-step's smoothness wins; switch to full-step if you're torque-starved.
Can I run it from 3.3V to avoid the 5V rail?
It will move at 3.3V but with dramatically less torque — coil current drops by a third and the gearbox friction eats much of what's left. Keep motor power at 5V and let the ULN2003 handle the level difference: its inputs switch fine from 3.3V GPIO, which is exactly how the ESP32 and Pico wiring above works.
Can it hold position when powered off?
Somewhat — the gear train adds enough friction that light mechanisms stay put, but there's no electrical holding torque without current. For loads that can back-drive (a lifted arm, a spring), either keep the final coil pattern energized while holding, or design the mechanism so gravity is neutral. And remember position is open-loop: add a limit switch or home sensor if absolute position matters after power cycles.

Related Tutorials