0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

ESP32 伺服馬達 SG90 控制完整教學:PWM 脈衝控制、LEDC 多軸同步、平滑運動與梯形加減速

2026 年 6 月 23 日 13點熱度 0人點贊 0條評論

ESP32 伺服馬達 SG90 控制:PWM 脈衝控制與多軸管理

ESP32 伺服馬達控制是機器人與 IoT 互動裝置中最常見的應用之一。SG90 是一款便宜的微型伺服馬達,廣泛應用於機器手臂、雲台、智慧門鎖、RC 模型等場景。ESP32 伺服馬達控制涉及 PWM 脈衝寬度調變、LEDC 硬體計時器、多通道同步以及電源管理等多個面向。

本文將深入探討:

  • 伺服馬達 PWM 控制原理(50 Hz, 1~2 ms 脈衝)
  • ESP32 LEDC 硬體 PWM 產生器
  • 多軸伺服管理(8+ 通道獨立控制)
  • 平滑運動與梯形加減速
  • Servo 函式庫 vs 底層 LEDC API
  • 電源設計與實務注意事項

SG90 規格

參數 數值
工作電壓 4.8 ~ 6.0 V
堵轉扭力 (4.8V) 1.8 kg.cm
轉速 (4.8V) 0.12 sec/60deg (無負載)
控制訊號 PWM 50 Hz (20 ms 週期)
脈衝寬度範圍 0.5 ~ 2.5 ms
旋轉角度 0 ~ 180 deg(部分可 360deg 連續旋轉)
死區 約 2~5 us(對應 ~0.2deg)
重量 9 g
尺寸 23 x 12.2 x 29 mm

PWM 控制原理

伺服馬達的控制非常簡單:每隔 20 ms(50 Hz)發送一個脈衝,脈衝寬度決定旋轉角度:

SG90 PWM 控制時序

角度與脈衝寬度的對應關係:

角度 脈衝寬度 Duty Cycle (50 Hz)
0° 1.0 ms 5.0%
45° 1.25 ms 6.25%
90° 1.5 ms 7.5%
135° 1.75 ms 8.75%
180° 2.0 ms 10.0%

SG90 角度 vs PWM 脈衝寬度

關鍵公式:

脈衝寬度 (ms) = 1.5 + (角度 - 90) / 90
角度範圍: 0 ~ 180 度
脈衝寬度範圍: 0.5 ~ 2.5 ms (SG90 標準)
解析度: 約 11 us/deg (20ms / 180deg / 20bit timer)

硬體接線

ESP32 多軸伺服馬達接線圖

⚠ 重要:伺服馬達不可從 ESP32 的 3.3V 取電!

SG90 在堵轉時電流可達 750 mA,遠超過 ESP32 3.3V 穩壓器的輸出能力。必須使用獨立 5V 2A+ 電源,並將 ESP32 GND 與電源 GND 連接(共地)。

ESP32 LEDC PWM 硬體

ESP32 內建 LEDC (LED Control) PWM 控制器,專門用於產生精確的 PWM 訊號:

  • 8 通道(可擴展到 16 通道)
  • 20-bit 解析度(可設定 1~20 bit)
  • 硬體自動輸出(不佔用 CPU)
  • 可設定頻率(1 Hz ~ 40 MHz)
  • 3 組時基(每組共用頻率,各通道獨立佔空比)

Arduino 程式設計

使用 Servo 函式庫(最簡易)

// ESP32 伺服馬達 — Servo 函式庫
#include <Servo.h>

Servo servo0;
Servo servo1;

void setup() {
    Serial.begin(115200);

    servo0.attach(13);  // GPIO 13
    servo1.attach(12);  // GPIO 12

    // 歸零
    servo0.write(0);
    servo1.write(0);
    delay(1000);
}

void loop() {
    // 掃描 0~180 度
    for (int pos = 0; pos <= 180; pos += 1) {
        servo0.write(pos);
        delay(15);  // 約需 15 ms/deg (SG90 0.12s/60deg)
    }
    for (int pos = 180; pos >= 0; pos -= 1) {
        servo0.write(pos);
        delay(15);
    }

    // 直接設定脈衝寬度 (us)
    servo1.writeMicroseconds(1000);  // 0deg
    delay(1000);
    servo1.writeMicroseconds(1500);  // 90deg
    delay(1000);
    servo1.writeMicroseconds(2000);  // 180deg
    delay(1000);
}

LEDC 底層 API(精確控制)

// ESP32 伺服馬達 — LEDC 底層 PWM 控制
#define SERVO_FREQ     50      // 50 Hz
#define SERVO_RESOLUTION 16    // 16-bit 解析度
#define SERVO_CH0      0       // LEDC 通道 0
#define SERVO_PIN0     13

// 角度轉脈衝寬度 (us)
int angleToPulse(int angle) {
    // angle: 0~180, 回傳 500~2500 us
    return map(angle, 0, 180, 500, 2500);
}

// 脈衝寬度轉 duty cycle
uint32_t pulseToDuty(int pulse_us) {
    // 50 Hz = 20,000 us 週期
    // 16-bit: 0~65535
    return (uint64_t)pulse_us * 65535 / 20000;
}

void setServoAngle(int channel, int angle) {
    int pulse = angleToPulse(angle);
    uint32_t duty = pulseToDuty(pulse);
    ledcWrite(channel, duty);
}

void setup() {
    // 設定 LEDC 通道
    ledcSetup(SERVO_CH0, SERVO_FREQ, SERVO_RESOLUTION);
    ledcAttachPin(SERVO_PIN0, SERVO_CH0);

    // 設定到 90deg
    setServoAngle(SERVO_CH0, 90);
}

void loop() {
    for (int a = 0; a <= 180; a += 5) {
        setServoAngle(SERVO_CH0, a);
        delay(50);
    }
}

多軸伺服同步控制

多軸伺服 LEDC 硬體輸出時序

// ESP32 四軸伺服同步控制 (LEDC 多通道)
#define SERVO_FREQ 50
#define SERVO_RES  16

// 伺服設定結構
struct ServoConfig {
    uint8_t pin;
    uint8_t channel;
    int currentAngle;
    int targetAngle;
};

ServoConfig servos[4] = {
    {13, 0, 90, 0},   // Servo 0: GPIO 13, CH0
    {12, 1, 90, 0},   // Servo 1: GPIO 12, CH1
    {14, 2, 90, 0},   // Servo 2: GPIO 14, CH2
    {27, 3, 90, 0},   // Servo 3: GPIO 27, CH3
};

void setupAllServos() {
    for (int i = 0; i < 4; i++) {
        ledcSetup(servos[i].channel, SERVO_FREQ, SERVO_RES);
        ledcAttachPin(servos[i].pin, servos[i].channel);
        setAngle(i, servos[i].currentAngle);
    }
}

void setAngle(int idx, int angle) {
    servos[idx].targetAngle = constrain(angle, 0, 180);
    // 轉換脈衝寬度: 0.5~2.5ms
    int pulse = map(angle, 0, 180, 500, 2500);
    uint32_t duty = (uint64_t)pulse * 65535 / 20000;
    ledcWrite(servos[idx].channel, duty);
}

void moveAllTo(int angles[4]) {
    for (int i = 0; i < 4; i++) {
        setAngle(i, angles[i]);
    }
}

void setup() {
    setupAllServos();
}

void loop() {
    int pose1[4] = {0, 45, 90, 135};
    int pose2[4] = {180, 135, 90, 45};

    moveAllTo(pose1);
    delay(2000);
    moveAllTo(pose2);
    delay(2000);
}

平滑運動(梯形加減速)

伺服馬達平滑運動插值

// 階梯式插值 — 避免伺服馬達抖動
void smoothMove(int idx, int target, int stepDelay = 20) {
    int current = servos[idx].currentAngle;
    int step = (target > current) ? 1 : -1;

    // 逐步移動,每次移動 1 度
    for (int pos = current; pos != target; pos += step) {
        setAngle(idx, pos);
        servos[idx].currentAngle = pos;
        delay(stepDelay);  // 每度延遲 (ms)
    }
    // 確保到達目標
    setAngle(idx, target);
    servos[idx].currentAngle = target;
}

// 自動計算最佳速度
void smoothMoveWithSpeed(int idx, int target, float speedDegPerSec) {
    int current = servos[idx].currentAngle;
    int dist = abs(target - current);
    if (dist == 0) return;

    int delayMs = max(5, (int)(1000.0 / speedDegPerSec));
    int step = (target > current) ? 1 : -1;

    // 梯形加減速
    int accelDist = dist / 3;  // 加速距離
    int decelDist = dist / 3;  // 減速距離

    for (int pos = current; pos != target; pos += step) {
        setAngle(idx, pos);
        servos[idx].currentAngle = pos;

        int remaining = abs(target - pos);
        int traveled = abs(pos - current);

        if (traveled < accelDist) {
            // 加速: 逐漸減少延遲
            delay(delayMs * (1 + (float)(accelDist - traveled) / accelDist));
        } else if (remaining < decelDist) {
            // 減速: 逐漸增加延遲
            delay(delayMs * (1 + (float)(decelDist - remaining) / decelDist));
        } else {
            // 等速
            delay(delayMs);
        }
    }
}

PCA9685 I2C 擴展(32+ 通道)

// ESP32 + PCA9685 16 通道伺服控制
// PCA9685 使用 I2C 通訊,可控制 16 個伺服馬達
// 多個 PCA9685 可串接 (最多 62 個 = 992 伺服)
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

// PCA9685 PWM 頻率設定
#define SERVO_FREQ 50  // 50 Hz

// 脈衝寬度極限 (us)
#define SERVO_MIN  500  // 0deg
#define SERVO_MAX  2500 // 180deg

void setup() {
    pwm.begin();
    pwm.setOscillatorFrequency(27000000);  // PCA9685 內部震盪器
    pwm.setPWMFreq(SERVO_FREQ);            // 50 Hz
    delay(10);
}

void setPCA9685Servo(int ch, int angle) {
    int pulse = map(angle, 0, 180, SERVO_MIN, SERVO_MAX);
    // PCA9685 的 pulse 單位: 1/4096 週期
    // 50 Hz 週期 = 20 ms = 20000 us
    // 4096 / 20000 * pulse_us
    pwm.setPWM(ch, 0, pulse * 4096 / 20000);
}

void loop() {
    // 控制通道 0~15
    for (int ch = 0; ch < 16; ch++) {
        setPCA9685Servo(ch, 90);  // 全部置中
    }
    delay(2000);

    for (int ch = 0; ch < 16; ch++) {
        setPCA9685Servo(ch, 0);
    }
    delay(2000);
}

ESP-IDF LEDC API

// ESP-IDF 伺服馬達控制
#include "driver/ledc.h"

#define SERVO_PIN      13
#define SERVO_CHANNEL  LEDC_CHANNEL_0
#define SERVO_TIMER    LEDC_TIMER_0
#define SERVO_FREQ     50
#define SERVO_RES      LEDC_TIMER_16_BIT

void setup() {
    ledc_timer_config_t timer_conf = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .duty_resolution = SERVO_RES,
        .timer_num = SERVO_TIMER,
        .freq_hz = SERVO_FREQ,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer_conf);

    ledc_channel_config_t ch_conf = {
        .gpio_num = SERVO_PIN,
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .channel = SERVO_CHANNEL,
        .timer_sel = SERVO_TIMER,
        .duty = 0,
        .hpoint = 0
    };
    ledc_channel_config(&ch_conf);
}

void setServo(int angle) {
    uint32_t pulse = map(angle, 0, 180, 500, 2500);  // us
    uint32_t duty = pulse * 65535 / 20000;
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SERVO_CHANNEL, duty);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SERVO_CHANNEL);
}

電源設計注意事項

伺服數量 建議電源 備註
1~2 顆 5V 1A 手機充電器 可直接從 USB 取電
3~6 顆 5V 3A 變壓器 使用獨立穩壓模組
7~12 顆 5V 5A 以上 需使用 5V 10A 電源 + 大電容
12+ 顆或 PCA9685 5V 10A+ / 6V 電池 需使用 LM2596 降壓穩壓

實務注意事項

  • 獨立電源:伺服馬達不可從 ESP32 3.3V 取電,需獨立 5V 2A+ 電源並共地
  • 大電容:伺服馬達啟動瞬間電流很大,電源端並聯 470~1000 uF 電解電容
  • 訊號電平:ESP32 3.3V PWM 訊號對 SG90 完全相容(SG90 邏輯閾值約 1.5V)
  • 死區:SG90 有約 2~5 us 的死區,角度控制精度約 0.2~0.5deg
  • 抖動:PWM 頻率必須精確 50 Hz,頻率飄移會造成伺服抖動
  • 保護:堵轉時持續供電會燒毀馬達或驅動晶片,建議加入電流檢測
  • 金屬齒輪:SG90 為塑膠齒輪,頻繁使用建議更換 MG90S(金屬齒輪)
  • 波特率限制:Servo.write() 內部使用 50 Hz 更新,每 20 ms 才能改變一次角度

常見問題

問題 原因 解法
伺服不動作 電源不足或 PWM 頻率錯誤 確認 5V 電源,確認 50 Hz
伺服抖動 PWM 不穩定或電源雜訊 加大電容,使用 LEDC 硬體 PWM
角度不準 脈衝寬度偏差 使用 writeMicroseconds() 微調
ESP32 重啟 伺服啟動電流拉低 3.3V 獨立電源 + 共地 + 大電容
Servo.h 編譯錯誤 ESP32 Servo 函式庫衝突 使用 ESP32Saver/ServoESP32 或 LEDC API
多軸不同步 多個 Servo 物件延遲 使用 LEDC 硬體通道同步

總結

ESP32 控制伺服馬達有從最簡的 Servo 函式庫到底層 LEDC API 的多種選擇。對於大多數應用,Servo 函式庫已經足夠;但需要精確同步或多軸控制時,LEDC 硬體 API 提供更好的效能和靈活性。

選型參考:

  • 1~2 顆簡易控制:Servo 函式庫(寫入角度,自動管理 PWM)
  • 4~8 軸專案:LEDC 底層 API(精確同步,硬體輸出)
  • 8~16 軸以上:PCA9685 I2C 擴展(只需 2 條 I2C 線)
  • 工業級 / 大扭力:改用 MG996R 或 Dynamixel 智慧伺服(數位通訊)
標籤: 教學
最後更新:2026 年 6 月 23 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang