共计 2260 个字符,预计需要花费 6 分钟才能阅读完成。
为什么需要闭环控制?
刚开始玩电机控制时,我用 PWM 直接驱动电机,发现两个头疼问题:
- 电机转速会随着负载变化明显波动(空载时转得快,加上负载就变慢)
- 想精确控制转 10 圈,结果可能转了 9 圈半或 11 圈
这是因为开环控制就像蒙眼走路——我们发出指令后,根本不知道电机实际执行情况。而带编码器的电机给了我们 ” 眼睛 ”,通过反馈信号实现闭环控制。
硬件选型要点
编码器类型选择
常见的有两种编码器:
- 增量式编码器(需要参考零点)
- 绝对式编码器(可直接读取位置)
对于大多数 Arduino 项目,增量式编码器性价比更高。我用的就是常见的 600PPR(每转 600 脉冲)正交编码器。
电机驱动方案对比
| 驱动方式 | 优点 | 缺点 |
|---|---|---|
| L298N | 便宜易得 | 效率低发热大 |
| TB6612 | 体积小效率高 | 电流较小 |
| VNH5019 | 大电流带保护 | 价格较高 |
我选择 TB6612,因为它的 MOSFET 驱动效率高达 95%,而且有正反转控制引脚。
核心实现三步走
1. 编码器信号采集
正交编码器有两路输出(A 相和 B 相),关键是要用硬件中断来捕获跳变沿:
// 引脚定义
#define ENCODER_A 2 // 必须接支持中断的引脚
#define ENCODER_B 3
volatile long encoderPos = 0; // volatile 用于多线程访问
void setup() {pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
// 设置中断服务函数
attachInterrupt(digitalPinToInterrupt(ENCODER_A), doEncoderA, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_B), doEncoderB, CHANGE);
}
// 中断服务函数
void doEncoderA() {if(digitalRead(ENCODER_A) == digitalRead(ENCODER_B)) {encoderPos++;} else {encoderPos--;}
}
void doEncoderB() {if(digitalRead(ENCODER_A) == digitalRead(ENCODER_B)) {encoderPos--;} else {encoderPos++;}
}
2. PID 控制器实现
Arduino 有现成的 PID 库,但理解原理很重要:
// 自定义简化版 PID 实现
class SimplePID {
public:
float Kp, Ki, Kd;
float setpoint;
float lastError, integral;
SimplePID(float kp, float ki, float kd) {
Kp = kp; Ki = ki; Kd = kd;
reset();}
void reset() {lastError = integral = 0;}
float compute(float input) {
float error = setpoint - input;
integral += error;
float derivative = error - lastError;
lastError = error;
return Kp*error + Ki*integral + Kd*derivative;
}
};
3. 电机驱动整合
将编码器计数转换为转速(RPM):
// 计算转速函数
float getRPM() {
static long lastPos = 0;
static unsigned long lastTime = 0;
long deltaPos = encoderPos - lastPos;
unsigned long deltaTime = millis() - lastTime;
lastPos = encoderPos;
lastTime = millis();
// 600PPR 编码器,4 倍频后每转 2400 计数
return (deltaPos * 60000.0) / (2400 * deltaTime);
}
参数整定实战技巧
调 PID 参数就像炒菜放调料,我的经验是:
- 先把 Ki 和 Kd 设为 0,逐渐增加 Kp 直到系统开始震荡
- 取震荡时 Kp 值的 50% 作为初始值
- 慢慢增加 Ki 消除静差(但别加太多!)
- 最后加 Kd 抑制超调
用串口绘图工具观察响应曲线最直观:

常见坑位预警
信号干扰问题
遇到编码器计数不准时:
- 给编码器电源加 104 电容
- 使用双绞线连接
- 在中断服务函数中做消抖处理
过流保护
电机堵转时电流会飙升,建议:
- 驱动芯片加散热片
- 程序里监测电流(可用 ACS712 模块)
- 设置软件限流
// 简单电流保护示例
if(millis() - lastCheck > 100) {float current = analogRead(CURRENT_PIN) * 0.185; // ACS712 5A 量程
if(current > 2.0) { // 超过 2A 停止
motorStop();}
lastCheck = millis();}
进阶玩法
等基础调速稳定后,可以尝试:
- 位置控制模式(先加速后减速的 S 曲线规划)
- 多电机同步(主从模式或 CAN 总线通信)
- 结合 ROS 做移动机器人
我的小车项目就是用两个编码器电机 +Arduino 实现的差速转向,里程计精度能达到±2cm:
最后的小建议
调试时一定要有耐心,PID 参数可能需要反复调整几十次。建议先用小电压测试,避免电机高速失控。遇到问题可以先检查:
- 编码器接线是否正确(A/ B 相不要接反)
- 中断优先级是否冲突
- 采样周期是否合适(一般 10-100ms)
完整工程代码已上传 GitHub(搜索 ”Arduino-Encoder-PID”),里面有更详细的注释和测试案例。欢迎交流改进建议!
正文完
