In this tutorial , you’ll learn to remotely control a stepper motor with an IR remote in a fun, easy way.
Our stepper motor has a dedicated driver board for simple ESP32 connection.
We’ll use an affordable breadboard power supply (plugs into the breadboard) to power the motor with 9V 1A power, as we won’t drive the motor directly from the ESP32.
The IR sensor connects directly to the ESP32—it has nearly zero power consumption.
(1) x Elegoo ESP32
(2) x 400 tie-points breadboard
(1) x IR receiver module
(1) x IR remote
(1) x ULN2003 stepper motor driver module
(1) x Stepper motor
(6) x F-M wires (Female to Male DuPont wires)
(5) x M-M wire (Male to Male jumper wire)
You can click the blue text link to download the program file to your local device, and double-click the file to open it after the download is complete. Please note: Before opening the file, ensure that you have installed the Arduino IDE development environment and completed the installation of relevant components such as the board support package and driver corresponding to the ESP32 development board. If you have any questions about this operation process, you can refer to the "part 1" chapter of the document for detailed guidance.
With this code, the ESP32 controls a 28BYJ-48 stepper motor using an IR remote control. The motor can be rotated clockwise or counterclockwise, will automatically stop after 3 seconds of inactivity, and enter sleep mode after 5 seconds to save power.
#include <Stepper.h>
Library Include:
#include <Stepper.h>:Arduino Stepper library for controlling stepper motors// Stepper motor parameters (28BYJ-48 + ULN2003 compatible)
const int stepsPerRevolution = 2048; // Steps per full rotation (standard for 28BYJ-48)
const int motorSpeed = 10; // Rotation speed (5-15 rpm, higher = faster)
Stepper Motor Parameters:
const int stepsPerRevolution = 2048;:Steps per full rotation for 28BYJ-48 stepper motorconst int motorSpeed = 10;:Motor rotation speed in RPM (revolutions per minute)// Motor pin definitions (extract individual pins for later sleep control)
const int MOTOR_PIN1 = 5;
const int MOTOR_PIN2 = 19;
const int MOTOR_PIN3 = 18;
const int MOTOR_PIN4 = 21;
Stepper myStepper(stepsPerRevolution, MOTOR_PIN1, MOTOR_PIN2, MOTOR_PIN3, MOTOR_PIN4); // LN1-LN3-LN2-LN4
Motor Pin Configuration:
const int MOTOR_PIN1 = 5;const int MOTOR_PIN2 = 19;const int MOTOR_PIN3 = 18;const int MOTOR_PIN4 = 21;Stepper myStepper(stepsPerRevolution, MOTOR_PIN1, MOTOR_PIN2, MOTOR_PIN3, MOTOR_PIN4);:Creates Stepper object with specified pins// IR receiver pin
const int IR_PIN = 25;
IR Receiver Pin:
const int IR_PIN = 25;:Pin connected to IR receiver module// Motor state variables
bool isRunning = false; // Running state flag
bool isSleeping = false; // Sleep state flag (new)
int motorDirection = 1; // 1=clockwise, -1=counterclockwise
unsigned long lastCmdTime = 0; // Last command received time
const unsigned long STOP_DELAY = 3000; // Auto-stop delay with no operation (3 seconds)
const unsigned long SLEEP_DELAY = 5000; // Delay before entering deep sleep (5 seconds, can be same as STOP_DELAY)
Motor State Variables:
bool isRunning = false;:Flag indicating if motor is currently runningbool isSleeping = false;:Flag indicating if motor is in sleep mode (new)int motorDirection = 1;:Motor rotation direction (1=clockwise, -1=counterclockwise)unsigned long lastCmdTime = 0;:Timestamp of last IR command receivedconst unsigned long STOP_DELAY = 3000;:Auto-stop delay (3 seconds)const unsigned long SLEEP_DELAY = 5000;:Delay before entering sleep mode (5 seconds)// Read IR pulse duration (in microseconds)
unsigned long readPulse(int level, unsigned long timeout = 20000) {
unsigned long start = micros();
while (digitalRead(IR_PIN) == level) {
if (micros() - start > timeout) return 0; // Return 0 on timeout
}
return micros() - start;
}
Read IR Pulse Function:
unsigned long readPulse(int level, unsigned long timeout = 20000):Reads the duration of an IR pulseunsigned long start = micros();:Records start timewhile (digitalRead(IR_PIN) == level) { ... }:Waits until pin level changesif (micros() - start > timeout) return 0;:Returns 0 if timeout occursreturn micros() - start;:Returns pulse duration in microseconds// Motor sleep function (set all motor pins to INPUT high-impedance state) (new)
void motorSleep() {
if (isSleeping) return; // Avoid repeated execution
pinMode(MOTOR_PIN1, INPUT);
pinMode(MOTOR_PIN2, INPUT);
pinMode(MOTOR_PIN3, INPUT);
pinMode(MOTOR_PIN4, INPUT);
isSleeping = true;
Serial.println("Status: Motor entered sleep mode (low power, no heat)");
}
Motor Sleep Function (New):
void motorSleep():Puts motor into sleep mode to save powerif (isSleeping) return;:Avoids repeated executionisSleeping = true;:Updates sleep state flag// Motor wake-up function (restore motor pins to OUTPUT for operation) (new)
void motorWakeup() {
if (!isSleeping) return; // Avoid repeated execution
pinMode(MOTOR_PIN1, OUTPUT);
pinMode(MOTOR_PIN2, OUTPUT);
pinMode(MOTOR_PIN3, OUTPUT);
pinMode(MOTOR_PIN4, OUTPUT);
myStepper.setSpeed(motorSpeed); // Restore motor speed configuration
isSleeping = false;
Serial.println("Status: Motor woken up, ready for operation");
}
Motor Wake-up Function (New):
void motorWakeup():Wakes motor from sleep modeif (!isSleeping) return;:Avoids repeated executionmyStepper.setSpeed(motorSpeed);:Restores motor speed configurationisSleeping = false;:Updates sleep state flag// Process IR code and control motor
void handleIRCode(unsigned long code) {
Serial.print("IR Code: 0x");
Serial.println(code, HEX); // Print raw code for debuggingswitch (code) {
case 0xFF629D: // VOL+ code (update with your actual code from serial monitor)
motorWakeup(); // Wake up motor before operation (new)
motorDirection = 1; // Clockwise rotation
isRunning = true;
lastCmdTime = millis();
Serial.println("Command: VOL+ → Clockwise");
break;
case 0xFFA857: // VOL- code (update with your actual code from serial monitor)
motorWakeup(); // Wake up motor before operation (new)
motorDirection = -1; // Counterclockwise rotation
isRunning = true;
lastCmdTime = millis();
Serial.println("Command: VOL- → Counterclockwise");
break;
case 0xFFFFFFFF: // Long press repeat code
if (isRunning) {
lastCmdTime = millis(); // Extend running time
Serial.println("Command: Long Press → Continue Running");
}
break;
default:
Serial.println("Undefined key, ignored");
break;
}
}
Handle IR Code Function:
void handleIRCode(unsigned long code):Processes IR remote control commandsSerial.print("IR Code: 0x"); Serial.println(code, HEX);:Prints raw IR code for debuggingswitch (code) { ... }:Handles different IR commands
case 0xFF629D::VOL+ button → wake up motor and set clockwise rotationcase 0xFFA857::VOL- button → wake up motor and set counterclockwise rotationcase 0xFFFFFFFF::Long press → extend running timedefault::Undefined key → ignorevoid setup() {
// Initialize motor pins (explicitly set to OUTPUT for initial operation) (new)
pinMode(MOTOR_PIN1, OUTPUT);
pinMode(MOTOR_PIN2, OUTPUT);
pinMode(MOTOR_PIN3, OUTPUT);
pinMode(MOTOR_PIN4, OUTPUT);// Initialize motor
myStepper.setSpeed(motorSpeed);// Initialize IR pin
pinMode(IR_PIN, INPUT);// Initialize serial communication (baud rate 9600)
Serial.begin(9600);
Serial.println("=== System Started Successfully ===");
Serial.println("Operation Guide:");
Serial.println("VOL+ → Motor Clockwise");
Serial.println("VOL- → Motor Counterclockwise");
Serial.println("Auto-stop after 3 seconds of inactivity, auto-sleep after 5 seconds");
Serial.println("-------------------");
}
Setup Function:
pinMode(MOTOR_PIN1, OUTPUT);pinMode(MOTOR_PIN2, OUTPUT);pinMode(MOTOR_PIN3, OUTPUT);pinMode(MOTOR_PIN4, OUTPUT);myStepper.setSpeed(motorSpeed);:Sets motor speedpinMode(IR_PIN, INPUT);:Sets IR pin as inputSerial.begin(9600);:Initializes serial communicationvoid loop() {
// IR signal decoding
if (digitalRead(IR_PIN) == LOW) { // Detect IR start low level
// Verify leader code (9ms low level)
unsigned long lowTime = readPulse(LOW);
if (lowTime < 8000 || lowTime > 10000) return;// Verify 4.5ms high level after leader code unsigned long highTime = readPulse(HIGH); if (highTime < 4000 || highTime > 5000) return; // Read 32-bit data code unsigned long code = 0; for (int i = 0; i < 32; i++) { // Bit start low level (560us) unsigned long bitLow = readPulse(LOW); if (bitLow < 400 || bitLow > 700) return; // Bit data high level (560us=0, 1680us=1) unsigned long bitHigh = readPulse(HIGH); if (bitHigh == 0) return; code <<= 1; if (bitHigh > 1000) code |= 1; // High level >1000us is considered 1 } handleIRCode(code); // Process decoded command delay(50); // Debounce delay}
// Motor operation control
if (isRunning) {
// Check for auto-stop timeout
if (millis() - lastCmdTime > STOP_DELAY) {
isRunning = false;
Serial.println("Status: Auto-stopped due to timeout");
} else {
// Continuous rotation (1 step at a time to avoid blocking program)
myStepper.step(motorDirection);
}
} else {
// Enter sleep mode if stopped and no operation for SLEEP_DELAY (new)
if (millis() - lastCmdTime > SLEEP_DELAY && !isSleeping) {
motorSleep();
}
}
}
Loop Function:
if (digitalRead(IR_PIN) == LOW) { ... }:Detects IR start signalhandleIRCode(code);:Processes decoded commanddelay(50);:Debounce delayif (isRunning) { ... }:Controls motor if runningif (millis() - lastCmdTime > STOP_DELAY) { ... }:Auto-stops motor after inactivitymyStepper.step(motorDirection);:Moves motor one step at a time (non-blocking)else { ... }:Handles stopped stateif (millis() - lastCmdTime > SLEEP_DELAY && !isSleeping) { motorSleep(); }:Enters sleep mode after extended inactivity (new)