电机编码器反馈:闭环控制系统实战详解

为什么需要闭环控制?

开环控制(比如直接用 PWM 驱动电机)有个致命问题:你不知道电机实际转了多少。负载变化、电压波动、摩擦力变化都会导致实际转速和位置偏离预期。

闭环控制通过编码器实时反馈电机实际状态,与目标值比较后动态调整输出,实现精准控制。这就是为什么 CNC 机床、机器人关节、云台相机都用闭环系统。

编码器类型详解

增量式编码器(Incremental Encoder)

最常见、最便宜。输出两路相位差 90° 的方波(A 相和 B 相),通过计数脉冲数计算转角,通过相位判断方向。

优点: 结构简单、成本低、响应快
缺点: 断电后丢失位置信息,需要回零操作

绝对式编码器(Absolute Encoder)

每个位置对应唯一的二进制编码,断电后位置不丢失。

优点: 无需回零、位置绝对准确
缺点: 价格高、接线复杂

磁编码器 vs 光编码器

类型 精度 成本 抗污染 典型分辨率
光电式 1000-5000 PPR
磁电式 100-4096 PPR
电容式 1000-10000 PPR

DIY 项目推荐磁编码器(如 AS5600),便宜又耐造。

硬件清单

部件 型号 单价 数量 备注
直流电机 N20 减速电机 ¥25 1 带编码器版本
磁编码器 AS5600 ¥15 1 I2C 接口,12 位分辨率
电机驱动 TB6612FNG ¥8 1 双路 H 桥,支持 PWM
主控板 Arduino Nano ¥15 1 或 ESP32
电源 12V 2A 适配器 ¥20 1 根据电机电压选择
杜邦线 ¥5 1 包 连接线

总计:约¥88

AS5600 磁编码器接线

AS5600 支持 I2C 和模拟电压输出,这里用 I2C 模式:

AS5600 Arduino Nano
VCC 5V
GND GND
SCL A5 (SCL)
SDA A4 (SDA)
OUT 悬空(I2C 模式不用)

电机磁铁安装在电机轴上,AS5600 固定在距离磁铁 1-3mm 的位置。

闭环控制原理

闭环控制的核心是 PID 算法

误差 = 目标位置 - 实际位置
输出 = Kp×误差 + Ki×误差积分 + Kd×误差微分
  • P(比例): 误差越大,输出越大。但纯 P 控制会有稳态误差
  • I(积分): 累积历史误差,消除稳态误差。但过大会导致超调
  • D(微分): 预测误差变化趋势,抑制超调。对噪声敏感

完整代码示例

1. 读取编码器角度

#include 
#include 

AS5600 as5600;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  as5600.begin(Wire);
}

void loop() {
  int angle = as5600.readAngle();  // 0-4095 (12 位)
  float degrees = angle * 360.0 / 4096.0;

  Serial.print("Angle: ");
  Serial.print(angle);
  Serial.print(" (");
  Serial.print(degrees);
  Serial.println("°)");

  delay(100);
}

2. 完整闭环位置控制

#include 
#include 

// 编码器
AS5600 as5600;

// 电机驱动引脚
const int PWM_PIN = 9;
const int IN1_PIN = 7;
const int IN2_PIN = 8;

// PID 参数
float Kp = 2.0;
float Ki = 0.5;
float Kd = 1.0;

// 控制变量
int targetPosition = 0;
int currentPosition = 0;
float integral = 0;
float lastError = 0;
unsigned long lastTime = 0;

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

  pinMode(PWM_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);

  // 初始化电机停止
  analogWrite(PWM_PIN, 0);
}

void loop() {
  // 读取目标位置(串口输入)
  if (Serial.available()) {
    targetPosition = Serial.parseInt();
    integral = 0;  // 重置积分
    lastError = 0;
    Serial.print("Target: ");
    Serial.println(targetPosition);
  }

  // 读取当前位置
  currentPosition = as5600.readAngle();

  // PID 计算
  int error = targetPosition - currentPosition;
  unsigned long currentTime = millis();
  float dt = (currentTime - lastTime) / 1000.0;  // 秒

  if (dt > 0) {
    integral += error * dt;
    float derivative = (error - lastError) / dt;

    float output = Kp * error + Ki * integral + Kd * derivative;

    // 限制输出范围
    output = constrain(output, -255, 255);

    // 驱动电机
    driveMotor(output);

    lastError = error;
    lastTime = currentTime;
  }

  // 调试输出
  Serial.print("Pos: ");
  Serial.print(currentPosition);
  Serial.print(" Err: ");
  Serial.print(error);
  Serial.print(" Out: ");
  Serial.println((int)output);

  delay(10);  // 100Hz 控制频率
}

void driveMotor(float pwm) {
  if (pwm > 0) {
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(PWM_PIN, pwm);
  } else if (pwm < 0) {
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
    analogWrite(PWM_PIN, -pwm);
  } else {
    analogWrite(PWM_PIN, 0);
  }
}

3. PID 参数整定技巧

没有万能参数,必须根据实际系统调整:

  1. 先调 P: 设置 Ki=0, Kd=0,增大 Kp 直到系统开始振荡,然后减小到 70%
  2. 再加 D: 逐渐增大 Kd 抑制超调,直到响应平滑
  3. 最后加 I: 如果有稳态误差,慢慢增大 Ki 消除

经验值参考:

  • 小电机(N20):Kp=1.5-3.0, Ki=0.3-0.8, Kd=0.5-1.5
  • 中电机(37GB):Kp=2.0-4.0, Ki=0.5-1.2, Kd=1.0-2.5
  • 大电机:需要更大 Kp 和 Kd

速度闭环控制

位置控制是目标角度,速度控制是目标转速:

// 速度控制模式
float targetSpeed = 100;  // 度/秒
float currentSpeed = 0;
int lastPosition = 0;

void loop() {
  int currentPosition = as5600.readAngle();

  // 计算速度(考虑编码器溢出)
  int delta = currentPosition - lastPosition;
  if (delta > 2048) delta -= 4096;
  if (delta < -2048) delta += 4096;

  currentSpeed = delta * 100.0;  // 10ms 间隔,转换为度/秒
  lastPosition = currentPosition;

  // 速度 PID
  float error = targetSpeed - currentSpeed;
  // ... 后续 PID 计算同位置控制
}

常见问题排查

问题 1:编码器读数跳动

原因: 磁铁安装距离不当或干扰
解决:

  • 调整磁铁与传感器距离到 1-3mm
  • 检查电源稳定性,加 100uF 电容
  • 使用屏蔽线或双绞线

问题 2:电机振荡不止

原因: PID 参数过大
解决:

  • 减小 Kp 和 Kd
  • 检查控制频率是否稳定(用 micros() 而不是 delay)
  • 增加微分滤波:derivative = 0.7 * lastDerivative + 0.3 * rawDerivative

问题 3:有稳态误差

原因: Ki 太小或积分限幅
解决:

  • 增大 Ki(每次 10-20%)
  • 检查积分是否被意外清零
  • 确认电机驱动有能力克服摩擦力

问题 4:响应太慢

原因: Kp 太小或控制频率低
解决:

  • 增大 Kp
  • 提高控制频率到 200-500Hz
  • 检查电机驱动电流是否足够

进阶优化

1. 积分限幅

防止积分累积过大导致超调:

float integralMax = 500;
integral = constrain(integral, -integralMax, integralMax);

2. 微分滤波

减少噪声影响:

float derivativeFilter = 0.8;
derivative = derivativeFilter * lastDerivative + 
             (1 - derivativeFilter) * rawDerivative;

3. 死区补偿

克服静摩擦力:

if (abs(output) < 20 && abs(error) > 50) {
  output = (output > 0) ? 20 : -20;
}

实际应用场景

  • 云台相机: 保持相机稳定,抵消手部抖动
  • 机器人关节: 精准控制机械臂角度
  • 平衡车: 实时调整电机保持平衡
  • CNC 进给轴: 精确控制刀具位置
  • 卷扬机: 恒张力控制

总结

闭环控制是电机控制的进阶技能,核心在于:

  1. 选对编码器: DIY 用 AS5600 足够,工业用多圈绝对编码器
  2. 调好 PID: 没有捷径,必须实际测试
  3. 注意细节: 电源、接线、控制频率都会影响性能

从开环到闭环,你的项目会从"能动"升级到"精准可控"。

希望这篇博客文章对您有所帮助!