ESP32 避障小車:用超音波做一台會自己閃開障礙物的車
這是 TinyBot Forge 的第一個專案。做完之後,你會懂三件事:怎麼用超音波量距離、怎麼用 PWM 控制馬達速度,以及怎麼讓機器人根據感測器「自己做決定」。
✓ 本文程式已在 2026/09/29 以 Arduino ESP32 開發板套件 3.3.12 編譯通過(開啟全部編譯警告,0 個警告),開發板選 ESP32 Dev Module。
1. 需要的零件
| 零件 | 數量 | 說明 |
|---|---|---|
| ESP32 DevKit V1(30 或 38 腳) | 1 | 本文腳位以 38 腳版標示,30 腳版腳位名稱相同 |
| HC-SR04 超音波感測器 | 1 | 量測範圍約 2–400 cm |
| L298N 馬達驅動板 | 1 | 最常見的紅色雙 H 橋模組 |
| 兩輪車架套件(含 2 顆 TT 減速馬達、萬向輪) | 1 | 俗稱「智能小車底盤」 |
| 18650 電池 ×2 與雙槽電池盒 | 1 組 | 約 7.4 V;也可用 6 顆 3 號電池盒 |
| 電阻 1 kΩ、2 kΩ | 各 1 | 給 ECHO 腳分壓用,不可省略 |
| 杜邦線(公對母、母對母)、小麵包板 | 若干 |
零件購買連結整理中,上線後會放在這裡。
2. 運作原理
HC-SR04 發出一段 40 kHz 的超音波,碰到障礙物反彈回來。ECHO 腳維持高電位的時間,就是聲音來回所花的時間。聲速約 343 m/s,來回 1 公分大約是 58 微秒,所以:
距離(cm)= 高電位時間(µs)÷ 58
小車每隔約 70 毫秒量一次距離。前方超過 20 公分就直走;小於 20 公分就停車、倒退、右轉,右邊也擋住的話改往左轉。
3. 接線
| 從 | 接到 | 備註 |
|---|---|---|
| HC-SR04 VCC | L298N 5V 輸出 | HC-SR04 需要 5V |
| HC-SR04 TRIG | ESP32 GPIO 18 | 3.3V 觸發訊號 HC-SR04 讀得到 |
| HC-SR04 ECHO | 1 kΩ → GPIO 19;GPIO 19 再經 2 kΩ 接 GND | 把 5V 分壓成約 3.3V |
| HC-SR04 GND | GND | |
| L298N ENA | ESP32 GPIO 25 | 先拔掉 ENA 上的跳帽 |
| L298N IN1 / IN2 | ESP32 GPIO 26 / 27 | |
| L298N ENB | ESP32 GPIO 33 | 先拔掉 ENB 上的跳帽 |
| L298N IN3 / IN4 | ESP32 GPIO 32 / 13 | |
| L298N OUT1 / OUT2 | 左馬達兩條線 | 轉向相反就對調 |
| L298N OUT3 / OUT4 | 右馬達兩條線 | 轉向相反就對調 |
| 電池 + | L298N 12V 輸入 | 保留 L298N 上「5V 使能」跳帽 |
| 電池 - | L298N GND | |
| L298N 5V 輸出 | ESP32 VIN | 用電池時不要同時插 USB 供電跑馬達 |
| L298N GND | ESP32 GND | 一定要共地,否則訊號不穩 |
最常燒板子的錯誤:HC-SR04 的 ECHO 腳直接接 ESP32。ECHO 輸出 5V,ESP32 腳位只能承受 3.3V,短時間可能還能動,但長期會損壞腳位。請一定加分壓電阻。
4. 安裝 ESP32 開發環境
- 安裝 Arduino IDE 2。
- 檔案 → 偏好設定 → 「額外的開發板管理員網址」,貼上:
https://espressif.github.io/arduino-esp32/package_esp32_index.json - 工具 → 開發板 → 開發板管理員,搜尋
esp32,安裝 Espressif Systems 的 esp32,版本 3.0 以上。 - 工具 → 開發板,選 ESP32 Dev Module,再選對 USB 序列埠。
電腦抓不到序列埠的話,通常是缺 USB 驅動:板子上的晶片是 CP2102 就裝 Silicon Labs 驅動,是 CH340 就裝 WCH 驅動。
5. 程式
/*
* TinyBot Forge — ESP32 避障小車
* https://tinybotforge.com
*
* 硬體:ESP32 DevKit V1、HC-SR04 超音波感測器、L298N 馬達驅動板、兩顆 TT 直流馬達
* 環境:Arduino IDE 2.x + ESP32 開發板套件 3.x(使用 ledcAttach / ledcWrite 新版 API)
*
* 注意:HC-SR04 的 ECHO 腳輸出 5V,ESP32 腳位只能承受 3.3V,
* ECHO 必須經過分壓電阻(1kΩ + 2kΩ)再接到 GPIO 19。
*/
// ---------- 腳位 ----------
const int PIN_TRIG = 18; // HC-SR04 TRIG
const int PIN_ECHO = 19; // HC-SR04 ECHO(經分壓)
const int PIN_ENA = 25; // 左馬達速度(PWM)
const int PIN_IN1 = 26; // 左馬達方向
const int PIN_IN2 = 27;
const int PIN_ENB = 33; // 右馬達速度(PWM)
const int PIN_IN3 = 32; // 右馬達方向
const int PIN_IN4 = 13;
// ---------- 參數(可依你的車調整) ----------
const int PWM_FREQ = 1000; // PWM 頻率 1 kHz
const int PWM_BITS = 8; // 解析度 8 bit → 速度 0–255
const int SPEED_CRUISE = 170; // 直走速度
const int SPEED_TURN = 160; // 轉彎速度
const float STOP_CM = 20.0; // 小於這個距離就閃避
const unsigned long BACK_MS = 350; // 倒車時間
const unsigned long TURN_MS = 450; // 轉彎時間
// ---------- 馬達 ----------
// speed:-255(全速後退)到 255(全速前進)
void setMotor(int pinEn, int pinA, int pinB, int speed) {
speed = constrain(speed, -255, 255);
digitalWrite(pinA, speed > 0 ? HIGH : LOW);
digitalWrite(pinB, speed < 0 ? HIGH : LOW);
ledcWrite(pinEn, abs(speed));
}
void drive(int left, int right) {
setMotor(PIN_ENA, PIN_IN1, PIN_IN2, left);
setMotor(PIN_ENB, PIN_IN3, PIN_IN4, right);
}
void stopCar() { drive(0, 0); }
// ---------- 超音波測距 ----------
// 回傳公分;超過量測範圍回傳 999
float readOnceCm() {
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
unsigned long us = pulseIn(PIN_ECHO, HIGH, 25000UL); // 25 ms ≈ 4 公尺
if (us == 0) return 999.0;
return us / 58.0; // 聲速換算:來回 1 cm ≈ 58 µs
}
// 量三次取中位數,濾掉偶發的錯誤讀數
float readDistanceCm() {
float a = readOnceCm(); delay(10);
float b = readOnceCm(); delay(10);
float c = readOnceCm();
if (a > b) { float t = a; a = b; b = t; }
if (b > c) { float t = b; b = c; c = t; }
if (a > b) { float t = a; a = b; b = t; }
return b;
}
// ---------- 閃避 ----------
void avoid() {
stopCar();
delay(100);
drive(-SPEED_CRUISE, -SPEED_CRUISE); // 倒車
delay(BACK_MS);
drive(SPEED_TURN, -SPEED_TURN); // 先試右轉
delay(TURN_MS);
stopCar();
delay(80);
if (readDistanceCm() > STOP_CM * 1.5) return;
drive(-SPEED_TURN, SPEED_TURN); // 右邊也擋住,改往左轉兩倍時間
delay(TURN_MS * 2);
stopCar();
delay(80);
}
void setup() {
Serial.begin(115200);
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_IN1, OUTPUT);
pinMode(PIN_IN2, OUTPUT);
pinMode(PIN_IN3, OUTPUT);
pinMode(PIN_IN4, OUTPUT);
ledcAttach(PIN_ENA, PWM_FREQ, PWM_BITS);
ledcAttach(PIN_ENB, PWM_FREQ, PWM_BITS);
stopCar();
Serial.println("TinyBot 避障小車:2 秒後出發");
delay(2000);
}
void loop() {
float cm = readDistanceCm();
Serial.printf("距離 %.1f cm\n", cm);
if (cm < STOP_CM) {
avoid();
} else {
drive(SPEED_CRUISE, SPEED_CRUISE);
}
delay(40);
}
程式重點
ledcAttach(腳位, 頻率, 解析度):ESP32 套件 3.x 的新寫法。網路上很多舊教學用ledcSetup和ledcAttachPin,在 3.x 會編譯失敗,看到就知道是舊文章。- 三次取中位數:超音波偶爾會讀到錯誤數值,例如打到斜面時沒收到回音。取中位數能濾掉單次異常,比取平均穩定。
pulseIn設 25 ms 逾時:沒收到回音時不會卡住整個程式。
6. 測試與調整
- 先架空測試:把車底墊高讓輪子懸空,上傳程式,打開序列埠監控視窗(115200 baud),用手在感測器前移動,確認距離數字合理。
- 確認轉向:輪子應該往前轉。哪一邊轉反了,就把那顆馬達的兩條線對調。
- 放到地上跑。
| 想調整 | 改這個參數 |
|---|---|
| 車子太快、撞上才閃 | 調低 SPEED_CRUISE,或調高 STOP_CM |
| 轉不夠角度 | 調高 TURN_MS |
| 馬達嗡嗡叫但不轉 | 調高速度數值;TT 馬達低於約 120 常常推不動 |
7. 常見問題
距離一直顯示 999
沒收到回音。檢查 ECHO 分壓接線、HC-SR04 是否有 5V 供電、GND 是否共地。
一接馬達 ESP32 就重開機
馬達啟動瞬間電流很大,把電壓拉低了。改用充飽的 18650,不要只靠 USB 供電;ESP32 的 VIN 與 GND 之間可以並一顆 100 µF 電容。
上傳時出現 Failed to connect
按住板子上的 BOOT 鍵,等畫面出現 Connecting… 再放開。
車子走不直
兩顆 TT 馬達本來就有轉速差異。把跑比較快那一邊的速度調低 5 到 15。