用STM32做了台AGV底盘,编码器老丢步走歪,最后栽在一根排线上
引子
照着社区教程攒了台差速 AGV 底盘,STM32 读编码器做测速闭环。空载转得好好的,一上低速带载直行就往一边偏,串口一看——左轮编码器计数偶尔整拍往外跳、又或者半天不涨,明显丢步。PID 怎么调都压不住,一度怀疑电机和机械装歪了。
硬件清单
- 主控:STM32F103C8T6(用 TIM2/TIM3 编码器模式读 AB 相)
- 驱动器:艾思控 AQMD2408NS-MBE 直流有刷驱动器(适配 12V-45W / 24V-130W,倍流 16A/8A、短接制动峰值 50A,能耗制动 3A,带电刹接口、独立编码器接口、通讯隔离,尺寸 55×55×17mm)
- 电机:24V 永磁有刷减速电机 ×2,尾端带霍尔 AB 相编码器(11 PPR,倍频后 44 计数/转)
- 电源:24V 5A 开关电源
- 上位:CH340 串口模块 + 电脑串口助手(本次排查全程当"穷人示波器"用)
接线说明
电机动力线(U/V 两芯)接驱动器输出端,编码器 A、B 两相走驱动器的独立编码器接口,主控电源与动力电源分开走线、只在电源端单点共地。STM32 的 PWM 占空比经两根信号线进驱动器调速端,编码器脉冲再从驱动器侧引回 TIM2/TIM3。
【配图建议:整机俯视接线走线图,红为动力回路、蓝为编码器信号回路,标出捆扎段】
【配图建议:驱动器独立编码器接口端子特写 + 屏蔽层单点接地位置】
现象描述
低速直行(占空比约 25%)时,车体稳定往左偏;串口每 100ms 打印一次左右轮增量计数,正常应当两边同步平滑增长,实测左轮每隔几秒"卡一下"——某拍突然为 0,下一拍又猛地多出一截,像丢了一拍又补回来。电机听声音无异响,手转编码器轴读数连续,说明编码器本体没坏。
排查过程
第一步,先怀疑 PID 和测速周期。 把速度环 Kp 调小、测速周期从 10ms 拉到 20ms,丢步现象一点没改善,反而低速更抖。判断不是控制参数问题,先排除。
第二步,怀疑机械与电机。 拆下左轮空转、换一只同型号电机对调测试,丢步照样出现在"原来那条线"上,跟电机无关,排除机械和电机本体。
第三步,上串口当示波器抓计数波形。 把左轮编码器每拍的原始计数经串口打出来,发现"卡一拍"的时间点并非随机——它跟占空比、跟电机负载转矩相关:低速大占空比跳变时密集出现。这让我把怀疑转向了信号线,而不是算法。
第四步,改线做对照。 原本编码器的 AB 两根细线和 PWM 调速线、动力线一起被扎带捆在同一束里,从底盘一头拉到另一头。我把编码器线单独抽出来,换带屏蔽的双绞、远离动力线走,丢步立刻稀疏到几乎没有——锁定根因是信号被功率回路干扰。
根因分析
- PWM 开关沿耦合进编码器线。 驱动器输出的 PWM 沿很陡,捆扎段里编码器细线与 PWM/动力线长期平行,容性耦合把开关毛刺叠加到 AB 相上,脉冲计数芯片把毛刺误判成边沿,造成"多计数",硬件消抖窗口内又把真边沿吃掉,表现为"丢一拍"。
- 回流路径共用、共地不干净。 编码器和功率回路在底盘多处各自接地,电机换向时的大电流回流在地线上压出毫伏级跳动,正好落在编码器阈值附近,临界翻转到 0/1 之间抖动。
- 长排线当天线。 低速时电机反电动势小、PWM 占空比变化剧烈,干扰能量相对更强;编码器线越长越贴动力线,拾取的共模噪声越多,所以越是低速带载越明显——这解释了"高速反而正常"。
- 测速端没做硬件滤波。 AB 相直接进 MCU 定时器,没在信号进脚前做任何 RC 抑制,毛刺原封不动进了计数逻辑。
解决过程
改了三件事:一是编码器线独立走线、屏蔽层在驱动器侧单点接地;二是在 A、B 相进主控脚前各加一级 RC 低通;三是把编码器接到驱动器自带的独立编码器接口上,借助这一档接口本身的隔离设计,让脉冲回路和调速 PWM、动力回路在驱动板内部就分开。
硬件上每相:信号串 1kΩ,对地 1nF(fc≈160kHz,远高于电机最高转速对应脉冲频率,又压得住 PWM 沿毛刺)。软件上把编码器口配置加了一路数字滤波,并用串口把滤波前后计数同时打印,肉眼验证是否还有异常跳变。
// TIM2 编码器模式:AB 相进脚前已由 1k+1nF RC 滤波
void Encoder_Init(void){
TIM_Encoder_InitTypeDef e = {0};
e.EncoderMode = TIM_ENCODERMODE_TI12; // AB 双沿,4 倍频
e.IC1Polarity = TIM_ICPOLARITY_RISING;
e.IC1Selection = TIM_ICSELECTION_DIRECTTI;
e.IC1Prescaler = TIM_ICPSC_DIV1;
e.IC1Filter = 0x0F; // 数字滤波:0~15,越大越钝,先取15
e.IC2Selection = TIM_ICSELECTION_DIRECTTI;
e.IC2Filter = 0x0F;
TIM_EncoderInit(TIM2, &e);
TIM_Cmd(TIM2, ENABLE);
}
// 每 20ms 取一次增量,串口打印,异常拍直接暴露
void SpeedTick(void){ // 20ms 定时中断里调用
static int16_t last = 0;
int16_t now = (int16_t)TIM2->CNT;
int16_t inc = now - last; last = now; // 本拍增量
printf("ENC %d\r\n", inc); // 串口当示波器:肉眼找跳变
}
【配图建议:串口助手滚动打印截图,左为改线前(带 0/突跳),右为改线后(平滑)】
验证结果
改线+加 RC+数字滤波后,串口连续跑了十几分钟低速带载直行,左轮增量计数不再出现 0 拍和突然补拍,左右轮基本同步,车体直行偏差肉眼可接受。用串口这套对比法,没有示波器也定位到了问题。整体温升在长时间低速运行下也不明显,这一档带 24V-130W 电机留的电流余量够用。
踩坑小结
- 编码器"丢步"先别怀疑 PID——先分清是算法丢速还是信号丢拍,串口打印原始增量就能区分。
- 编码器信号线千万别和 PWM、动力线捆一束;能独立走线就独立走,屏蔽层只在驱动器侧单点接地。
- 信号进 MCU 前先做 RC + 定时器数字滤波两级防御,RC 截止频率卡在"电机最高脉冲频率之上、PWM 沿毛刺之下"。
- 带独立编码器接口的驱动器会省不少事,脉冲回路和调速回路在板内就分开了,比自己硬拉排线干净。
- 手头没示波器时,串口打增量计数是好替代,异常拍在滚动打印里非常显眼。
如果你也碰上编码器偶发丢拍,是先从走线动手,还是先猛加软件滤波扛过去?欢迎评论区聊聊你的处理顺序。




