會揮手的桌上機器人:ESP32 伺服馬達入門
伺服馬達是機械手臂、機器人關節的基礎。這篇不裝任何函式庫,直接用 ESP32 的 PWM 產生伺服訊號,讓你真正搞懂「角度是怎麼控制的」。
✓ 本文程式已在 2026/09/29 以 Arduino ESP32 開發板套件 3.3.12 編譯通過(開啟全部編譯警告,0 個警告),開發板選 ESP32 Dev Module。
1. 需要的零件
| 零件 | 數量 | 說明 |
|---|---|---|
| ESP32 DevKit V1 | 1 | |
| SG90 伺服馬達 | 1 | 藍色小伺服馬達,附塑膠搖臂 |
| HC-SR04 超音波感測器 | 1 | 選配:有人靠近才揮手 |
| 電阻 1 kΩ、2 kΩ | 各 1 | ECHO 分壓用 |
| 電解電容 100–470 µF | 1 | 建議:穩定伺服馬達電源 |
| 紙盒、冰棒棍或 3D 列印外殼 | 當機器人的身體和手 |
2. 伺服馬達的原理
SG90 每 20 毫秒(50 Hz)接收一個脈衝,脈衝的「寬度」決定角度:約 0.5 毫秒是 0°,約 2.4 毫秒是 180°,中間等比例。
ESP32 的 LEDC 可以把頻率設成 50 Hz、解析度設成 14 bit(0–16383)。一個週期 20000 微秒對應 16383,所以:
工作週期 = 脈寬(µs)÷ 20000 × 16383
程式裡的 servoWrite() 就是做這個換算。每顆 SG90 的極限略有不同,轉到底時如果發出「嘰嘰」聲,把 PULSE_MIN_US 調高、PULSE_MAX_US 調低一點。
3. 接線
| 從 | 接到 | 備註 |
|---|---|---|
| SG90 橘線(訊號) | ESP32 GPIO 23 | |
| SG90 紅線(電源) | ESP32 VIN(USB 供電時約 5V) | 不要接 3.3V,扭力會不足 |
| SG90 棕線(地) | ESP32 GND | |
| 電容 | 正極接 VIN,負極接 GND | 注意極性,長腳是正極 |
| HC-SR04 VCC / GND | VIN / GND | |
| HC-SR04 TRIG | GPIO 18 | |
| HC-SR04 ECHO | 1 kΩ → GPIO 19;GPIO 19 經 2 kΩ 接 GND | 分壓成約 3.3V |
一顆 SG90 用 USB 供電可以,兩顆以上請外接 5V 電源。伺服馬達轉動瞬間電流很大,會讓 ESP32 重開機。外接電源時,電源的 GND 要和 ESP32 的 GND 接在一起。
4. 程式
沒有接超音波感測器的話,把 USE_SENSOR 改成 false,機器人會每 5 秒自己揮一次手。
/*
* TinyBot Forge — 會揮手的桌上機器人(伺服馬達入門)
* https://tinybotforge.com
*
* 不用額外函式庫:直接用 ESP32 的 LEDC 產生 50 Hz 伺服訊號。
* 硬體:ESP32 DevKit V1、SG90 伺服馬達、HC-SR04(選配,有人靠近才揮手)
* 環境:Arduino IDE 2.x + ESP32 開發板套件 3.x
*/
const int PIN_SERVO = 23;
const int PIN_TRIG = 18;
const int PIN_ECHO = 19; // HC-SR04 ECHO 需經 1kΩ/2kΩ 分壓
const bool USE_SENSOR = true; // 沒接超音波就改成 false,會每 5 秒揮一次
// SG90:週期 20 ms(50 Hz),脈寬約 0.5 ms = 0°、2.4 ms = 180°
const int SERVO_FREQ = 50;
const int SERVO_BITS = 14; // 14 bit → 0–16383
const int PULSE_MIN_US = 500;
const int PULSE_MAX_US = 2400;
const float WAVE_TRIGGER_CM = 40.0;
void servoWrite(int angle) {
angle = constrain(angle, 0, 180);
int us = map(angle, 0, 180, PULSE_MIN_US, PULSE_MAX_US);
// 脈寬換成工作週期:duty = us / 20000 × 16383
uint32_t duty = (uint32_t)us * ((1UL << SERVO_BITS) - 1) / 20000UL;
ledcWrite(PIN_SERVO, duty);
}
// 平滑移動:一度一度轉,動作比較自然,也比較不會拉低電壓
void servoSweep(int from, int to, int stepDelayMs) {
int step = (to > from) ? 1 : -1;
for (int a = from; a != to; a += step) {
servoWrite(a);
delay(stepDelayMs);
}
servoWrite(to);
}
float readDistanceCm() {
digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
unsigned long us = pulseIn(PIN_ECHO, HIGH, 25000UL);
return us == 0 ? 999.0 : us / 58.0;
}
void wave() {
servoSweep(90, 150, 4); // 舉手
for (int i = 0; i < 3; i++) { // 揮三下
servoSweep(150, 110, 3);
servoSweep(110, 150, 3);
}
servoSweep(150, 90, 6); // 放下
}
void setup() {
Serial.begin(115200);
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
ledcAttach(PIN_SERVO, SERVO_FREQ, SERVO_BITS);
servoWrite(90);
delay(500);
}
void loop() {
if (USE_SENSOR) {
float cm = readDistanceCm();
Serial.printf("距離 %.1f cm\n", cm);
if (cm < WAVE_TRIGGER_CM) {
wave();
delay(1500); // 揮完休息一下,避免一直重複
}
delay(100);
} else {
wave();
delay(5000);
}
}
5. 組裝建議
- 先校正角度:上傳程式後伺服馬達會轉到 90°。這時再把搖臂裝上去,讓手臂「自然下垂」的位置剛好對應 90°。
- 把伺服馬達用熱熔膠固定在紙盒側面,冰棒棍黏在搖臂上當手。
- 超音波感測器放在盒子正面,兩個圓筒剛好可以當眼睛。
6. 常見問題
伺服馬達抖動不停
通常是電源不穩。加上電容,或改用外接 5V。也確認 GND 有接好。
網路上的教學都用 ESP32Servo 函式庫,差在哪?
函式庫幫你做了同樣的換算,用起來更方便。但自己寫一次,你就知道伺服訊號長什麼樣子,之後換任何開發板都會。
轉動角度不到 180°
很多便宜的 SG90 實際只能轉 160–170°。調整 PULSE_MIN_US 與 PULSE_MAX_US,找出你那顆的極限。