从零搭建智能小车:基于A4950与Arduino的直流减速电机PID速度闭环实战

张开发
2026/4/18 22:54:35 15 分钟阅读

分享文章

从零搭建智能小车:基于A4950与Arduino的直流减速电机PID速度闭环实战
1. 硬件选型与电路搭建搞智能小车的第一步就是把硬件给凑齐了。我刚开始玩的时候最头疼的就是选配件市面上电机驱动模块五花八门后来发现A4950特别适合新手。这个芯片自带过流保护发热量小最关键的是接线简单不用像L298N那样还要外接一堆二极管。核心配件清单Arduino Mega 2560UNO也行但中断引脚不够用A4950电机驱动模块建议买带散热片的版本12V直流减速电机带霍尔编码器我用的是6V-12V/500RPM款航模电池12V/2200mAh够玩半小时杜邦线若干建议用硅胶线的不容易脱焊接线这块有个坑要特别注意A4950的VM引脚接电池正极时一定要先经过一个开关我去年烧过两块板子就是因为电机堵转时电流过大。正确的接法应该是电池正极 → 开关 → A4950的VM引脚 电池负极 → A4950的GND引脚 → Arduino的GND共地编码器接线更讲究以我用的JGA25-370电机为例电机A相接Arduino的2号引脚中断0B相接3号引脚中断1记得给编码器信号线加上10kΩ上拉电阻不然高速旋转时容易丢脉冲2. 编码器信号处理实战很多教程只讲理论实际用编码器时会遇到各种玄学问题。先说个真实案例上周我徒弟的小车明明没动串口却显示转速200最后发现是杜邦线接触不良产生的杂波。四倍频计数才是王道普通测速只检测A相上升沿精度太低。我改良后的方案用attachInterrupt()捕获A、B相的所有边沿变化volatile long encoderCount 0; void setup() { attachInterrupt(digitalPinToInterrupt(2), updateEncoder, CHANGE); // A相 attachInterrupt(digitalPinToInterrupt(3), updateEncoder, CHANGE); // B相 } void updateEncoder() { int a digitalRead(2); int b digitalRead(3); if(a b) encoderCount; else encoderCount--; }这个方法的妙处在于电机反转时计数值自动递减而且分辨率提升4倍。实测在500RPM下误差从±15RPM降到了±3RPM。定时中断测速法用FlexiTimer2库每20ms计算一次转速#include FlexiTimer2.h float RPM 0; void control() { noInterrupts(); long currentCount encoderCount; encoderCount 0; interrupts(); RPM (currentCount / 780.0) * 3000; // 780是电机转一圈的脉冲数 }这里有个细节noInterrupts()和interrupts()必须成对出现否则会丢数据。曾经因为漏写这个我的小车转速显示总是跳变。3. PID调参的玄学与科学网上PID教程一堆但能把人讲明白的没几个。先说结论速度闭环用PI就够了D项纯属添乱。去年参加机器人比赛时我加了D项后电机反而开始抽搐。参数整定三步法先把Ki设为0Kp从0.1开始试float Velocity_KP 0.1, Velocity_KI 0;慢慢增大Kp直到电机出现轻微震荡我的电机在Kp1.2时开始抖取震荡值的60%作为最终KpVelocity_KP 1.2 * 0.6; // 得到0.72逐渐增加Ki直到转速能稳定在目标值我的电机在Ki0.05时300RPM的稳态误差±2RPM抗积分饱和技巧直接上改良版PI代码int Incremental_PI(int Encoder, int Target) { static float PWM, Last_bias, integral; float Bias Encoder - Target; // 抗饱和处理 if(abs(PWM) 155) { integral Velocity_KI * Bias; } PWM Velocity_KP * Bias integral; PWM constrain(PWM, -155, 155); Last_bias Bias; return (int)PWM; }这个版本增加了积分分离当输出接近限幅值时停止积分避免电机停不下来。实测发现堵转恢复时间从原来的3秒缩短到0.5秒。4. 系统联调与性能优化调好的PID在空载时很稳但一装上轮子就崩这是新手常遇到的机械问题。我的小车第一次下地测试时左轮转速总是比右轮慢20%后来发现是底盘装配不平导致的摩擦不均。动态补偿方案在loop()里加入负载检测void loop() { static int lastRPM 0; int currentRPM getRPM(); // 检测负载突变 if(abs(currentRPM - lastRPM) 50) { Velocity_KP * 1.2; // 临时增大比例项 delay(100); Velocity_KP / 1.2; } lastRPM currentRPM; }PWM频率优化默认490Hz的PWM会让电机吱吱叫改成31kHz就安静了void setHighFrequencyPWM() { TCCR1B (TCCR1B 0b11111000) | 0x01; // D9,D10引脚 TCCR3B (TCCR3B 0b11111000) | 0x01; // D2,D3,D5引脚 }注意Mega2560的不同引脚对应不同定时器改错会导致舵机失控。有次我误改了D11的定时器结果超声波模块直接罢工。最后说说电源管理——用示波器看电池电压会发现电机启动瞬间电压会骤降2V以上。我的解决方案是并接一个大电容4700μF/25V同时代码里加入软启动for(int i0; i100; i) { analogWrite(motorPin, i); delay(10); }

更多文章