Xe tránh vật cản với ESP32: cảm biến siêu âm HC-SR04 trên servo quét, tự chọn đường

Làm xe robot tự tránh vật cản với ESP32: cảm biến siêu âm HC-SR04 gắn trên servo quét trái phải, cầu phân áp cho chân ECHO, thuật toán chọn đường, lưu đồ và thí nghiệm STEM.

Xe tránh vật cản là bước đầu tiên từ "xe điều khiển" sang "robot tự hành". Xe tự chạy thẳng; khi cảm biến siêu âm thấy vật phía trước, xe dừng, lùi lại, quay cảm biến nhìn sang hai bên rồi chọn hướng thoáng hơn để đi tiếp. Dự án dùng lại khung xe, L298N và cách nối động cơ của bài xe robot điều khiển qua Wi-Fi, thêm cảm biến HC-SR04 gắn trên một servo SG90.

Bài viết giải thích cách HC-SR04 đo khoảng cách, cách nối an toàn với ESP32 (chân ECHO ra 5 V), chương trình tránh vật cản kèm lưu đồ, và gợi ý thí nghiệm STEM.

1. HC-SR04 đo khoảng cách thế nào

Hình minh họa tín hiệu của cảm biến HC-SR04: xung TRIG 10 micro giây, 8 chu kỳ sóng 40 kHz, xung ECHO rộng bằng thời gian sóng đi và về, công thức khoảng cách bằng thời gian chia 58, và cầu phân áp 1 kΩ 2 kΩ hạ ECHO từ 5 V xuống 3,3 V
Hình minh họa: tín hiệu TRIG, ECHO và cầu phân áp cho chân ECHO.
  1. ESP32 phát một xung 10 µs vào chân TRIG.
  2. Cảm biến phát 8 chu kỳ siêu âm 40 kHz và đưa chân ECHO lên mức cao.
  3. Khi nhận được sóng dội về, ECHO xuống mức thấp. Độ rộng xung ECHO bằng thời gian sóng đi tới vật và quay về.
  4. Âm thanh đi khoảng 0,0343 cm mỗi micro giây, nên khoảng cách (cm) ≈ thời gian (µs) / 58. Ví dụ ECHO rộng 1450 µs ứng với 25 cm.

HC-SR04 đo khoảng 2–400 cm theo mô tả của nhà sản xuất. Vật mềm như vải, rèm, hoặc mặt phẳng nghiêng nhiều làm sóng dội về yếu hoặc lệch hướng, nên có thể đo sai.

Lưu ý mức điện áp: HC-SR04 loại thường cần 5 V và chân ECHO ra 5 V. Phải hạ xuống khoảng 3,3 V bằng cầu phân áp 1 kΩ / 2 kΩ trước khi vào ESP32: 5 V × 2 / 3 ≈ 3,3 V. Chân TRIG nhận mức 3,3 V của ESP32 được.

2. Linh kiện cần chuẩn bị

  • Xe robot hai bánh đã lắp theo bài xe robot ESP32 điều khiển qua Wi-Fi: ESP32 DevKit V1, L298N, khung xe 2 động cơ, khay pin 18650 loại 2S.
  • Cảm biến siêu âm HC-SR04 và giá gắn.
  • Servo SG90.
  • Điện trở 1 kΩ và 2 kΩ (hoặc hai điện trở 1 kΩ nối tiếp thay cho 2 kΩ).
  • Dây cắm test board, breadboard mini để cắm cầu phân áp.

3. Sơ đồ nối dây

Sơ đồ khối xe tránh vật cản: HC-SR04 TRIG vào D18, ECHO qua cầu phân áp vào D19; servo SG90 tín hiệu vào D23; ESP32 điều khiển L298N và hai động cơ qua D27 D26 D25 và D13 D33 D32; pin 2 x 18650 cấp nguồn
Hình minh họa: sơ đồ khối và bảng nối dây.
  • HC-SR04 và servo lấy 5 V từ cùng nguồn cấp cho chân VIN của ESP32 (cọc 5V của L298N hoặc mạch hạ áp). GND nối chung.
  • Dán HC-SR04 lên càng servo, đặt ở đầu xe. Khi servo ở 90°, cảm biến phải nhìn thẳng về phía trước.
  • Phần động cơ giữ nguyên như bài xe robot Wi-Fi.

4. Thuật toán tránh vật cản

Lưu đồ thuật toán tránh vật cản: đo khoảng cách phía trước, nếu xa hơn 25 cm thì chạy thẳng, nếu không thì dừng, lùi, quét hai bên; nếu cả hai bên gần thì quay đầu, nếu không thì rẽ về bên xa hơn
Hình minh họa: lưu đồ tương ứng với hàm loop() của chương trình.

Mỗi vòng lặp, chương trình đo khoảng cách phía trước. Xa hơn 25 cm thì chạy tiếp. Gần hơn thì xe dừng, lùi một đoạn ngắn để có chỗ quay, rồi quay servo nhìn sang phải và sang trái. Bên nào thoáng hơn thì xe rẽ về bên đó. Nếu cả hai bên đều bị chặn, xe quay đầu.

5. Chương trình

/*
  PYWORLD.VN - Xe robot tự tránh vật cản: ESP32 + cảm biến siêu âm HC-SR04 trên servo quét + L298N
  HC-SR04: TRIG -> GPIO18, ECHO -> cầu phân áp 1k/2k -> GPIO19, VCC 5 V. Servo quét -> GPIO23
  Động cơ như bài xe robot Wi-Fi: ENA 27, IN1 26, IN2 25 (trái); ENB 13, IN3 33, IN4 32 (phải)
*/

#define TRIG_PIN  18
#define ECHO_PIN  19
#define SERVO_PIN 23
#define ENA 27
#define IN1 26
#define IN2 25
#define ENB 13
#define IN3 33
#define IN4 32

const int KHOANG_DUNG = 25;     // cm: gặp vật gần hơn thì dừng và tìm đường
const int TOC_DO = 60;          // % khi chạy thẳng
const int TOC_DO_QUAY = 70;     // % khi quay tại chỗ
const int GOC_THANG = 90, GOC_PHAI = 20, GOC_TRAI = 160;   // góc servo, chỉnh theo cách lắp

void banh(int chanEN, int chanA, int chanB, int v) {
  v = constrain(v, -100, 100);
  digitalWrite(chanA, v > 0 ? HIGH : LOW);
  digitalWrite(chanB, v < 0 ? HIGH : LOW);
  ledcWrite(chanEN, (uint32_t)abs(v) * 1023 / 100);
}

void chay(int trai, int phai) {
  banh(ENA, IN1, IN2, trai);
  banh(ENB, IN3, IN4, phai);
}

void datGoc(int goc) {                       // servo 50 Hz, 500..2400 micro giây cho 0..180 độ
  uint32_t us = 500 + 1900UL * constrain(goc, 0, 180) / 180;
  ledcWrite(SERVO_PIN, (uint32_t)((uint64_t)us * 65536 / 20000));
}

// Một lần đo: xung TRIG 10 micro giây, đo độ rộng xung ECHO; quá 30 ms (khoảng 5 m) coi như không có vật
float doMotLan() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  unsigned long t = pulseIn(ECHO_PIN, HIGH, 30000);
  return (t == 0) ? 400 : t / 58.0;          // cm: âm thanh đi và về, khoảng 58 micro giây cho mỗi cm
}

// Lấy trung vị của 3 lần đo để bỏ số đo sai lẻ tẻ
float doKhoangCach() {
  float a = doMotLan(); delay(15);
  float b = doMotLan(); delay(15);
  float c = doMotLan();
  return max(min(a, b), min(max(a, b), c));
}

float nhin(int goc) {
  datGoc(goc);
  delay(300);                                // chờ servo quay tới nơi
  return doKhoangCach();
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  int chan[] = {IN1, IN2, IN3, IN4};
  for (int p : chan) pinMode(p, OUTPUT);
  ledcAttach(ENA, 1000, 10);
  ledcAttach(ENB, 1000, 10);
  ledcAttach(SERVO_PIN, 50, 16);
  datGoc(GOC_THANG);
  chay(0, 0);
  delay(2000);                               // đặt xe xuống sàn
}

void loop() {
  float truoc = doKhoangCach();
  if (truoc > KHOANG_DUNG) {
    chay(TOC_DO, TOC_DO);
    delay(40);
    return;
  }

  // Gặp vật cản: dừng, lùi một đoạn, quay đầu cảm biến nhìn hai bên
  chay(0, 0);
  Serial.printf("Truoc: %.0f cm -> dung", truoc);
  delay(200);
  chay(-TOC_DO, -TOC_DO);
  delay(300);
  chay(0, 0);
  float phai = nhin(GOC_PHAI);
  float trai = nhin(GOC_TRAI);
  datGoc(GOC_THANG);
  Serial.printf(", quet: phai %.0f cm, trai %.0f cm", phai, trai);

  if (phai < KHOANG_DUNG && trai < KHOANG_DUNG) {
    Serial.println(" -> quay dau");
    chay(TOC_DO_QUAY, -TOC_DO_QUAY);
    delay(700);
  } else if (phai >= trai) {
    Serial.println(" -> re phai");
    chay(TOC_DO_QUAY, -TOC_DO_QUAY);
    delay(350);
  } else {
    Serial.println(" -> re trai");
    chay(-TOC_DO_QUAY, TOC_DO_QUAY);
    delay(350);
  }
  chay(0, 0);
  delay(200);
}
  • doMotLan() phát xung TRIG, đo độ rộng xung ECHO bằng pulseIn() với thời gian chờ tối đa 30 ms (khoảng 5 m). Không có tiếng vọng thì trả về 400 cm, nghĩa là phía trước thoáng.
  • doKhoangCach() đo 3 lần và lấy số ở giữa (trung vị), để một lần đo sai không làm xe dừng vô cớ.
  • Servo được điều khiển bằng PWM 50 Hz, 16 bit như bài servo SG90 trong cùng loạt, không cần thư viện.
  • Các hằng số KHOANG_DUNG, TOC_DO, thời gian rẽ 350 ms phụ thuộc xe của bạn. Chỉnh để xe rẽ được khoảng 90°.

Serial Monitor (115200 baud) in quyết định mỗi lần gặp vật, ví dụ: Truoc: 22 cm -> dung, quet: phai 80 cm, trai 35 cm -> re phai. Khi thử, có thể nhấc bánh xe lên khỏi bàn và đưa tay trước cảm biến để xem.

6. Gợi ý dùng trong giờ học STEM

Mục tiêu: học sinh hiểu cảm biến, ra quyết định và chấp hành trong một robot tự hành; đo đạc và thử nghiệm để cải tiến thuật toán.

  1. Kiểm chứng tốc độ âm thanh. Đặt một tấm bìa cách cảm biến 10, 20, 30, 50 cm (đo bằng thước). In ra thời gian ECHO, tính tốc độ âm thanh từ số liệu và so với 343 m/s.
  2. Vật nào khó thấy? Thử với tấm gỗ, cốc thủy tinh, vải, bọt biển, tấm bìa đặt nghiêng 45°. Ghi lại khoảng cách đo được và giải thích.
  3. Mê cung bằng hộp giấy. Xếp hộp thành mê cung, đo thời gian xe thoát ra. Các nhóm đổi KHOANG_DUNG, tốc độ, thời gian rẽ để cải thiện kết quả.
  4. Cải tiến thuật toán. Thay vì dừng hẳn, cho xe giảm tốc dần khi lại gần vật, với tốc độ tỉ lệ với khoảng cách. Đây là bước đầu của điều khiển tỉ lệ (P).

7. Xử lý lỗi thường gặp

Lỗi thường gặp
Hiện tượngNguyên nhân thường gặpCách xử lý
Luôn đo được 400 cmKhông có xung ECHO: sai dây, thiếu 5 V, nối nhầm TRIG và ECHOKiểm tra VCC 5 V, GND chung; kiểm tra cầu phân áp
Khoảng cách nhảy lung tungSóng dội từ sàn hoặc thân xe; nguồn nhiễu khi động cơ chạyGắn cảm biến cao hơn mặt sàn và nhô ra khỏi thân xe; thêm tụ 100 µF ở nguồn 5 V của cảm biến
Xe rẽ quá ít hoặc quá nhiềuThời gian rẽ không hợp với xeChỉnh 350 ms và 700 ms; pin yếu thì xe rẽ ít hơn
Xe đâm vào vật trước khi dừngChạy nhanh, khoảng dừng ngắnTăng KHOANG_DUNG, giảm TOC_DO
Cảm biến không nhìn đúng hướngGóc servo khác cách lắpChỉnh GOC_THANG, GOC_PHAI, GOC_TRAI

8. Xem thêm

Cùng loạt: xe robot ESP32 điều khiển qua Wi-Fi, ESP32 điều khiển servo SG90, ESP32 điều khiển động cơ DC với L298N.


Tin tức liên quan

Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient
Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient

Hướng dẫn dùng MQTT với ESP32 trên Arduino IDE: broker, topic, publish, subscribe, cài Mosquitto và PubSubClient, gửi dữ liệu và điều khiển module relay qua mạng nội bộ.

Hướng dẫn lưu dữ liệu vào bộ nhớ flash của ESP32 bằng thư viện Preferences
Hướng dẫn lưu dữ liệu vào bộ nhớ flash của ESP32 bằng thư viện Preferences

Hướng dẫn dùng thư viện Preferences trên ESP32 với Arduino IDE để lưu số đếm, trạng thái relay và thông số cài đặt vào bộ nhớ flash, không mất khi tắt nguồn.

Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32
Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32

So sánh mạch hạ áp LM2596 và MP1584, cách chỉnh điện áp ra bằng đồng hồ vạn năng trước khi nối tải, cấp nguồn 5 V cho ESP32 DevKit V1 từ nguồn 12 V và xử lý lỗi thường gặp.

Giữ tốc độ động cơ DC ổn định bằng PID trên ESP32: encoder, chỉnh Kp Ki Kd, Serial Plotter
Giữ tốc độ động cơ DC ổn định bằng PID trên ESP32: encoder, chỉnh Kp Ki Kd, Serial Plotter

Hướng dẫn điều khiển vòng kín tốc độ động cơ DC bằng PID trên ESP32: đo tốc độ bằng encoder và PCNT, chu kỳ 20 ms, chống tích phân bão hòa, chỉnh Kp Ki Kd và thí nghiệm STEM.

Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32
Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32

Hướng dẫn dùng Bluetooth Classic của ESP32 trên Arduino IDE: ghép đôi với điện thoại Android, gửi nhận chữ bằng BluetoothSerial và điều khiển module relay bằng lệnh ON, OFF.

ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED
ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED

Hướng dẫn dựng mô hình ESP-NOW nhiều node: tối đa bốn ESP32 gửi số đo về một trạm thu, trạm thu hiện điện áp, RSSI và tình trạng từng node trên màn hình OLED 0.96 inch.

Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266
Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266

Hướng dẫn nối OLED SSD1306 với ESP8266, quét địa chỉ I2C, cài thư viện và hiển thị thời gian bằng mã Arduino mẫu.

Hướng dẫn giao tiếp I2C trên ESP32: quét địa chỉ, đổi chân SDA/SCL và nối nhiều thiết bị
Hướng dẫn giao tiếp I2C trên ESP32: quét địa chỉ, đổi chân SDA/SCL và nối nhiều thiết bị

Hướng dẫn dùng bus I2C trên ESP32 với Arduino IDE: quét địa chỉ thiết bị, đổi chân SDA và SCL, nối màn hình OLED và ADS1115 chung một bus, xử lý khi hai thiết bị trùng địa chỉ.


Bình luận
Đã thêm vào giỏ hàng