Arduino电机PWM控制及编码器实战:从基础原理到精准调速

1次阅读
没有评论

共计 4161 个字符,预计需要花费 11 分钟才能阅读完成。

image.webp

背景痛点

在机器人或自动化项目中,电机控制常常面临速度不稳定、无法精确控制位置的问题。传统的开环控制方式,即只发送控制信号而不反馈实际状态,存在几个明显的缺陷:

Arduino 电机 PWM 控制及编码器实战:从基础原理到精准调速

  • 速度受负载变化影响大,尤其在启动和停止时波动明显
  • 无法感知电机实际转速和位置,导致控制精度低
  • 缺乏保护机制,容易因堵转或过载损坏电机

这些问题在需要精确控制的场合(如 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 位)。配置步骤如下:

  1. 设置波形生成模式为相位校正 PWM(WGM13:10=0001)
  2. 选择非反相输出模式(COM1A1:0=10)
  3. 设置预分频系数为 1(CS12:10=001)
  4. 设置 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 两相),可采用双边沿触发模式:

  1. 配置中断引脚为 INPUT_PULLUP 模式
  2. 设置中断触发方式为 CHANGE(双沿触发)
  3. 在 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 控制器是闭环控制的核心,需特别注意积分饱和问题:

  1. 比例项 (P):与当前误差成正比,决定响应速度
  2. 积分项 (I):累积历史误差,消除稳态误差
  3. 微分项 (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):无噪声,但可能导致电机驱动效率下降

编码器信号处理

  1. 硬件消抖 :在编码器信号线上添加 0.1uF 电容
  2. 软件消抖 :在 ISR 中添加延时判断
    void encoderISR() {
      static unsigned long lastTime = 0;
      if(micros() - lastTime < 100) return; // 100us 消抖
      lastTime = micros();
      // 正常处理逻辑...
    }

中断冲突预防

  • 避免使用与 Timer1 冲突的库(如 Servo 库)
  • 对于多编码器系统,优先使用 PCINT 引脚而非外部中断
  • 保持 ISR 尽可能简短,避免长时间中断影响系统响应

性能验证

使用示波器检查以下指标:

  1. PWM 波形质量 :观察占空比变化是否平滑,边缘是否干净
  2. 速度跟踪误差 :记录阶跃响应下的超调量和稳定时间
  3. 抗干扰能力 :手动扰动电机轴,观察系统恢复速度

典型性能指标参考:
– 空载速度误差:<±5%
– 阶跃响应稳定时间:<0.5s
– 负载突变恢复时间:<0.3s

扩展思考

如何实现多电机同步控制?可以考虑以下方向:

  1. 主从控制 :指定一个主电机,其他电机跟随其位置
  2. 电子齿轮 :通过比例关系同步多个电机运动
  3. 分布式控制 :每个电机使用独立 PID,通过 CAN 总线协调

希望这篇实战指南能帮助你构建可靠的电机控制系统。在实际项目中,建议先用小功率电机测试,确认所有保护机制正常工作后再上大功率设备。

正文完
 0
评论(没有评论)