
简介面向需要实现电机精确距离控制的STM32开发者这份压缩包提供了一个基于PID算法与编码器反馈的完整工程示例。资源针对增量式编码器测距、PID参数整定及PWM调速等核心环节给出可直接在Keil中打开的工程文件。包内共有236个文件以121个.h头文件、107个.c源文件为主涵盖stm32f4xx系列HAL库驱动、定时器编码器接口及控制逻辑另有.uvprojx工程文件、.s启动文件、.hex烧录文件与清理脚本压缩包仅1.65MB结构紧凑便于学习与二次开发。已有1020人学习下载。通过阅读源码可掌握STM32如何读取编码器脉冲计算行驶距离、如何编写位置式PID控制器以及如何将控制量映射为电机PWM占空比。同时包内保留了I2C、UART、SPI等外设驱动代码适合用于底盘控制、小车循迹等扩展场景是理解闭环运动控制的实用参考。 做智能车、AGV、机器人底盘这类项目的时候“走指定距离”这个需求几乎绕不开。我最早接触这个问题是帮朋友调一台两轮差速小车要求按一下按键车稳稳当当停在一米外。当时想得特别简单给电机一个固定PWM延时个几百毫秒不就行了结果车要么冲出去一米五要么走半米就停换一块电池又变一个样。那一刻我才意识到距离控制背后根本不是“写个延时”这么简单它是一个典型的闭环控制问题。这篇文章就专门讲怎么用STM32配合编码器把一个“行驶指定距离”的功能做得又准又稳适合正在做智能车、AGV搬运小车、或者任何运动控制底盘的读者参考。1. 项目整体思路为什么“行驶指定距离”不是写个延时那么简单1.1 开环控制的致命缺陷很多人第一步都会尝试开环GPIO翻转电平、TIM输出PWM让电机转然后延时一段时间断电。这玩意儿在小车上能不能跑能跑但根本谈不上“控制”。原因特别朴素电机转速不是恒定的。首先电池电压会波动。我刚充满电的锂电池是8.4V用到后面掉到7.2V同样占空比的PWM下电机实际转速差了接近20%。其次地面摩擦阻力不一样瓷砖地、地毯、塑胶跑道同样电压下车速差别很大。再加上电机生产批次差异、两个轮子磨损程度不同开环跑的距离几乎无法复现。我实测过同一台车同一段代码满电和欠电状态下跑出来的距离误差能到30%以上。这就意味着想要准就必须引入反馈用“实际走了多远”来修正“应该给多少动力”。1.2 闭环控制系统长什么样闭环控制的思路其实特别像人走路你心里定了个目标距离五米眼睛看着前面的参照物脚在迈步大脑不断对比“目标”和“实际”的差值然后调整步幅和频率。放到STM32系统里这几个角色分别对应目标量用户设定的行驶距离比如1500mm反馈量编码器累计脉冲算出来的实际行驶距离执行器PWM驱动的直流减速电机控制器PID算法或者简单的分段减速策略。系统的核心链路是编码器脉冲 → 定时器计数 → 主循环或中断里累加距离 → 和设定值比较 → 计算出误差 → 输出新的PWM占空比。这个回路里最重要的传感器就是编码器它是整个闭环的眼睛。没有它后面所有计算都是空中楼阁。2. 硬件平台搭建选对编码器电机成功一半2.1 电机、驱动器与车体怎么选做行驶距离控制我建议直接用带霍尔编码器的直流减速电机不要用普通TT马达。普通马达完全没有任何反馈信号永远走不了闭环。市面上常见的带编码器电机有两种一种是13线制的电机两根线加编码器电源地加A/B相一种是六线制的电机两根线加编码器四根线其实本质都一样只是封装不同。选电机时重点看三个参数减速比、编码器线数、空载转速。以我常用的MG310电机为例减速比1:30编码器是霍尔式的电机轴每转一圈输出13个脉冲经过减速箱后轮子转一圈编码器累计390个脉冲。如果用四倍频模式读取就是1560个计数单位。轮子直径65mm周长约204mm算下来每个四倍频计数对应大约0.13mm的行驶距离这个精度对于小车类项目完全够用。驱动器方面L298N和TB6612二选一的话我强烈推荐TB6612。L298N压降太大2.5V左右的饱和压降直接吃掉一部分电压电池电压稍微低一点电机就转不动了TB6612的导通压降只有0.5V左右效率高很多而且体积小、发热低可以直接兼容3.3V逻辑电平省去电平转换。接线时注意逻辑电源和电机电源分开TB6612的VM接电池正极VCC接3.3VSTBY和PWMA接高电平AIN1/AIN2控制方向。2.2 编码器信号接入STM32的两种方式编码器输出A相和B相两个方波信号两路信号相位相差90度。STM32读取它们有两种主流方案。方案一使用定时器的编码器模式。STM32的TIM1、TIM2、TIM3、TIM4都内置编码器接口把A相接CH1B相接CH2配置成Encoder Mode TI1TI2定时器计数器就会自动根据相位关系加减计数。这个方案的好处是CPU零负担不用在中断里做方向判断硬件帮你做了四倍频计数器数值直接就是位移量。缺点是占用了定时器的两个通道而且要注意定时器溢出的处理要开启更新中断或者在主循环里周期性读取并累加到软件变量里。方案二用外部中断加GPIO查询。A相接EXTI外部中断在中断里读B相电平判断方向然后软件加减一个变量。这个方案灵活但中断频繁时容易丢脉冲转速高的时候更明显。我做距离控制一般优先选方案一因为编码器模式既省心又不容易出错唯一的坑是定时器是16位的计数范围只有0到65535所以必须定期把计数值搬到软件里做累加还要考虑正反转时计数器会从65535回绕到0的情况。3. 距离计算从脉冲到毫米的关键换算3.1 一个脉冲对应多少毫米这是整个项目里最容易算错的一步也是我踩过最深的一个坑。先给个标准公式单脉冲对应距离 轮子周长 ÷ (编码器每圈脉冲数 × 四倍频系数) π × 轮径 ÷ (电机减速比 × 编码器线数 × 倍频系数)以我的配置为例轮径65mm、减速比30、编码器13线、四倍频系数4代入计算脉冲当量 3.14159 × 65 ÷ (30 × 13 × 4) ≈ 204.2 ÷ 1560 ≈ 0.131mm也就是说定时器计数器每增加1车实际前进约0.131毫米。假设目标距离是1500mm需要的计数总量就是1500 ÷ 0.131 ≈ 11450个。这个数算出来之后主程序的目标就非常明确了累计计数到11450就执行停车。这里有一个细节特别提醒如果有减速比一定不要忘了乘进去。我见过有人把13线当成每圈13个脉冲直接用轮子转一圈计数只加了13结果车跑出去十几米才停。另外两个轮子的实际轮径可能有微小差异如果对精度要求极高可以实测跑一段距离反推当量比如让车跑10圈轮子量实际位移再除以理论位移得到一个修正系数这个属于标定范畴后面我还会提到。3.2 速度怎么测距离控制光知道走了多远不够还得知道当前速度PID调速时离不开速度反馈。速度的本质是“单位时间内脉冲变化量”。我在工程里用了两个方式方式一是定时器做等间隔采样。开一个10ms的定时中断在中断里读取编码器当前计数值减去上次的计数值得到这10ms里的脉冲增量。把这个增量乘上脉冲当量0.131mm再除以0.01秒就是毫米每秒的速度。举例来说10ms里测到50个脉冲速度就是50 × 0.131 ÷ 0.01 655mm/s。方式二是测脉宽法适合速度很低、脉冲很稀疏的场合做法是等脉冲上升沿来了之后用另一个定时器测两个脉冲之间的时间间隔。这种方式低速时精度高高速时容易溢出我一般只在极低速对堵转状态判断时用日常调速都用方式一。10ms的采样周期要对所有中断共用合理规划。编码器模式用的是TIM2我速度采样用的是TIM3的更新中断PWM输出用TIM4的通道1。几个定时器各司其职逻辑非常清晰调试的时候也容易定位问题。4. PID调速与停车策略让车“刹车”也有节奏4.1 PID到底在解决什么问题前面说过同一个PWM占空比在不同电池电压下转速不一样。PID就是用来解决“怎么让实际速度自动跟着目标速度走”的问题。比如我希望车以600mm/s的速度巡航但实际只有400mm/sPID就会输出更大的PWM值当实际速度接近600mm/s时PWM增量减小最终稳定在一个合适的值。我常用的增量式PID代码结构是这样的float pid_update(float target_speed, float current_speed) { float error target_speed - current_speed; integral error; if (integral INT_LIMIT) integral INT_LIMIT; if (integral -INT_LIMIT) integral -INT_LIMIT; differential error - last_error; last_error error; return Kp * error Ki * integral Kd * differential; }返回值就是PWM占空比的修正量。整定顺序我一般先只留P从小到大一点点加等车不抖且能跟上目标值时再加I消除稳态误差最后加D抑制过冲。调试PID有个很实用的技巧串口打印目标速度和实际速度的曲线。我习惯用STM32串口把两组数据发出来格式类似“target:600,current:580”在串口助手或者匿名上位机里绘图。如果当前速度围绕目标值快速抖动说明P太大如果慢吞吞追不上说明P太小稳态差一截则需要加强I项。别凭感觉调拿数据说话。4.2 停车策略快到目标要减速距离控制里最影响精度的环节不是跑的时候而是停下来的时候。直流电机有惯性从高速到停止还有一个减速过程这个过程中车依然在前进属于“过冲”。我最早在1.5m/s的高速下直接目标到了就断电实测过冲量能达到20cm以上这显然不合格。解决办法是分段减速我称之为“两段式停车策略”。设定一个减速阈值比如剩余距离小于200mm时目标速度降为巡航速度的三分之一剩余距离小于50mm时再降为更低的速度比如60mm/s。代码逻辑其实很简单float get_target_speed(int remaining_distance) { if (remaining_distance 300) return CRUISE_SPEED; // 正常巡航 if (remaining_distance 100) return CRUISE_SPEED / 2; if (remaining_distance 30) return CRUISE_SPEED / 4; return 0; // 到达目标 }注意判断剩余距离时要用“目标计数总量 - 当前累计计数”这个差值是实时变化的。当剩余距离小于某个值后PID的目标速度立刻下降车开始平滑减速。实测下来两段减速低速逼近后停车误差能控制在1cm以内。如果项目允许再复杂一点可以做S型速度规划加减速过程更平滑过冲更小但两段式对大多数场景已经够用了。还有一个小技巧最后一段低速逼近的判定不要等到剩余距离为0才停可以在低速段每10ms检测一次一旦剩余距离小于等于0立即将PWM置0并锁定方向引脚为刹车状态。有些电机驱动支持刹车模式比如TB6612把AIN1和AIN2都设为高电平电机两端短路形成电磁制动能明显缩短滑行距离这个一定要用上。5. 常见问题与调试经验实录5.1 编码器数值乱跳怎么办我调试时遇到最诡异的现象是车没动编码器数值却在跳。排查下来有两个主要原因。一是电源纹波干扰霍尔编码器对电源噪声非常敏感尤其是电机启停瞬间电流冲击大会把毛刺传到编码器电源线上。解决方法是给编码器电源加一个100nF到1uF的陶瓷电容位置尽量靠近编码器接口电机供电和编码器供电分开走线不要共用一条细线。第二个原因是接地回路问题。我的驱动器和STM32共用一个电源时电机大电流在地线上产生的压降会干扰逻辑信号。后来我把逻辑地和功率地在驱动器附近单点连接干扰立刻小了很多。所以遇到编码器计数乱跳先查硬件再查软件不要一上来就怀疑程序逻辑。5.2 停车精度不够还有什么招两段式减速都用了误差还是偏大怎么办我遇到过几种情况。第一种目标距离特别长比如十米以上累计误差累积。这种情况需要做标定修正前面提到的脉冲当量修正系数在这里起作用。具体做法让车跑一个已知距离比如10圈轮子量实际位移算出修正系数用修正后的当量参与计算。第二种两个轮子转速不一致导致车跑偏虽然只是走直线但实际走的是弧线。常见原因是两个电机负载不同或者驱动器两路PWM共用了同一个占空比但是没有各自闭环。解决办法是给左右轮分别配一套PID用编码器分别反馈让两轮实际线速度强制一致这也是差速小车循迹的基础。第三种PID参数不匹配导致低速时抖动。低速段电机本身转动不平稳齿轮换相扭矩波动会让编码器看起来忽快忽慢PID为了追平这个波动反而输出振荡。我的处理是低速段适当降低P增益或者设置一个死区误差小于某个阈值时维持当前PWM不变。5.3 问题速查表我把调试过程中遇到的典型问题整理成了一张表方便大家在现场快速定位。现象可能原因排查与解决车不动驱动器使能脚未拉高检查STBY引脚是否接3.3V车不动PWM通道输出异常用示波器或逻辑分析仪看PWM波形走了但距离偏短减速比没乘核对计算公式中的减速比和编码器线数走了但距离偏长行程中电池掉压剧烈加PID闭环别用固定PWM编码器数值乱跳电源干扰编码器电源加去耦电容功率地逻辑地分开停车过冲大没有减速段增加分段减速启用刹车模式低速抖动PID增益太高降低P增益或加误差死区轮子跑偏两轮转速不一致左右轮分别闭环统一目标线速度5.4 关于“no stm32 target found”这类调试器问题排查距离控制问题时难免需要反复烧录下载这时候如果弹出来“no stm32 target found”之类的报错先别急着怀疑程序写崩了。我遇到好几次是ST-Link的SWD四根线接触不良尤其是杜邦线长时间插拔后针脚氧化重新插紧就好了。还有就是目标板供电不足ST-Link单靠自身接口供电带不动整个板子直接用外部供电再下载就正常了。平时我习惯在SWD接口的VCC和GND之间并一个100uF电容可以有效稳定下载时的供电。最后再分享一个我一直在用的调试习惯把目标距离、当前累计计数、剩余距离、当前速度这四组数据通过串口实时打出来。调车的时候盯着串口数据比对每个阶段的实际表现比盲调盲测高效得多。这个项目做完之后我明显感觉对STM32定时器资源、编码器原理、PID控制的理解都上了一个台阶。把这个基本功打牢后面再做循迹、避障、里程计定位都是在今天这套系统上加传感器和算法的量级扩展。本文还有配套的精品资源点击获取