Stepper Motors (28BYJ-48)

How to control stepper motors with ESP32

A stepper motor moves in precise, discrete steps rather than spinning freely like a DC motor. This makes it ideal for applications where you need exact positioning -- think 3D printers, CNC machines, camera sliders, and automated blinds. The 28BYJ-48 is an inexpensive, widely available stepper motor that comes paired with the ULN2003 driver board. It is the go-to starter stepper for learning the fundamentals.

This guide uses the ESP32-WROOM-32 DevKit. Pin labels and GPIO numbers may differ on your board -- always check your board's pinout diagram.

🔗Key Specs

ParameterValue
Operating voltage$5\,\text{V}$ DC
Phase4-phase unipolar
Step angle$5.625°$ per step (half-step mode)
Steps per revolution$2048$ (half-step), $4096$ with gear reduction in some references
Gear ratio$1:64$
Current draw$\approx 240\,\text{mA}$
Holding torque$\approx 300\,\text{g}\cdot\text{cm}$

Note: There is some confusion online about the exact step count. The 28BYJ-48 is commonly reported as 2048 steps per revolution in half-step mode. You may need to calibrate by running a full revolution and observing the result.

Which motor type? Steppers move in precise, discrete steps. Need continuous rotation with speed control? See DC motors. Need to hold a specific angle? See servo motors. Our motor comparison guide explains the differences.

🔗What You'll Need

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
28BYJ-48 stepper motor1AliExpress | Amazon.de .co.uk .com
ULN2003 stepper driver board1Usually sold together with the motorAliExpress | Amazon.de .co.uk .com
5V power supply1External, do not power from ESP32Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires6AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

🔗Wiring

The ULN2003 driver board acts as an interface between the ESP32 and the motor. It contains Darlington transistor arrays that can handle the motor's current.

🔗ULN2003 Driver to ESP32

ULN2003 PinESP32 PinNotes
IN1GPIO 25Coil A control
IN2GPIO 26Coil B control
IN3GPIO 27Coil C control
IN4GPIO 14Coil D control

🔗Power

ULN2003 PinConnection
+ (VCC)External 5V supply (not ESP32 3.3V)
- (GND)External supply GND and ESP32 GND

The motor connects directly to the ULN2003 board via its white 5-pin connector -- just plug it in.

Warning: Do not power the stepper motor from the ESP32's 3.3V or 5V pin. The motor draws around $240\,\text{mA}$, which can overload the ESP32's voltage regulator. Use a separate 5V supply and connect the grounds.

🔗Required Libraries

The Stepper library comes built into the Arduino IDE -- no installation necessary.

🔗Code Example: Basic Rotation

This sketch rotates the stepper motor one full revolution in each direction.

#include <Stepper.h>

#define STEPS_PER_REV 2048  // 28BYJ-48 in half-step mode

// Note: the pin order matters for correct stepping sequence
// ULN2003 IN1, IN3, IN2, IN4 mapping for the 28BYJ-48
Stepper stepper(STEPS_PER_REV, 25, 27, 26, 14);

void setup() {
    Serial.begin(115200);
    stepper.setSpeed(10);  // RPM (keep low for 28BYJ-48, max ~15 RPM)
    Serial.println("Stepper motor ready.");
}

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

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

Important: Notice the pin order in the Stepper() constructor: 25, 27, 26, 14 (IN1, IN3, IN2, IN4). The 28BYJ-48's internal coil arrangement requires this non-sequential order. If you use 25, 26, 27, 14, the motor will vibrate but not spin properly.

🔗Code Example: Rotate by Degrees

This helper function lets you command the motor in degrees rather than steps.

#include <Stepper.h>

#define STEPS_PER_REV 2048

Stepper stepper(STEPS_PER_REV, 25, 27, 26, 14);

void rotateDegrees(float degrees) {
    int steps = (int)(degrees / 360.0 * STEPS_PER_REV);
    stepper.step(steps);
}

void setup() {
    Serial.begin(115200);
    stepper.setSpeed(10);
}

void loop() {
    Serial.println("Rotate 90 degrees CW");
    rotateDegrees(90);
    delay(1000);

    Serial.println("Rotate 90 degrees CCW");
    rotateDegrees(-90);
    delay(1000);

    Serial.println("Rotate 180 degrees CW");
    rotateDegrees(180);
    delay(2000);
}

🔗How It Works

🔗Stepping Sequence

A stepper motor has multiple coils arranged around a rotor with permanent magnets. By energizing the coils in a specific sequence, you create a rotating magnetic field that pulls the rotor from one position to the next. Each switch in the sequence moves the rotor by one "step."

The 28BYJ-48 is a 4-phase unipolar motor. In half-step mode (which the Stepper library uses), it energizes the coils in an 8-step sequence:

StepCoil ACoil BCoil CCoil D
1HIGHLOWLOWLOW
2HIGHHIGHLOWLOW
3LOWHIGHLOWLOW
4LOWHIGHHIGHLOW
5LOWLOWHIGHLOW
6LOWLOWHIGHHIGH
7LOWLOWLOWHIGH
8HIGHLOWLOWHIGH

The internal gear train multiplies the number of steps needed for one output shaft revolution, which is what gives the motor its fine positioning ability.

🔗Speed Limits

The 28BYJ-48 is a geared motor designed for precision, not speed. Setting setSpeed() above approximately $15\,\text{RPM}$ will cause it to skip steps or stall. For reliable operation, keep the speed at $10$--$12\,\text{RPM}$.

🔗Troubleshooting

ProblemPossible CauseSolution
Motor vibrates but does not rotateWrong pin order in constructorUse Stepper(STEPS, 25, 27, 26, 14) -- note the non-sequential order
Motor gets hotCoils energized while idleThe Stepper library keeps coils energized after step(). Write all pins LOW when not moving to reduce heat.
Motor stalls or skips stepsSpeed set too highReduce speed to $10\,\text{RPM}$ or below
Motor moves less than a full revolutionWrong steps-per-revolution valueTry $2048$ or adjust based on observation
Motor does not move at allNo external power to ULN2003Provide 5V from an external supply, not the ESP32
ESP32 resets when motor runsPower draw too high or no shared GNDUse separate 5V supply with shared ground

Tip: To prevent the motor from overheating when idle, set all motor pins to LOW after completing a movement. Add this function and call it when the motor should be at rest:

void disableMotor() {
    digitalWrite(25, LOW);
    digitalWrite(26, LOW);
    digitalWrite(27, LOW);
    digitalWrite(14, LOW);
}

Note that this releases the holding torque, so the shaft can be turned manually.

🔗Next Steps

  • Build a motorized camera slider or turntable
  • Create an automated window blind controller
  • Use a stepper for precise fluid dispensing (peristaltic pump)
  • Explore the AccelStepper library for acceleration/deceleration profiles and non-blocking movement