A pin of the ESP32 can light an LED, but it can't drive a motor: a motor needs far more current. That's what a motor driver is for. In this lesson the L293D chip switches the power to the DC motor, and the ESP32 tells it how fast and which way to turn. A web page with a slider and three buttons lets you control the fan from your phone.
What you'll learn
- Why a motor needs a driver chip
- How an H-bridge changes the direction of a motor
- How PWM sets the speed of a motor
- Why a motor needs a short kick to start at low speed
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- L293D motor driver chip and 3–6 V DC motor (ESP32 IoT Starter Kit)
- A 2.4 GHz Wi-Fi network
- A small propeller or a strip of card for the fan blade
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Make it real: cut a propeller from card about 6 cm across, make a small hole in the middle and push it onto the motor shaft. Tape the motor to the edge of the table so the blade can turn freely.
Wiring
The L293D sits across the gap in the middle of the breadboard, with the notch on the left. Pin 1 is bottom left; the pins are counted anticlockwise.

| From | To |
|---|---|
| L293D pin 1 (EN1) | ESP32 GPIO 25 |
| L293D pin 2 (IN1) | ESP32 GPIO 26 |
| L293D pin 7 (IN2) | ESP32 GPIO 27 |
| L293D pins 3 and 6 (OUT1, OUT2) | The two motor wires |
| L293D pins 4, 5, 12, 13 | GND |
| L293D pins 8 and 16 (VCC2, VCC1) | 5V |
The sketch
Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only). Open the Serial Monitor at 115200 baud to see the address, then open it in a browser on a phone or computer on the same Wi-Fi.
#include <WiFi.h>
#include <WebServer.h>
const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const int EN_PIN = 25; // speed (PWM)
const int IN1_PIN = 26; // direction
const int IN2_PIN = 27;
WebServer server(80);
int speedPercent = 0;
bool forward = true;
const char PAGE[] = R"rawliteral(
<!DOCTYPE html><html><head><meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'><title>Fan</title>
<style>body{font-family:sans-serif;padding:16px;text-align:center}button{font-size:20px;margin:6px;padding:10px 16px}</style></head>
<body><h1>Fan control</h1>
<p style='font-size:40px;margin:8px' id='value'>0 %</p>
<input type='range' id='speed' min='0' max='100' value='0' style='width:90%'>
<p><button onclick="send('dir=f')">Forward</button><button onclick="send('dir=r')">Reverse</button><button onclick="stop()">Stop</button></p>
<script>
const s = document.getElementById('speed');
function send(q) { fetch('/set?' + q); }
function stop() { s.value = 0; s.oninput(); }
s.oninput = function () {
document.getElementById('value').textContent = s.value + ' %';
send('speed=' + s.value);
};
</script></body></html>
)rawliteral";
void applyMotor() {
digitalWrite(IN1_PIN, forward ? HIGH : LOW);
digitalWrite(IN2_PIN, forward ? LOW : HIGH);
if (speedPercent == 0) {
analogWrite(EN_PIN, 0);
return;
}
int duty = map(speedPercent, 1, 100, 90, 255); // below about 90 the motor only hums
analogWrite(EN_PIN, duty);
}
void setup() {
Serial.begin(115200);
pinMode(IN1_PIN, OUTPUT);
pinMode(IN2_PIN, OUTPUT);
pinMode(EN_PIN, OUTPUT);
applyMotor();
WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.print("Open http://");
Serial.println(WiFi.localIP());
server.on("/", []() { server.send(200, "text/html", PAGE); });
server.on("/set", []() {
if (server.hasArg("speed")) {
int wanted = constrain(server.arg("speed").toInt(), 0, 100);
if (speedPercent == 0 && wanted > 0) { // kick-start from standstill
speedPercent = 100;
applyMotor();
delay(150);
}
speedPercent = wanted;
}
if (server.hasArg("dir")) {
bool wantForward = server.arg("dir") == "f";
if (wantForward != forward) { // stop briefly before reversing
analogWrite(EN_PIN, 0);
delay(300);
forward = wantForward;
}
}
applyMotor();
server.send(200, "text/plain", "ok");
});
server.begin();
}
void loop() {
server.handleClient();
}
How it works
The L293D contains two H-bridges. Each is a set of four electronic switches arranged like the letter H around the motor. With IN1 high and IN2 low, current flows through the motor one way; swap them and it flows the other way, so the motor reverses. EN1 switches the whole bridge on and off.
The ESP32 switches EN1 on and off thousands of times per second with PWM. The longer it is on in each cycle, the faster the motor turns. A small DC motor needs a certain push to start turning, so very low values only make it hum. The sketch therefore maps 1–100 % to the range that actually turns the motor, and gives it a short burst at full speed when it starts from standstill. Before reversing it stops the motor for a moment, which is kinder to the motor and the chip.
The L293D and the motor run on the ESP32's 5V pin, powered by USB. That is fine for the small motor in the kit; bigger motors need their own power supply.
Try this next
- Add a DHT11 and switch the fan on automatically above 26 °C.
- Add buttons for 25, 50 and 100 %.
- Show the current speed and direction on the page when it opens.
Troubleshooting
- It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.
- The page doesn't open: the phone or computer must be on the same Wi-Fi as the ESP32, not on mobile data.
- The motor doesn't turn at all: check that the L293D notch is on the left, and that pins 8 and 16 are on 5V and pins 4, 5, 12 and 13 on GND.
-
The motor hums but doesn't start: give the blade a small push, or raise the 90 in the
map()line. - The ESP32 restarts when the motor starts: the USB port can't supply enough current. Use a different port or a powered hub.