共计 4161 个字符,预计需要花费 11 分钟才能阅读完成。
背景痛点
在机器人或自动化项目中,电机控制常常面临速度不稳定、无法精确控制位置的问题。传统的开环控制方式,即只发送控制信号而不反馈实际状态,存在几个明显的缺陷:

- 速度受负载变化影响大,尤其在启动和停止时波动明显
- 无法感知电机实际转速和位置,导致控制精度低
- 缺乏保护机制,容易因堵转或过载损坏电机
这些问题在需要精确控制的场合(如 3D 打印机、机械臂)尤为突出。采用 PWM(脉宽调制)结合编码器的闭环控制方案,可以有效解决这些问题。
技术对比
电机驱动芯片选择
常见的电机驱动芯片主要有 L298N 和 TB6612 两种:
- L298N
- 优点:价格便宜,驱动能力强(最大 46V/2A)
- 缺点:PWM 响应较慢,发热量大需加散热片
-
适用场景:对成本敏感、不要求高频 PWM 的中小功率应用
-
TB6612
- 优点:PWM 响应快(100kHz),效率高,内置保护电路
- 缺点:驱动能力较弱(15V/1.2A)
- 适用场景:需要高频 PWM 或电池供电的移动机器人
编码器类型选择
根据反馈信号类型,编码器可分为增量式和绝对式:
- 增量式编码器
- 输出 A / B 两相脉冲信号,通过计数脉冲数确定位置变化量
- 优点:结构简单,价格低,适合连续旋转应用
-
缺点:断电后位置信息丢失,需重新校准
-
绝对式编码器
- 每个位置对应唯一编码值,直接输出绝对位置
- 优点:断电不丢失位置,抗干扰强
- 缺点:价格高,分辨率有限
对于大多数 Arduino 项目,增量式编码器是性价比更高的选择。
核心实现
Arduino PWM 配置
Arduino UNO 的 Timer1 可配置为相位校正 PWM 模式,提供更高的 PWM 分辨率(10 位)。配置步骤如下:
- 设置波形生成模式为相位校正 PWM(WGM13:10=0001)
- 选择非反相输出模式(COM1A1:0=10)
- 设置预分频系数为 1(CS12:10=001)
- 设置 ICR1 为 TOP 值(决定 PWM 频率)
典型配置代码:
// 设置 PWM 频率为 31.25kHz(16MHz/(2*1*255))TCCR1A = _BV(COM1A1) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(CS10);
ICR1 = 255; // 8 位分辨率
编码器脉冲计数
使用外部中断捕获编码器脉冲是最可靠的方法。对于正交编码器(A/ B 两相),可采用双边沿触发模式:
- 配置中断引脚为 INPUT_PULLUP 模式
- 设置中断触发方式为 CHANGE(双沿触发)
- 在 ISR 中根据 A / B 相状态判断方向
典型实现:
volatile long encoderPos = 0;
void setup() {pinMode(2, INPUT_PULLUP); // Encoder A 相
pinMode(3, INPUT_PULLUP); // Encoder B 相
attachInterrupt(digitalPinToInterrupt(2), encoderISR, CHANGE);
}
void encoderISR() {if(digitalRead(2) == digitalRead(3)) {encoderPos++; // 正向旋转} else {encoderPos--; // 反向旋转}
}
PID 调速算法
PID 控制器是闭环控制的核心,需特别注意积分饱和问题:
- 比例项 (P):与当前误差成正比,决定响应速度
- 积分项 (I):累积历史误差,消除稳态误差
- 微分项 (D):预测误差变化趋势,抑制超调
带抗积分饱和的 PID 实现:
class PID {
public:
PID(float kp, float ki, float kd, float max_out)
: Kp(kp), Ki(ki), Kd(kd), maxOutput(max_out) {}
float compute(float setpoint, float input) {
float error = setpoint - input;
// 比例项
float Pout = Kp * error;
// 积分项(带抗饱和)integral += error;
if(integral > maxOutput/Ki) integral = maxOutput/Ki;
if(integral < -maxOutput/Ki) integral = -maxOutput/Ki;
float Iout = Ki * integral;
// 微分项
float derivative = error - prevError;
float Dout = Kd * derivative;
prevError = error;
// 综合输出并限幅
float output = Pout + Iout + Dout;
if(output > maxOutput) output = maxOutput;
if(output < -maxOutput) output = -maxOutput;
return output;
}
private:
float Kp, Ki, Kd;
float integral = 0;
float prevError = 0;
float maxOutput;
};
完整代码示例
下面是一个整合 PWM 控制、编码器反馈和 PID 调速的完整示例:
#include <Arduino.h>
// 硬件引脚定义
#define MOTOR_PWM 9
#define MOTOR_IN1 8
#define MOTOR_IN2 7
#define ENCODER_A 2
#define ENCODER_B 3
// 全局变量
volatile long encoderPos = 0;
float targetSpeed = 0; // 目标转速(脉冲 / 秒)// PID 控制器
PID motorPID(1.0, 0.5, 0.1, 255); // KP, KI, KD, 最大输出
void setup() {
// 初始化串口
Serial.begin(115200);
// 配置电机驱动引脚
pinMode(MOTOR_PWM, OUTPUT);
pinMode(MOTOR_IN1, OUTPUT);
pinMode(MOTOR_IN2, OUTPUT);
// 配置编码器中断
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderISR, CHANGE);
// 配置 PWM(Timer1,8 位相位校正 PWM,31.25kHz)TCCR1A = _BV(COM1A1) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(CS10);
ICR1 = 255;
// 初始停止电机
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, LOW);
OCR1A = 0;
}
void loop() {
static unsigned long lastTime = 0;
static long lastPos = 0;
// 每 100ms 计算一次速度
if(millis() - lastTime >= 100) {
// 计算实际速度(脉冲 / 秒)float actualSpeed = (encoderPos - lastPos) * 10.0;
lastPos = encoderPos;
lastTime = millis();
// PID 计算输出
float output = motorPID.compute(targetSpeed, actualSpeed);
// 设置电机方向和 PWM
if(output >= 0) {digitalWrite(MOTOR_IN1, HIGH);
digitalWrite(MOTOR_IN2, LOW);
} else {digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, HIGH);
output = -output;
}
OCR1A = constrain(output, 0, 255);
// 串口输出调试信息
Serial.print("Target:");
Serial.print(targetSpeed);
Serial.print("Actual:");
Serial.print(actualSpeed);
Serial.print("PWM:");
Serial.println(output);
}
// 通过串口设置目标速度
if(Serial.available()) {targetSpeed = Serial.parseFloat();
Serial.print("Set target speed to:");
Serial.println(targetSpeed);
}
}
// 编码器中断服务程序
void encoderISR() {if(digitalRead(ENCODER_A) == digitalRead(ENCODER_B)) {encoderPos++;} else {encoderPos--;}
}
避坑指南
PWM 频率选择
- 低频 PWM(1-5kHz):适用于普通直流电机,但可能产生可闻噪声
- 中频 PWM(8-20kHz):平衡效率和噪声,适合大多数应用
- 高频 PWM(>20kHz):无噪声,但可能导致电机驱动效率下降
编码器信号处理
- 硬件消抖 :在编码器信号线上添加 0.1uF 电容
- 软件消抖 :在 ISR 中添加延时判断
void encoderISR() { static unsigned long lastTime = 0; if(micros() - lastTime < 100) return; // 100us 消抖 lastTime = micros(); // 正常处理逻辑... }
中断冲突预防
- 避免使用与 Timer1 冲突的库(如 Servo 库)
- 对于多编码器系统,优先使用 PCINT 引脚而非外部中断
- 保持 ISR 尽可能简短,避免长时间中断影响系统响应
性能验证
使用示波器检查以下指标:
- PWM 波形质量 :观察占空比变化是否平滑,边缘是否干净
- 速度跟踪误差 :记录阶跃响应下的超调量和稳定时间
- 抗干扰能力 :手动扰动电机轴,观察系统恢复速度
典型性能指标参考:
– 空载速度误差:<±5%
– 阶跃响应稳定时间:<0.5s
– 负载突变恢复时间:<0.3s
扩展思考
如何实现多电机同步控制?可以考虑以下方向:
- 主从控制 :指定一个主电机,其他电机跟随其位置
- 电子齿轮 :通过比例关系同步多个电机运动
- 分布式控制 :每个电机使用独立 PID,通过 CAN 总线协调
希望这篇实战指南能帮助你构建可靠的电机控制系统。在实际项目中,建议先用小功率电机测试,确认所有保护机制正常工作后再上大功率设备。
正文完
