ESP32 + DC Motor L298N: Controlling Speed and Direction
Beginner1/8/2026- Author: IoTSpark Maker

ESP32 + DC Motor L298N: Controlling Speed and Direction

Control a DC motor's speed (PWM) and direction using an L298N driver on ESP32 — a foundation for robot cars, conveyor belts, and controllable fans.

esp32l298ndc-motorpwmmotor-driverdieu-khien-dong-co
0 steps2 components

The L298N is the most popular DC motor driver for beginners learning motor control, since it packs in two H-bridges, handles up to 2A per channel, and has an onboard 5V regulator.

This project uses one channel (Motor A) of the L298N to control speed via PWM (the ENA pin through ledcAttach) and direction via two digital pins (IN1/IN2), with an automatic speed ramp demo and manual control over Serial.

Detailed guide

Control a DC motor's speed (PWM) and direction using an L298N driver on ESP32 — a foundation for robot cars, conveyor belts, and controllable fans.

1. Introduction

The L298N is the most popular DC motor driver for beginners, since it packs in two H-bridges, handles up to 2A per channel, 5V-35V motor voltage, and has an onboard 5V regulator. An H-bridge lets you reverse the current through a DC motor (changing rotation direction) and PWM-modulate it to change the average speed.

This project uses the L298N's Motor A channel: two digital pins (IN1/IN2) set the rotation direction, the ENA pin receives a PWM signal that sets the speed, with an automatic speed-ramp demo and manual control over Serial.

2. Components Needed

Component

Qty

Notes

ESP32 DevKit V4

1

The main control board

L298N — Dual DC Motor Driver

1

A 2A/channel, 5V-35V driver — using channel A

3-12V DC motor

1

Rated current <2A (within the L298N's limit)

External motor power (6-12V battery/adapter)

1

Powers the L298N's VS pin — NOT sourced from the ESP32

3. Wiring Diagram

L298N pin

ESP32 pin

Notes

VCC (logic 5V)

5V (VIN)

Powers the driver IC's logic

GND

GND

Must share GND with the motor supply (VS) too

IN1

GPIO16

Motor A direction bit

IN2

GPIO17

Motor A direction bit

ENA

GPIO18

1kHz PWM controlling Motor A speed

VS

External 6-12V supply (not connected to the ESP32)

Power supply for the motor, kept separate from the ESP32's 5V logic

OUT1 / OUT2

The DC motor's 2 wires

Not connected to the ESP32 — this is a power output

GPIO16/17/18 aren't part of the boot-strapping pin group, so they're safe to use as outputs right from ESP32 boot.

4. Sample Code

/*
 * ESP32 + DC Motor L298N - Dieu khien toc do va chieu quay
 * Board: ESP32 DevKit V4 (board_esp32_devkitc)
 * Module: L298N - DC Motor Driver Dual (mod_l298n_motor), dung kenh A
 *
 * Wiring:
 *   L298N VCC (logic 5V) -> ESP32 5V (VIN)
 *   L298N GND            -> ESP32 GND (chung GND voi nguon dong co)
 *   L298N IN1            -> ESP32 GPIO16
 *   L298N IN2            -> ESP32 GPIO17
 *   L298N ENA (PWM toc do)-> ESP32 GPIO18
 *   L298N VS (motor 5V-35V) -> Nguon dong co rieng (PIN/adapter), KHONG lay tu ESP32
 *   L298N OUT1/OUT2       -> 2 day dong co DC (khong noi ve ESP32)
 *
 * An toan phan cung (checklist dong co):
 *  - L298N chiu toi da 2A/kenh - dong co DC nho (<=1A) an toan; dong co lon hon
 *    phai kiem tra dong stall trong datasheet truoc khi dau.
 *  - GND logic (ESP32) va GND motor (VS) BAT BUOC noi chung - neu khong PWM/IN
 *    se khong co muc tham chieu dung, dieu khien loi/nhieu.
 *  - L298N co diode bao ve flyback tich hop san trong IC driver (khong can diode
 *    ngoai rieng cho dong co DC chuan, khac voi relay/solenoid dung cuon day tro).
 *  - Khong cap 12V/VS truc tiep vao bat ky chan GPIO ESP32 (chi 3.3V logic).
 */

static const int IN1_PIN = 16;
static const int IN2_PIN = 17;
static const int ENA_PIN = 18;

static const int PWM_CHANNEL = 0;
static const int PWM_FREQ_HZ = 1000;
static const int PWM_RESOLUTION_BITS = 8; // 0-255

int currentSpeed = 0;      // 0-255
bool currentForward = true;

void setMotor(bool forward, int speed) {
  speed = constrain(speed, 0, 255);
  digitalWrite(IN1_PIN, forward ? HIGH : LOW);
  digitalWrite(IN2_PIN, forward ? LOW : HIGH);
  ledcWrite(PWM_CHANNEL, speed);
  currentForward = forward;
  currentSpeed = speed;
}

void stopMotor() {
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, LOW);
  ledcWrite(PWM_CHANNEL, 0);
  currentSpeed = 0;
}

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println();
  Serial.println("=== ESP32 + L298N DC Motor boot OK ===");

  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);

  ledcAttach(ENA_PIN, PWM_FREQ_HZ, PWM_RESOLUTION_BITS);
  // Note: Arduino-ESP32 core 3.x dung ledcAttach(pin, freq, resolution) va
  // ledcWrite(pin, duty) truc tiep tren so pin (khong can channel rieng nua
  // trong core moi, nhung giu bien PWM_CHANNEL de tuong thich code doc).

  stopMotor();
  Serial.println("Lenh Serial: 'F<0-255>' chay tien, 'R<0-255>' chay lui, 'S' dung.");
  Serial.println("{\"status\":\"ready\"}");
}

void handleSerialCommand() {
  if (!Serial.available()) return;
  char cmd = Serial.read();
  if (cmd == 'S' || cmd == 's') {
    stopMotor();
    Serial.println("{\"cmd\":\"stop\"}");
    return;
  }
  if (cmd == 'F' || cmd == 'f' || cmd == 'R' || cmd == 'r') {
    int val = Serial.parseInt();
    setMotor(cmd == 'F' || cmd == 'f', val);
    Serial.printf("{\"cmd\":\"%s\",\"speed\":%d}\n", (cmd == 'F' || cmd == 'f') ? "forward" : "reverse", currentSpeed);
  }
  while (Serial.available() && Serial.peek() != '\n') Serial.read();
  if (Serial.available()) Serial.read();
}

void demoRamp() {
  // Demo tu dong: tang toc tien -> giam -> tang toc lui -> giam -> dung, lap lai
  static unsigned long lastStepMs = 0;
  static int demoPhase = 0; // 0 ramp up fwd, 1 ramp down fwd, 2 ramp up rev, 3 ramp down rev
  const unsigned long STEP_MS = 40;

  if (millis() - lastStepMs < STEP_MS) return;
  lastStepMs = millis();

  switch (demoPhase) {
    case 0:
      setMotor(true, currentSpeed + 5);
      if (currentSpeed >= 255) demoPhase = 1;
      break;
    case 1:
      setMotor(true, currentSpeed - 5);
      if (currentSpeed <= 0) demoPhase = 2;
      break;
    case 2:
      setMotor(false, currentSpeed + 5);
      if (currentSpeed >= 255) demoPhase = 3;
      break;
    case 3:
      setMotor(false, currentSpeed - 5);
      if (currentSpeed <= 0) demoPhase = 0;
      break;
  }
}

void loop() {
  handleSerialCommand();
  demoRamp();

  static unsigned long lastTelemetryMs = 0;
  if (millis() - lastTelemetryMs >= 1000) {
    lastTelemetryMs = millis();
    Serial.printf("{\"direction\":\"%s\",\"speed\":%d}\n", currentForward ? "forward" : "reverse", currentSpeed);
  }
}

5. Code Walkthrough

Arduino-ESP32 core 3.x uses the new PWM API: ledcAttach(pin, freq, resolution) attaches a 1kHz/8-bit PWM signal directly to GPIO18, then ledcWrite(pin, duty) writes a 0-255 duty cycle. The setMotor(forward, speed) function

sets IN1/IN2 to opposite states to choose direction (HIGH/LOW or LOW/HIGH — if both are the same level, the motor brakes or free-spins depending on the circuit's exact behavior),

then writes the PWM value to ENA to set the speed. demoRamp() runs a 4-phase cycle: ramp up forward → ramp down forward → ramp up reverse → ramp down reverse, each step 40ms apart to simulate smooth acceleration. Serial commands like F<speed>/R<speed>/S allow manual control alongside the demo.

6. Hardware Safety Notes

  • The L298N handles up to 2A per channel continuously — safe with a DC motor rated under 1A; a higher-power motor needs its stall current checked in the datasheet first (the current when the motor is fully jammed, which can be 5-10x the rated current).

  • The logic GND (ESP32/VCC side) and the power GND (VS/motor supply side) must share a common point — otherwise the IN1/IN2/ENA signal levels lose their reference, and the driver misbehaves or doesn't respond.

  • The L298N has built-in flyback protection diodes inside the IC for standard DC motor windings — no separate diodes needed like when using a relay/solenoid.

  • Never feed the VS voltage (6-12V or higher) into any ESP32 GPIO pin — ESP32 GPIOs only tolerate 3.3V, and doing so will instantly destroy the chip.

  • The VS supply should be physically separate (its own battery/adapter) from the ESP32's power — sharing one weak supply can cause voltage sag and reset the ESP32 when the motor starts.

7. Common Issues

Issue

Cause

Fix

The motor doesn't turn even though Serial reports speed > 0

VS (power supply) isn't connected to the L298N — only the logic VCC is powered

Check that a separate 6-12V supply is connected to VS, sharing GND

The ESP32 resets itself when the motor starts running

Sharing one weak power supply between the ESP32 and the motor, causing a sudden voltage drop

Separate the supplies: power the motor via its own battery/adapter through VS, sharing only GND

The motor buzzes slightly but doesn't turn at low speed

The PWM duty cycle is too low to overcome the motor's static friction

Raise the minimum PWM value (typically >60/255) before the motor can start turning

8. Summary

This guide covered using the L298N to control a DC motor's speed (PWM via ENA) and direction (IN1/IN2) on ESP32, along with key safety notes about a shared GND and separating the power supply.

This is the foundation for 2-wheel/4-wheel robot car projects, mini conveyor belts, or any application needing bidirectional DC motor control, in the Motor Control category.