掌握直流电机控制:PID 与 PWM 专家技巧全解析
元器件与物料
- Arduino Due(实际上任何 Arduino 板均可) ×1
- Makeblock Me TFT LCD(可选,可用其他显示屏或不使用) ×1
- 4.75kΩ 电阻 ×4
- 旋转电位器(通用) ×4(其中 3 个可选,用于调节 PID 系数)
- 通用 PNP 三极管 ×4(具体型号视实现而定)
- 直流电机(通用) ×1
- 滑动开关(用于方向选择) ×1
- 光电测速传感器 HC-020K ×1
- 面包板(通用) ×1
- 跳线(通用) ×1
应用与在线服务
- Arduino IDE
项目简介
基于 PID 控制器和 PWM 输出的直流电机转速与方向控制
引言
几乎所有项目都希望同时控制电机转速和方向,但大多直接将 PWM 信号发送给电机(即使通过驱动电路)。这种方法在需要精确匹配目标转速时总会失败,因为“摩擦”和“惯性”这对“孪生兄弟”会干扰控制。惯性让事物在行动前“思考”,摩擦限制加速度和速度。如果“功率”不在完全控制之下,它就“一无是处”。
直接用 PWM 控制转速时,实际转速永远无法达到设定值,存在显著误差(见上图)。因此我们需要另一种方法——PID 控制。
PID 控制器原理
想象开车:起步时需深踩油门,匀速时只需补偿速度损失,加速过快时则稍松油门。这正是“高效驾驶”的方式,也是 PID 控制器的工作原理。
PID 控制器读取设定值与实际输出的差值“误差信号 (e)”,包含三个分量:
- 比例 (P): 定义输出随误差变化的斜率(加速度)。
- 积分 (I): 随时间累积误差,以最小化最终稳态误差。
- 微分 (D): 监视误差变化率,提供“调节”作用。
Arduino 程序中的 PID 函数实现如下:
float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd) {
float P, I, D;
/* 基础公式: U = _Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */
P = _Kp * _E; // 比例分量
I = _Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; // 积分分量
D = _Kp * _Kd * (_E-_Eprev) / _dT; // 微分分量
return (P+I+D);
}
最终输出值 = 当前输出 + PID 控制器输出。主程序片段:
/* 误差信号、PID控制器输出和最终输出(PWM)给电机 */
E = RPMset - RPM;
float cPID = controllerPID(E, Eprev, dT, Kp, Ki, Kd);
if (RPMset == 0) OutputRPM = 0;
else OutputRPM = OutputRPM + cPID;
if (OutputRPM < _minRPM) OutputRPM = _minRPM;
直流电机供电电路
绝不建议直接从 Arduino 输出驱动直流电机,电机电流远超控制器输出能力。通常驱动继电器线圈,但继电器有机械触点,长期使用会失效。因此改用晶体管。直流电机由电流驱动而非电压,故采用晶体管控制电流。必须选择能承受电机电流的晶体管:先将电机直接接电源测量最大工况电流,或参考厂家规格书。
作者使用四只 BC307A PNP 三极管组成“桥式”电路改变电流方向(NPN BC337 更佳,因集电极电流耐受更高,但手头没有)。因电流流过发射极-集电极,需挑选直流电流增益 (hfe) 近似的三极管。测试电路如图所示,需考虑:
- 基极-发射极开启电压 (VBEon)
- 典型直流电流增益 (hfe) = IC / IB
- 最大连续集电极电流 (ICmax),电机电流不得超过此值
基极电阻计算公式:R = (VT - VBEon) * hfe / IM
作者案例:电机电流 70mA,BC307A hfe≈140,VBEon=0.62V,触发电压 3.3V(Arduino Due PWM),算得 R≈5360Ω,选用 4.9kΩ(2.2k+2.7k串联),PWM 引脚仅消耗 ~0.6mA,设计合理。
反转方向与重要注意事项
反转直流电机只需改变电流方向。原理图中 PWM 输出 #2 驱动 T1A/T1B,PWM 输出 #3 驱动 T2A/T2B,从而改变电机线圈电流方向。
关键问题: 电机启动瞬间会吸取远大于额定电流的浪涌电流(小功率电机约 130% 额定电流)。若在运行中直接反向供电,电机未完全停止会吸取极大电流,可能烧毁电源或线圈。使用晶体管驱动时,晶体管已限制电流,可避免此问题。无论如何,程序中加入保护逻辑:检测到方向改变时,先将两路 PWM 输出置零,等待电机完全停止后再切换方向。
if (Direction != prevDirection) {
analogWrite(_chMotorCmdCCW,0);
analogWrite(_chMotorCmdCW,0);
do { RPM = 60*(float)readFrequency(_chSpeedRead,4)/_DiscSlots; }
while (RPM > _minRPM);
}
测速原理
使用廉价 HC-020K 测速传感器,输出脉冲电平等于供电电压(数据手册标称 5V),但 Arduino Due 只能接受 3.3V。作者直接用 Due 的 3.3V 供电传感器,实测可工作。读取频率的函数如下:
int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed) {
pinMode(_DI_FrequencyCounter_Pin,INPUT);
byte _DigitalRead, _DigitalRead_Previous = 0;
unsigned long _Time = 0, _Time_Init;
float _Frequency = 0;
if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1);
else {
_Time_Init = micros();
do {
_DigitalRead = digitalRead(_DI_FrequencyCounter_Pin);
if ( (_DigitalRead_Previous==1) && (_DigitalRead==0) ) _Frequency++;
_DigitalRead_Previous = _DigitalRead;
_Time = micros();
} while ( _Time < (_Time_Init + (1000000/_ReadingSpeed)) );
}
return (_ReadingSpeed * _Frequency);
}
HC-020K 编码轮有 20 个槽,读取频率除以 20 得每秒转数,再乘 60 得 RPM:RPM = 60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots;
图形界面美化
使用 Makeblock Me TFT LCD 显示输入与结果,编写 CommandToTFT() 函数发送指令,便于切换串口连接点。另编写 Cartesian_Setup()、Cartesian_ClearPlotAreas()、Cartesian_Line() 绘制坐标系、清除绘图区、绘制曲线。详细图形函数说明请参阅 Makeblock 手册,此处不赘述。
结语
代码分为带图形界面版和纯控制版两份,方便单独研究 PID 控制实现。正常情况下无负载直流电机最低转速只能降至额定转速的 10-20%,但使用 PID 控制启动后可降至近 5%。
补充 (2018.02.25): 若改用 NPN 三极管,注意电流方向相反:PNP 导通时电流从发射极流向集电极,NPN 则从集电极流向发射极。因此 PNP 极性为 E(+) C(-),NPN 应为 C(+) E(-)。
代码
完整代码(含图形界面版和纯控制版)请见附件或项目页面。
原理图
这是一个原型,用于解释使用 PID 控制器进行直流电机调速及反转时的注意事项。(原文包含原理图图片)
制造工艺
- Satellite CCRMA 套件:构建传感器驱动的音频效果器
- Arduino Nano + 摇杆控制双步进电机:Visuino 图形化教程
- Arduino Uno驱动MAX7219级联8×8 LED矩阵:从入门到动画显示
- 校园智能体温监测系统 - 精准、实时、安全的学生健康守护方案
- Arduino 8 位并行 IO 端口库:一行代码实现 8 位数字读写
- Arduino 64 键原型键盘矩阵:用移位寄存器打造全自定义键盘
- Arduino Nano 专用 2.8 英寸 TFT 扩展板 – 320×240 SPI 显示屏
- 为Arduino构建隔离模拟输入电路
- 低成本 Arduino 闪电探测器制作指南
- 适用于 Arduino 项目的智能室内导航机器人套件
- 基于 ACS712 传感器的 Arduino 交流电流精准测量(真有效值)
- 钨铜合金提升直流电机性能