ESP32 + DC Motor L298N: Điều khiển tốc độ và chiều quay
Cơ bản1/8/2026- Tác giả: IoTlabs Maker

ESP32 + DC Motor L298N: Điều khiển tốc độ và chiều quay

Điều khiển tốc độ (PWM) và chiều quay động cơ DC bằng driver L298N trên ESP32, nền tảng cho xe robot, băng chuyền, quạt điều khiển được.

esp32l298ndc-motorpwmmotor-driverdieu-khien-dong-co

L298N là driver động cơ DC phổ biến nhất cho người mới học điều khiển động cơ vì tích hợp sẵn 2 cầu H, chịu dòng tới 2A/kênh và có regulator 5V onboard.

Bài này dùng 1 kênh (Motor A) của L298N để điều khiển tốc độ bằng PWM (chân ENA qua ledcAttach) và chiều quay bằng 2 chân số (IN1/IN2), kèm demo tăng/giảm tốc tự động và điều khiển qua lệnh Serial.

Hướng dẫn chi tiết

Điều khiển tốc độ bằng PWM và chiều quay động cơ DC qua driver L298N trên ESP32, kèm demo ramp tốc độ và điều khiển Serial.

1. Giới thiệu

L298N là driver động cơ DC phổ biến nhất để nhập môn vì tích hợp 2 cầu H (H-bridge), chịu dòng tới 2A/kênh, 5V-35V điện áp motor, và có regulator 5V onboard. Một cầu H cho phép đảo chiều dòng điện qua động cơ DC (đổi chiều quay) và điều chế PWM để đổi tốc độ trung bình.

Bài này dùng kênh Motor A của L298N: 2 chân số IN1/IN2 quyết định chiều quay, chân ENA nhận xung PWM quyết định tốc độ, kèm demo tăng/giảm tốc tự động (ramp) và điều khiển thủ công qua Serial.

2. Linh kiện cần có

Linh kiện

Số lượng

Ghi chú

ESP32 DevKit V4

1

Board điều khiển chính

L298N – DC Motor Driver Dual

1

Driver 2A/kênh, 5V-35V, dùng kênh A

Động cơ DC 3-12V

1

Dòng định mức <2A (trong giới hạn L298N)

Nguồn ngoài cho motor (pin/adapter 6-12V)

1

Cấp cho chân VS của L298N, KHÔNG lấy từ ESP32

3. Sơ đồ kết nối

Chân L298N

Chân ESP32

Ghi chú

VCC (logic 5V)

5V (VIN)

Cấp nguồn logic cho IC driver

GND

GND

Bắt buộc GND chung với cả nguồn motor (VS)

IN1

GPIO16

Bit chiều quay Motor A

IN2

GPIO17

Bit chiều quay Motor A

ENA

GPIO18

PWM 1kHz điều khiển tốc độ Motor A

VS

Nguồn ngoài 6-12V (không nối ESP32)

Nguồn công suất cho động cơ, tách biệt khỏi 5V logic ESP32

OUT1 / OUT2

2 dây động cơ DC

Không nối về ESP32 — đây là ngõ ra công suất

GPIO16/17/18 đều không thuộc nhóm boot-strapping pin nên an toàn dùng làm output ngay từ lúc khởi động ESP32.

4. Code mẫu

/*
 * 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. Giải thích code

Core Arduino-ESP32 3.x dùng API PWM mới: ledcAttach(pin, freq, resolution) gắn PWM 1kHz/8-bit trực tiếp vào GPIO18, sau đó ledcWrite(pin, duty) ghi duty cycle 0-255. Hàm setMotor(forward, speed)

Đặt IN1/IN2 đối nghịch nhau để chọn chiều (HIGH/LOW hoặc LOW/HIGH — nếu cả hai cùng mức thì motor thắng/dừng tự do tùy độ chính xác mạch),

Sau đó ghi PWM vào ENA để chọn tốc độ. demoRamp() chạy một chu trình 4 pha: tăng tốc tiến → giảm tốc tiến → tăng tốc lùi → giảm tốc lùi, mỗi bước cách nhau 40ms để mô phỏng gia tốc mượt. Lệnh Serial dạng F<speed>/R<speed>/S cho phép điều khiển thủ công song song với demo.

6. Lưu ý an toàn phần cứng

  • L298N chịu tối đa 2A/kênh liên tục — với động cơ DC dòng định mức <1A là an toàn; động cơ công suất lớn hơn phải tra dòng stall (dòng khi motor bị kẹt cứng, có thể gấp 5-10 lần dòng định mức) trong datasheet trước khi đấu.

  • GND logic (phía ESP32/VCC) và GND công suất (phía VS/nguồn động cơ) bắt buộc nối chung một điểm — nếu không, mức tín hiệu IN1/IN2/ENA sẽ mất mốc tham chiếu, driver hoạt động sai hoặc không phản hồi.

  • L298N tích hợp sẵn diode bảo vệ flyback bên trong IC cho các cuộn dây động cơ DC chuẩn — không cần diode rời như khi dùng relay/solenoid.

  • Tuyệt đối không cấp điện áp VS (6-12V hoặc cao hơn) vào bất kỳ chân GPIO nào của ESP32 — GPIO ESP32 chỉ chịu 3.3V, cấp nhầm sẽ hỏng chip ngay lập tức.

  • Nguồn VS nên tách biệt vật lý (pin/adapter riêng) khỏi nguồn cấp ESP32 — dùng chung một bộ nguồn yếu dễ gây sụt áp, reset ESP32 khi motor khởi động.

7. Lỗi thường gặp

Lỗi

Nguyên nhân

Cách sửa

Động cơ không quay dù Serial báo speed > 0

Chưa cấp nguồn VS (nguồn công suất) cho L298N, chỉ cấp VCC logic

Kiểm tra đã nối nguồn 6-12V riêng vào chân VS và GND chung

ESP32 tự reset khi motor bắt đầu chạy

Dùng chung một nguồn yếu cho cả ESP32 và motor, sụt áp đột ngột

Tách nguồn: motor dùng pin/adapter riêng qua VS, chỉ chia sẻ GND

Motor rung nhẹ nhưng không quay ở tốc độ thấp

Duty cycle PWM quá thấp không đủ vượt ma sát tĩnh của motor

Tăng giá trị PWM tối thiểu (thường >60/255) trước khi motor có thể khởi động quay

8. Tổng kết

Bài này đã trình bày cách dùng L298N điều khiển tốc độ (PWM qua ENA) và chiều quay (IN1/IN2) của động cơ DC trên ESP32, cùng các lưu ý an toàn quan trọng về GND chung và tách nguồn công suất.

Đây là nền tảng cho các dự án xe robot 2 bánh/4 bánh, băng chuyền mini, hoặc bất kỳ ứng dụng nào cần điều khiển động cơ DC hai chiều trong subcategory Điều khiển động cơ.