
步進馬達與 A4988 驅動器
步進馬達的特點是「叫你走一步就走一步」:每個脈衝對應一個固定的步進角(通常 1.8°),而且不需要編碼器回授就能做到開迴路定位。這個特性讓它在 CNC、3D 列印、機器手臂這些精密定位場景裡幾乎無可取代。
但要讓它動,中間必須有一顆驅動器 —— 因為馬達吃的是大電流,而你的 MCU 只能吐邏輯訊號。A4988 就是最經典的那一顆:Allegro 推出的 DMOS 微步進驅動器,內建自動電流衰減、過熱關斷和交叉電流保護,只需要 STEP/DIR 兩條訊號線就能控制步進馬達。

先記住這張圖的權責劃分:ESP32 只負責「什麼時候走、往哪走」,馬達的電流完全由 A4988 從 VMOT 那條獨立電源取得。搞混這件事,就是燒板子的開始。
STEP/DIR 控制時序
A4988 的介面是業界最簡潔的設計,四組訊號就把事情講完:
- STEP:每個上升緣觸發一次步進(或微步進)
- DIR:邏輯電平決定旋轉方向(HIGH = 順時針,LOW = 逆時針)
- ENABLE:低電位啟用驅動器輸出
- MS1/MS2/MS3:微步進模式選擇

關鍵時序要求:
- STEP 脈衝寬度:≥ 1μs(A4988 規格)
- DIR 建立時間:需在 STEP 上升緣前 ≥ 200ns 準備好
- 最高 STEP 頻率:約 200 kHz(對應 1.8° 馬達 → 1000 RPM)
- 最小 STEP 低電位時間:≥ 1μs
其中「DIR 建立時間」是最容易被忽略的一條。因為在程式裡 DIR 只是隨手寫的一個 digitalWrite(),但只要改變方向前沒有留時間,馬達就會在轉向的瞬間先走錯一兩步 —— 症狀是位置慢慢累積偏移,很難聯想到是時序問題。
微步進(Microstepping)
A4988 支援多種微步進模式,透過 MS1/MS2/MS3 三個腳位設定:
| MS1 | MS2 | MS3 | 微步進 | 每步角度 (1.8°) |
|---|---|---|---|---|
| LOW | LOW | LOW | 全步進 (1/1) | 1.800° |
| HIGH | LOW | LOW | 半歩進 (1/2) | 0.900° |
| LOW | HIGH | LOW | 1/4 步進 | 0.450° |
| HIGH | HIGH | LOW | 1/8 步進 | 0.225° |
| HIGH | HIGH | HIGH | 1/16 步進 | 0.113° |

微步進的原理是在 A/B 兩相繞組施加正弦/餘弦電流,把一個全步進細分成多個微小步進(上圖是示意)。好處是運轉更平滑、共振更小、解析度更高;代價是扭力會略降,而且需要的脈衝數變多。
梯形加減速
步進馬達在高速啟動時可能失步(丟失脈衝),而且失步是靜默的 —— 馬達不會回報,只會默默少走幾步。所以加減速曲線不是為了「優雅」,而是為了不失位:

加減速的原理很直觀:
- 加速階段:STEP 脈衝週期逐漸縮短(頻率逐漸增加),讓馬達平穩提速
- 等速階段:固定 STEP 頻率,維持目標轉速
- 減速階段:STEP 脈衝週期逐漸延長,讓馬達平穩停止
ESP32 + A4988 硬體接線
| A4988 | ESP32 | 說明 |
|---|---|---|
| STEP | GPIO 26 | 步進脈衝輸入 |
| DIR | GPIO 27 | 方向控制 |
| ENABLE | GPIO 14 | 啟用 (LOW = 啟用) |
| MS1 | GPIO 16 | 微步進選擇 |
| MS2 | GPIO 17 | 微步進選擇 |
| MS3 | GPIO 18 | 微步進選擇 |
| VMOT | 8-35V 電源 | 馬達電源 (獨立供電!) |
| GND | GND (共地) | ESP32 與電源共地 |
| 1A/1B | 馬達線圈 A | 連接步進馬達 |
| 2A/2B | 馬達線圈 B | 連接步進馬達 |
Arduino 程式碼:基本控制
先從最單純的版本開始 —— 手動發脈衝、手動控制方向:
// ESP32 步進馬達 — A4988 基本控制
#define STEP_PIN 26
#define DIR_PIN 27
#define ENA_PIN 14
void setup() {
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(ENA_PIN, OUTPUT);
digitalWrite(STEP_PIN, LOW);
digitalWrite(ENA_PIN, LOW); // 啟用驅動器
}
void stepMotor(bool dir, int steps, int delay_us) {
digitalWrite(DIR_PIN, dir ? HIGH : LOW);
delayMicroseconds(1); // DIR 建立時間
for (int i = 0; i < steps; i++) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(delay_us);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(delay_us);
}
}
void loop() {
// 順時針 200 步 (一圈) - 10 RPM
stepMotor(true, 200, 1500);
delay(1000);
// 逆時針 200 步 (一圈)
stepMotor(false, 200, 1500);
delay(1000);
}
使用 AccelStepper 函式庫(推薦)
實務上不太會自己刻脈衝,AccelStepper 把加減速、定位與多軸都處理好了:
// ESP32 步進馬達 — AccelStepper 函式庫(內建加減速)
#include <AccelStepper.h>
#define STEP_PIN 26
#define DIR_PIN 27
#define ENA_PIN 14
// 建立步進馬達物件 (driver type = 1: STEP/DIR)
AccelStepper stepper(1, STEP_PIN, DIR_PIN);
void setup() {
pinMode(ENA_PIN, OUTPUT);
digitalWrite(ENA_PIN, LOW); // 啟用
stepper.setMaxSpeed(1000); // 最高速度 (steps/s)
stepper.setAcceleration(500); // 加速度 (steps/s/s)
stepper.setSpeed(200); // 等速速度
}
void loop() {
// 移動 400 步 (2 圈),內建梯形加減速
stepper.moveTo(400);
while (stepper.distanceToGo() != 0) {
stepper.run(); // 必須在 loop 中頻繁呼叫
}
delay(1000);
// 回到原點
stepper.moveTo(0);
while (stepper.distanceToGo() != 0) {
stepper.run();
}
delay(1000);
// 持續旋轉
static unsigned long lastMove = 0;
if (millis() - lastMove > 2000) {
stepper.move(200); // 相對移動 200 步
lastMove = millis();
}
stepper.run();
}
自製梯形加減速
但如果你的專案不想引入函式庫,或需要非常特定的速度曲線,自己寫其實也不難:
// ESP32 步進馬達 — 自製梯形加減速
#define STEP_PIN 26
#define DIR_PIN 27
// 加減速參數
int stepsPerRev = 200; // 200 steps/rev (1.8°)
long targetPos = 0;
long currentPos = 0;
// 速度曲線參數
float maxSpeed = 800.0; // steps/s
float accel = 400.0; // steps/s/s
float currentSpeed = 0.0; // 目前速度
// 時間
unsigned long lastStepTime = 0;
unsigned long stepInterval = 0;
void setup() {
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
digitalWrite(STEP_PIN, LOW);
}
void moveTo(long target) {
targetPos = target;
currentSpeed = 0; // 從靜止開始
}
bool runSpeed() {
// 計算到目標的距離
long dist = targetPos - currentPos;
if (dist == 0) return false;
// 決定方向
bool dir = dist > 0;
digitalWrite(DIR_PIN, dir ? HIGH : LOW);
// 計算減速距離(對稱加減速)
float decelDist = (currentSpeed * currentSpeed) / (2.0 * accel);
if (abs(dist) < decelDist) {
// 需要減速
currentSpeed -= accel * 0.0001; // 簡化:每次呼叫減少速度
if (currentSpeed < 10) currentSpeed = 10;
} else if (currentSpeed < maxSpeed) {
// 加速階段
currentSpeed += accel * 0.0001;
if (currentSpeed > maxSpeed) currentSpeed = maxSpeed;
}
// 計算 STEP 間隔
stepInterval = 1000000.0 / currentSpeed; // μs
// 產生 STEP 脈衝
if (micros() - lastStepTime >= stepInterval) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(2);
digitalWrite(STEP_PIN, LOW);
lastStepTime = micros();
currentPos += dir ? 1 : -1;
}
return true;
}
void loop() {
// 往復運動
static int phase = 0;
if (currentPos == targetPos) {
if (phase == 0) {
moveTo(800); // 前進 4 圈
phase = 1;
} else {
moveTo(0); // 回到原點
phase = 0;
}
delay(500);
}
runSpeed();
}
微步進模式設定
微步進可以開機時決定,也可以執行中動態切換 —— 後者在「快移用大步、定位用小步」的場合很好用:
// ESP32 步進馬達 — 微步進模式動態切換
#define MS1_PIN 16
#define MS2_PIN 17
#define MS3_PIN 18
void setMicrostep(int mode) {
// mode: 0=Full, 1=Half, 2=1/4, 3=1/8, 4=1/16
switch (mode) {
case 0: // Full step
digitalWrite(MS1, LOW);
digitalWrite(MS2, LOW);
digitalWrite(MS3, LOW);
break;
case 1: // Half step
digitalWrite(MS1, HIGH);
digitalWrite(MS2, LOW);
digitalWrite(MS3, LOW);
break;
case 2: // 1/4 step
digitalWrite(MS1, LOW);
digitalWrite(MS2, HIGH);
digitalWrite(MS3, LOW);
break;
case 3: // 1/8 step
digitalWrite(MS1, HIGH);
digitalWrite(MS2, HIGH);
digitalWrite(MS3, LOW);
break;
case 4: // 1/16 step
digitalWrite(MS1, HIGH);
digitalWrite(MS2, HIGH);
digitalWrite(MS3, HIGH);
break;
}
}
void setup() {
pinMode(MS1_PIN, OUTPUT);
pinMode(MS2_PIN, OUTPUT);
pinMode(MS3_PIN, OUTPUT);
setMicrostep(4); // 預設 1/16 微步進(最平滑)
}
// 動態切換策略:
// - 快速移動時:全步進或半步進(扭力大、速度快)
// - 精確定位時:1/8 或 1/16 微步進(解析度高、震動小)
// - 切換時機:必須在馬達停止時切換,不可在運轉中改變
扭力-轉速特性

從扭力曲線可以看出:
- 低轉速區 (< 200 RPM):扭力接近額定值,適合需要大扭力的應用
- 中速共振區 (150~350 RPM):扭力波動大,可能產生抖動和噪音,建議避開或使用微步進
- 高速區 (> 500 RPM):扭力隨轉速增加急遽下降(反電動勢效應),最終達到 pull-out 轉速
- 微步進影響:1/16 微步進扭力約為全步進的 85%,但運轉更平滑
那句「1/16 微步進只剩 85% 扭力」值得記住:如果你已經在扭力邊緣,把微步進切細並不會讓定位變準,只會讓它更容易失步。順序應該是先確保扭力夠,再談解析度。
進階:多軸同步控制
要讓 X 與 Y 同時走出一條直線,關鍵不是「分別叫兩顆馬達走」,而是「讓它們共用同一個時間基準」:

// ESP32 雙軸步進馬達同步控制
#include <AccelStepper.h>
#define STEP_X 26
#define DIR_X 27
#define STEP_Y 33
#define DIR_Y 32
AccelStepper stepperX(1, STEP_X, DIR_X);
AccelStepper stepperY(1, STEP_Y, DIR_Y);
// 多軸管理
AccelStepper* steppers[] = { &stepperX, &stepperY };
const int NUM_AXIS = 2;
void setup() {
stepperX.setMaxSpeed(800);
stepperX.setAcceleration(400);
stepperY.setMaxSpeed(800);
stepperY.setAcceleration(400);
// 直線插補:同時移動到目標位置
stepperX.moveTo(800);
stepperY.moveTo(400);
}
void loop() {
// 同時驅動所有軸(同步運動)
bool running = false;
for (int i = 0; i < NUM_AXIS; i++) {
if (steppers[i]->distanceToGo() != 0) {
steppers[i]->run();
running = true;
}
}
if (!running) {
delay(1000);
// 反向移動
stepperX.moveTo(0);
stepperY.moveTo(0);
}
}
實務注意事項
- 獨立電源:A4988 VMOT 需要 8-35V 獨立電源,不可從 ESP32 的 3.3V/5V 供電。NEMA 17 典型 2A/phase,需搭配 2A 以上電源
- 大電容:A4988 電源端並聯 100μF 電解電容,吸收反電動勢
- 散熱:A4988 在 1A 以上需要散熱片,超過 1.5A 建議加風扇
- 共地:ESP32 GND 和 A4988 GND 必須連接
- 電流設定:A4988 的 VREF 腳位透過可變電阻設定輸出電流:I_TripMax = VREF / (8 * R_Sense)
- 共振:中速時可能發生機械共振,可嘗試改變微步進模式或加裝阻尼器
這份清單裡最常被跳過的是「並聯大電容」。少了它,馬達在急停或換向時產生的反電動勢會回灌到電源,症狀是莫名其妙的當機與重啟 —— 看起來像程式 bug,其實是電源。
總結
ESP32 搭配 A4988 驅動器,是控制步進馬達最簡潔可靠的方案。透過 STEP/DIR 介面和 AccelStepper 函式庫,可以在幾行程式碼內實現精確定位、加減速控制和多軸同步運動。

把決策流程攤成文字,選擇指引就是這幾句:
- 簡單旋轉或低精度定位:全步進 + delay() 控制
- 一般 CNC/3D 列印:1/8 或 1/16 微步進 + AccelStepper 函式庫
- 精密定位或低震動:1/16 或 1/32 微步進 + TMC2209/TMC5160 驅動器(取代 A4988)
- 多軸協同:AccelStepper 多軸管理 + 定時器中斷
我自己的檢查順序是:先量 VMOT 電壓、再調 VREF 電流、最後才開始寫加減速。因為扭力不足造成的失步,寫再漂亮的加減速曲線都救不回來。
文章評論