步进电机模块——让 Arduino 精确控制旋转
前面我们学了舵机,它能控制角度,但精度有限,而且不能连续旋转很多圈。今天我们要学的步进电机,解决了这两个问题——它能精确控制旋转角度,还能连续旋转,是 3D 打印机、CNC 雕刻机、机器人关节的核心驱动元件。
学完这篇,你会完成三个项目:
- 基础驱动:用 ULN2003 驱动板驱动 28BYJ-48 步进电机
- 精确控制:精确控制步进电机旋转指定角度
- 综合项目:简易数控转盘:用步进电机 + 按键,实现精确角度旋转的转盘
一、步进电机是什么?
步进电机是一种将电脉冲信号转换为角位移的执行元件。简单说:每收到一个脉冲信号,电机就转动一个固定的角度(步距角)。
步进电机 vs 普通直流电机 vs 舵机
| 对比项 | 直流电机 | 舵机 | 步进电机 |
|---|---|---|---|
| 控制方式 | 控制速度 | 控制角度(0~180°) | 控制角度(可多圈) |
| 精度 | 低 | 中等 | 非常高 |
| 连续旋转 | 可以 | 不可以 | 可以 |
| 位置保持 | 断电后自由转动 | 断电后可能偏移 | 断电后有保持力矩 |
| 典型应用 | 风扇、小车 | 机械臂关节 | 3D打印机、CNC |
28BYJ-48 步进电机
我们使用的是最常见的 28BYJ-48 四相五线制步进电机,配合 ULN2003 驱动板使用。
| 参数 | 数值 |
|---|---|
| 工作电压 | 5V |
| 步距角 | 5.625° / 64(经过内部减速齿轮) |
| 一圈所需步数 | 4096 步(半步模式) |
| 驱动方式 | 四相五线 |
| 配套驱动板 | ULN2003 |
28BYJ-48 内部有 1:64 的减速齿轮组,所以实际步距角 = 5.625° ÷ 64 ≈ 0.088°,一圈需要约 4096 步(半步驱动模式)。
二、ULN2003 驱动板
步进电机不能直接由 Arduino 的 GPIO 引脚驱动,因为电流不够。ULN2003 驱动板内部集成了达林顿晶体管阵列,可以放大电流来驱动步进电机。
ULN2003 引脚说明
| 引脚 | 说明 |
|---|---|
| IN1~IN4 | 信号输入,接 Arduino 的 4 个数字引脚 |
| OUT1~OUT4 | 输出,接步进电机的 5 根线 |
| VCC | 电源正极(5V) |
| GND | 电源负极,需与 Arduino 共地 |
接线方式
| ULN2003 引脚 | 接到 |
|---|---|
| IN1 | D8 |
| IN2 | D9 |
| IN3 | D10 |
| IN4 | D11 |
| VCC | 5V(外部电源更佳) |
| GND | GND |
步进电机的 5 根线直接插入 ULN2003 的 OUT 排母座,防呆设计,不会插反。
️ 步进电机工作电流较大,建议 ULN2003 的 VCC 使用外部 5V 电源供电,不要直接从 Arduino 的 5V 引脚取电,否则可能导致 Arduino 供电不足。
三、实验一:基础旋转
先让步进电机转起来,体验最基本的驱动方式。
// 步进电机引脚定义
#define IN1 8
#define IN2 9
#define IN3 10
#define IN4 11
// 四相八拍驱动序列(半步模式,更平滑)
int stepSequence[8][4] = {
{HIGH, LOW, LOW, LOW},
{HIGH, HIGH, LOW, LOW},
{LOW, HIGH, LOW, LOW},
{LOW, HIGH, HIGH, LOW},
{LOW, LOW, HIGH, LOW},
{LOW, LOW, HIGH, HIGH},
{LOW, LOW, LOW, HIGH},
{HIGH, LOW, LOW, HIGH}
};
void setup() {
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
Serial.println("步进电机基础测试");
}
void loop() {
// 正转一圈(4096步)
for (int step = 0; step < 4096; step++) {
int phase = step % 8;
digitalWrite(IN1, stepSequence[phase][0]);
digitalWrite(IN2, stepSequence[phase][1]);
digitalWrite(IN3, stepSequence[phase][2]);
digitalWrite(IN4, stepSequence[phase][3]);
delay(2); // 控制转速,数值越大越慢
}
// 停 1 秒
allLow();
delay(1000);
// 反转一圈
for (int step = 4095; step >= 0; step--) {
int phase = step % 8;
digitalWrite(IN1, stepSequence[phase][0]);
digitalWrite(IN2, stepSequence[phase][1]);
digitalWrite(IN3, stepSequence[phase][2]);
digitalWrite(IN4, stepSequence[phase][3]);
delay(2);
}
allLow();
delay(2000);
}
void allLow() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
代码解读
stepSequence[8][4]:定义了四相八拍的驱动序列,这是半步驱动模式,比四拍模式更平滑step % 8:取模运算,让序列循环使用- 正转:按顺序 0→7 执行序列
- 反转:按倒序 7→0 执行序列
delay(2):每步之间的间隔,控制转速
效果
- 电机正转一圈 → 停 1 秒 → 反转一圈 → 停 2 秒,循环
四、实验二:精确角度控制
封装一个函数,通过指定角度来控制步进电机旋转到指定位置。
#define IN1 8
#define IN2 9
#define IN3 10
#define IN4 11
// 一圈 4096 步 = 360°
// 每度需要的步数 = 4096 / 360 ≈ 11.38
#define STEPS_PER_DEGREE 11.38
int stepSequence[8][4] = {
{HIGH, LOW, LOW, LOW},
{HIGH, HIGH, LOW, LOW},
{LOW, HIGH, LOW, LOW},
{LOW, HIGH, HIGH, LOW},
{LOW, LOW, HIGH, LOW},
{LOW, LOW, HIGH, HIGH},
{LOW, LOW, LOW, HIGH},
{HIGH, LOW, LOW, HIGH}
};
// 当前角度位置(用于追踪)
float currentAngle = 0;
void setup() {
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
Serial.begin(9600);
Serial.println("步进电机精确角度控制");
}
void loop() {
// 转到 90°
rotateTo(90);
delay(1000);
// 转到 180°
rotateTo(180);
delay(1000);
// 转到 270°
rotateTo(270);
delay(1000);
// 回到 0°
rotateTo(0);
delay(2000);
}
// 旋转到指定角度
void rotateTo(float targetAngle) {
float diff = targetAngle - currentAngle;
int steps = (int)(abs(diff) * STEPS_PER_DEGREE);
bool clockwise = (diff > 0);
for (int i = 0; i < steps; i++) {
if (clockwise) {
step(1); // 正转一步
} else {
step(-1); // 反转一步
}
delay(2);
}
currentAngle = targetAngle;
Serial.print("旋转到: ");
Serial.print(targetAngle);
Serial.println("°");
}
// 执行一步
void step(int direction) {
static int currentStep = 0;
currentStep += direction;
if (currentStep > 7) currentStep = 0;
if (currentStep < 0) currentStep = 7;
digitalWrite(IN1, stepSequence[currentStep][0]);
digitalWrite(IN2, stepSequence[currentStep][1]);
digitalWrite(IN3, stepSequence[currentStep][2]);
digitalWrite(IN4, stepSequence[currentStep][3]);
}
代码解读
STEPS_PER_DEGREE:每度需要约 11.38 步currentAngle:记录当前角度位置,实现相对运动rotateTo():计算目标角度与当前角度的差值,决定正转还是反转step():使用static变量记住当前步序位置,支持正转和反转
效果
- 电机依次转到 90° → 180° → 270° → 0°,每次停顿 1 秒
五、综合项目:简易数控转盘
用步进电机 + 2 个按键,实现一个可以精确控制旋转角度的转盘。
- 按键 A:顺时针旋转 45°
- 按键 B:逆时针旋转 45°
接线
| 元件 | 引脚 | 接到 |
|---|---|---|
| 按键 A | 一端 | D2 |
| 按键 A | 另一端 | GND |
| 按键 B | 一端 | D3 |
| 按键 B | 另一端 | GND |
| ULN2003 | IN1~IN4 | D8~D11 |
代码
#define IN1 8
#define IN2 9
#define IN3 10
#define IN4 11
#define KEY_CW 2 // 顺时针按键
#define KEY_CCW 3 // 逆时针按键
#define STEPS_PER_DEGREE 11.38
#define ROTATE_ANGLE 45 // 每次旋转 45°
int stepSequence[8][4] = {
{HIGH, LOW, LOW, LOW},
{HIGH, HIGH, LOW, LOW},
{LOW, HIGH, LOW, LOW},
{LOW, HIGH, HIGH, LOW},
{LOW, LOW, HIGH, LOW},
{LOW, LOW, HIGH, HIGH},
{LOW, LOW, LOW, HIGH},
{HIGH, LOW, LOW, HIGH}
};
int currentStep = 0;
float currentAngle = 0;
void setup() {
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(KEY_CW, INPUT_PULLUP);
pinMode(KEY_CCW, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("简易数控转盘");
Serial.println("按键A: 顺时针45° 按键B: 逆时针45°");
}
void loop() {
// 顺时针旋转
if (digitalRead(KEY_CW) == LOW) {
delay(50); // 消抖
if (digitalRead(KEY_CW) == LOW) {
rotateBy(ROTATE_ANGLE);
waitKeyRelease(KEY_CW);
}
}
// 逆时针旋转
if (digitalRead(KEY_CCW) == LOW) {
delay(50);
if (digitalRead(KEY_CCW) == LOW) {
rotateBy(-ROTATE_ANGLE);
waitKeyRelease(KEY_CCW);
}
}
}
// 旋转指定角度(正数顺时针,负数逆时针)
void rotateBy(float angle) {
int steps = (int)(abs(angle) * STEPS_PER_DEGREE);
int direction = (angle > 0) ? 1 : -1;
for (int i = 0; i < steps; i++) {
doStep(direction);
delay(2);
}
currentAngle += angle;
// 保持角度在 0~360 范围内
if (currentAngle >= 360) currentAngle -= 360;
if (currentAngle < 0) currentAngle += 360;
Serial.print("当前角度: ");
Serial.print(currentAngle);
Serial.println("°");
}
void doStep(int direction) {
currentStep += direction;
if (currentStep > 7) currentStep = 0;
if (currentStep < 0) currentStep = 7;
digitalWrite(IN1, stepSequence[currentStep][0]);
digitalWrite(IN2, stepSequence[currentStep][1]);
digitalWrite(IN3, stepSequence[currentStep][2]);
digitalWrite(IN4, stepSequence[currentStep][3]);
}
// 等待按键释放,防止连续触发
void waitKeyRelease(int pin) {
while (digitalRead(pin) == LOW) {
delay(10);
}
}
代码解读
rotateBy():按指定角度旋转,正数顺时针,负数逆时针waitKeyRelease():等待按键释放,防止按住不放时连续触发- 按键消抖:先检测到低电平后,延时 50ms 再确认,避免抖动误触发
- 角度归一化:用
if判断让角度始终在 0~360° 范围内
效果
- 按一次按键 A → 转盘顺时针旋转 45°
- 按一次按键 B → 转盘逆时针旋转 45°
- 串口显示当前角度位置
六、进阶:使用 AccelStepper 库
手动编写驱动序列虽然能学到原理,但实际项目中推荐使用 AccelStepper 库,它支持加减速控制,运行更平滑。
安装库
Arduino IDE → 工具 → 管理库 → 搜索 "AccelStepper" → 安装
示例代码
#include <AccelStepper.h>
// 定义步进电机:半4拍模式,引脚 8,10,9,11(注意顺序)
AccelStepper stepper(AccelStepper::HALF4WIRE, 8, 10, 9, 11);
void setup() {
// 设置最大速度和加速度
stepper.setMaxSpeed(1000); // 最大速度(步/秒)
stepper.setAcceleration(500); // 加速度(步/秒²)
Serial.begin(9600);
Serial.println("AccelStepper 库测试");
}
void loop() {
// 正转 4096 步(一圈)
stepper.moveTo(4096);
while (stepper.currentPosition() != 4096) {
stepper.run();
}
delay(1000);
// 反转回 0
stepper.moveTo(0);
while (stepper.currentPosition() != 0) {
stepper.run();
}
delay(2000);
}
AccelStepper 的优势
- 加减速控制:启动时逐渐加速,停止前逐渐减速,避免丢步
- 非阻塞运行:
stepper.run()可以在loop()中持续调用,不阻塞其他代码 - 位置追踪:
currentPosition()实时获取当前位置 - 速度控制:
setMaxSpeed()和setSpeed()精确控制转速
七、步进电机使用注意事项
供电问题
28BYJ-48 工作电流约 200~300mA,建议 ULN2003 使用外部 5V 电源供电,不要从 Arduino 的 5V 引脚取电。
丢步问题
如果转速过快或负载过大,步进电机会"丢步"(实际转动角度小于预期)。解决方法:
- 降低转速(增大
delay值) - 减小负载
- 使用 AccelStepper 库的加减速功能
发热问题
步进电机在保持位置时也会消耗电流并发热,如果不需要保持力矩,可以在停止时调用 allLow() 断开所有相的电流。
精度说明
28BYJ-48 由于内部减速齿轮的间隙,实际精度约为 ±1°,不适合需要极高精度的场合。如果需要更高精度,可以选择不带减速齿轮的步进电机(如 NEMA17)。
八、本篇小结
回顾一下今天学到的核心知识:
- 步进电机原理:每个脉冲转动一个固定角度,精度远高于直流电机
- 28BYJ-48:四相五线制,配合 ULN2003 驱动板,一圈 4096 步
- 驱动序列:四相八拍半步驱动,比四拍模式更平滑
- 精确角度控制:通过计算步数实现指定角度旋转
- 综合项目:按键控制的数控转盘,每次精确旋转 45°
- AccelStepper 库:支持加减速、非阻塞运行,推荐使用
步进电机让 Arduino 具备了精密运动控制的能力。下一篇,我们学习LCD1602 液晶显示模块——让 Arduino 能够显示文字和自定义字符。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)