ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32智能小车实战:编码器测速+增量式PID+超声波避障全解析

STM32智能小车实战:编码器测速+增量式PID+超声波避障全解析 做这个项目之前我其实刚把STM32F103C8T6的GPIO、串口、定时器摸了个遍。当时就一个感觉——单片机外设再多一个个点亮LED、翻转电平始终差点意思。直到朋友丢给我一句话“你不如让小车自己跑起来撞墙会躲、直线不偏那才叫会单片机。”于是就有了这个“速通”项目的第2章。这一章的核心就三件事让小车知道自己跑多快、让电机精准执行速度指令、让小车根据前方障碍物自己转向。听起来不复杂真做起来编码器、中断、PID、超声波、滤波、舵机、定时器全部串在一起任何一个环节出问题车都跑不直或者撞墙。我尽量把整个过程讲透从硬件选型到算法实现再到调参踩坑把我实际测试过的东西原原本本写出来。1. 项目整体设计与任务拆解1.1 硬件选型的底层逻辑主控我用的就是STM32F103C8T6最小系统板。这颗芯片虽然是多年前的老将但放在这个小车上完全够用——72MHz主频三个通用定时器加一个高级定时器多个ADC通道再加上成熟到不能再成熟的固件库和HAL库生态拿来做电机控制加超声波的组合是再合适不过了。而且现在国产替代型号也不少比如GD32F103C8T6引脚和寄存器基本兼容直接替换也没问题采购成本还能再压一压。不过我要提醒一句如果用了国产替代芯片个别型号的定时器时钟树和Flash等待周期有细微差别建议先跑一遍例程确认无误再上项目。车体结构我选的是常见的4轮小车底盘前轮由舵机控制转向后轮由直流减速电机驱动。这个结构的优点是把“转向”和“驱动”解耦了非常适合做自动避障——你想让车转多大角度直接控制舵机就行不用像差速小车那样去协调两个轮子的速度差。舵机我用的是SG90 9g舵机扭矩对这个轻量车体足够如果车体更重或者要在粗糙地面跑建议换成MG996R或类似的大扭矩舵机。电机是整套系统的核心执行器我选的是带霍尔编码器的N20微型减速电机常见减速比有1:30、1:50、1:90等几种。对于小车的正常行驶速度来说1:30的减速比太慢1:90又偏快且扭矩大但转速低实测下来1:50是比较平衡的选择。串联在电机尾部的霍尔编码器一般有13线/圈也就是电机轴转一圈输出13个脉冲经过减速器后输出轴转一圈对应的编码器脉冲数是13乘以减速比也就是650个脉冲。这个参数后面换算速度时要用到。电机驱动用的TB6612FNG模块相比老式L298N它的导通压降小、发热低而且PWM频率可以做得比较高配合10kHz到20kHz的PWM信号电机运行时的电流声会小很多。L298N不是不能用但它的功耗和发热会让你在调试时多出很多不必要的麻烦。传感器选用HC-SR04超声波模块这是最容易买到、也最容易上手的测距方案。它测距范围大概2cm到400cm分辨率在3mm左右对小车的避障需求来说完全够用。要注意的是HC-SR04对被测物体的形状和角度比较敏感测斜面和圆柱体时容易有较大的测量误差这个在后面滤波环节会重点处理。1.2 软件架构与控制流硬件搭起来之后软件如果还停留在“一个while大循环里顺序执行所有功能”的思路等你把超声波测距、舵机扫描、PID计算、编码器读取全部塞进去就会发现超声波等待回波的时候电机控制就被卡住了舵机在转的时候编码器数据又不能及时读取。整个系统会变得又慢又卡。所以我的软件架构是“主循环 中断”的协作模型。跑得快的、要求实时性高的任务放在中断里比如编码器计数、PID周期计算、超声波回波时间捕获跑得慢的、对时序不太敏感的放在主循环里比如舵机角度切换、避障逻辑判断、串口打印调试信息。这样既保证了控制周期的确定性又简化了主循环的复杂度。整个控制流大概是这样的主循环里规划当前目标速度比如前进、后退、停止、转弯定时器中断按照固定周期比如5ms或10ms触发一次PID计算PID计算时读取当前的编码器累计值算出实际速度和目标速度的误差通过PID算法输出PWM占空比调整超声波模块在主循环或低速轮询中完成一次测距经过滤波后更新前方障碍物距离如果距离小于安全阈值避障逻辑决定下一步动作停车、转向、绕行舵机根据避障逻辑输出目标角度由另外一个定时器PWM通道输出控制信号。这样各个任务各司其职系统跑起来非常流畅不会有“测距时电机停下来”这种尴尬情况发生。2. 让小车“看见”和“感知”超声波测距与数据滤波2.1 HC-SR04测距原理与定时器捕获HC-SR04的测距原理其实很简单你给Trig引脚一个大于10微秒的高电平脉冲模块内部会自动发出8个40kHz的超声波脉冲然后把Echo引脚拉高。当超声波碰到障碍物返回并被接收端检测到Echo引脚变低。所以你只需要测量Echo引脚高电平持续的时间t就可以算出障碍物距离。距离 声速 × 时间 / 2为什么除以2因为超声波走的是来回从发射到接收实际路程是两倍距离。常温下声速约340m/s如果把t的单位用微秒距离用厘米换算下来就是距离(cm) 340(m/s) × t(μs) / 1000000 / 2 × 100 0.017 × t(μs)简化一下就是距离(cm) t(μs) / 58。这个“除以58”是超声波测距代码里最常见的写法很多初学者只记住了公式但不知道58的来源其实就是声速和单位换算的结果。那怎么测量Echo高电平持续时间呢有两种常见方案。第一种是轮询不断读取Echo引脚电平先等它变高再等它变低之间用微秒级延时或读取定时器的值来做时间差。这种方案代码简单但有一个致命问题——在等待Echo引脚变化时主循环整个被阻塞了如果测距距离3米回波时间长达17ms这段时间里小车完全失去控制。第二种方案就是用定时器输入捕获模式加外部中断这也是我推荐的做法把Echo引脚连接到定时器的输入捕获通道比如TIM2的CH1对应PA0同时开启该引脚的外部中断功能或直接使用定时器的输入捕获中断当检测到Echo上升沿时记录当前定时器计数值并配置为捕获下降沿当检测到Echo下降沿时记录当前定时器计数值两个计数值的差就是高电平持续时间的计数值再根据定时器时钟频率换算出实际时间。用HAL库或者标准库配置起来都不算复杂关键是理解输入捕获的沿触发切换逻辑。把测距放到中断里主循环该干嘛干嘛这个体验和轮询方案完全是两个世界。2.2 超声波滤波为什么必须滤波怎么过滤波超声波模块有一个让所有人头疼的问题测量值不稳定。同一个距离连续测20次结果可能上下浮动1cm到2cm如果遇到倾斜障碍物、墙角反射、或者旁边有另一个超声波模块在干扰偶尔还会冒出一个完全离谱的值比如明明前方30cm突然测出2.5m。这种异常值如果直接送进避障算法小车会在没有障碍物的地方突然急刹或者在真有障碍物的地方一头撞上去。所以滤波这步不能省。我实际测试下来组合使用“中值滤波 滑动平均”最可靠处理逻辑分两级第一级是剔除异常值。连续采集若干次我取了5次对这5个数据排序取中间值作为本次测距的有效值。中值滤波对“突然冒出来的野值”有奇效因为不管是偏大还是偏小的异常排序后都会被挤到两边中间的数值基本能代表真实距离。采样次数太少比如3次野值还是有概率选中采样次数太多比如7次以上响应速度又变慢5次是个不错的折中。第二级是平滑波动。把中值滤波后的结果放入一个环形缓冲区然后对这N个数据求平均。滑动平均的作用是对抗超声波信号本身的随机噪声让测距曲线变得平滑后续避障逻辑不容易因为距离值抖动而反复触发转向。缓冲区长度我设的是10对应大约500ms的平滑窗口实测在1m到3m范围内曲线很稳定。这里我踩过一个坑刚开始为了“响应快”我把两次测距的间隔压得很短几乎连续触发超声波。结果超声波模块自带的换能器还没完全停止振荡测量值反而变得更乱。后来查了资料发现HC-SR04两次触发间隔建议大于60ms实际我用了80ms周期这样既保证数据更新率也不至于让模块“过劳”。3. 编码器测速定时器编码器模式与中断计数3.1 霍尔编码器的AB相原理要让PID控制电机转速第一步是“测准”。速度从哪里来靠编码器。N20电机尾部那个黑色的小方块里集成了一个霍尔传感器和一个小磁环通过磁通变化输出脉冲信号。我买的是AB相双输出的型号也就是输出两路相位差为90度的方波信号——A相和B相。这两路信号的关系很有意思电机正转时A相上升沿对应的B相电平是确定的比如高电机反转时A相上升沿对应的B相电平正好反过来低。所以编码器不仅能测速度还能通过判断AB相的相位关系知道电机是正转还是反转。这就是所谓的“正交解码”。更进一步在一个完整的AB相周期内其实有4个跳变沿A上升、B上升、A下降、B下降。如果我们每个跳变沿都计数一次一圈的脉冲数就翻了4倍称为“4倍频”模式。比如13线编码器经过1:50减速后输出轴每转650个脉冲4倍频后就是2600个计数分辨率高了很多低速测速也更准。顺带说一句如果你的电机编码器只有单相输出只有一路信号那只能测速度、无法判断正反转。做自动避障和位置控制时正反转判断非常重要所以买电机时尽量选AB相双输出的型号。3.2 STM32定时器编码器模式配置STM32F103C8T6的一个非常方便的特性是它的定时器可以直接工作在“编码器接口模式”下硬件自动处理AB相的计数逻辑完全不需要我们自己在中断里手动判断正反转。我用的N20电机编码器接到TIM2的CH1和CH2PA0和PA1设置为编码器模式、4倍频计数在CubeMX里只需要把这两个引脚配置为“Encoder Mode”然后在定时器配置里选好计数模式和倍频系数即可。如果你习惯用标准库或寄存器核心配置思路是这样的把PA0、PA1配置为浮空输入编码器信号不用加上拉霍尔输出本身是推挽的复用功能开启TIM2TIM2配置为编码器模式计数方向由AB相信号自动决定开启TIM2的更新中断可选用于周期读取计数值并清零。这里有个细节编码器模式下定时器的计数上限要设置正确。TIM2是16位定时器最大计数值65535。4倍频模式下输出轴每转一圈2600个计数65535大概够转25圈对小车测速来说是足够的。但如果你的电机减速比更大、编码器响应更高或者需要长时间累计距离就要考虑溢出问题或者在软件里处理计数溢出的情况。3.3 速度换算公式与单位陷阱假设我们设置一个10ms的定时器中断每次中断读取一次TIM2的计数器值这个值就是过去10ms内编码器产生的脉冲数。然后问题来了怎么把“脉冲数”变成“cm/s”完整换算链路是这样的速度(cm/s) 脉冲计数 / 4 / 编码器线数(13) / 减速比(50) × 轮子周长(cm) × (1000 / 定时器周期ms)拆开解释一下脉冲计数是编码器接口在4倍频下统计的数值所以先除以4得到真实的编码器物理脉冲数再除以13得到电机轴转动圈数再除以50得到轮子输出轴转动圈数乘以轮子周长就是这段采样时间内轮子走过的距离最后除以采样时间秒就得到速度。这里面最容易搞错的是单位——定时器周期是10ms换算成秒是0.01所以乘的是100不是1000。以我的小车为例轮子直径65mm周长就是约20.42cm。如果10ms内读到130个脉冲计数那么速度就是130 / 4 / 13 / 50 × 20.42 × 100 10.21 cm/s。把这段公式写进代码然后用串口实时打印出来和手动推车对比一下就能确认你的换算系数对不对。强烈建议在上PID之前先做这一步验证——很多小车跑不直、发抖根源其实不是PID没调好而是速度反馈值根本就是错的。3.4 中断设计优先级分配与控制周期编码器测速和PID计算都依赖定时器中断。我的做法是用TIM3产生一个10ms的周期性中断在这个中断里完成读取TIM2计数器值清零或者软件清零换算成当前实际速度执行一次PID计算把计算出的PWM占空比写入TIM4的CCR寄存器。这个10ms周期的选择有讲究。太快比如1ms中断太频繁主循环和超声波测距会被大量抢占而且PID微分项对噪声特别敏感容易放大抖动太慢比如50ms以上系统的响应速度跟不上小车在遇到障碍物时会有明显的迟滞感。10ms对这个小车来说是比较均衡的选择控制频率100Hz够用且稳定。中断优先级方面我用NVIC把定时器优先级设得比超声波的外部中断优先级高因为电机控制是实时性要求最高的任务不能被超声波回波捕获打断。而超声波回波捕获只记录几个寄存器值即使被其他中断打断一下也不影响最终计算结果。这个优先级分配原则在后续添加更多外设时同样适用给控制回路最高优先级给数据采集稍低优先级给慢速的串口打印和逻辑处理最低优先级。4. 让电机听话PID控制算法与参数整定4.1 位置式PID和增量式PID怎么选PID这名字大家都听过但真正写代码的时候很多人卡在了“位置式”和“增量式”的选择上。简单说位置式PID的输出是“目标位置的绝对修正量”公式为u(k) Kp × e(k) Ki × Σe(i) Kd × (e(k) - e(k-1))这个“累加过去所有误差”的积分项有一个麻烦如果程序出问题或者执行器被卡住误差积分会不断累积输出可能冲到非常高的值恢复时又容易超调。而且它输出的是绝对值直接作为PWM占空比的话调试时必须特别小心积分限幅。增量式PID的输出是“当前控制量的增量”Δu(k) Kp × (e(k) - e(k-1)) Ki × e(k) Kd × (e(k) - 2e(k-1) e(k-2))实际输出 u(k) u(k-1) Δu(k)。因为每次只加一个增量即使算错了也不会瞬间飞出太多天然带有一定“惯性”更适合电流环、速度环这种对输出平滑度有要求的场景。我做速度环就用增量式PID。除了安全增量式还有一个好处不需要对积分项做复杂的限幅处理只要整体输出做一下限幅即可代码写起来清爽很多。4.2 增量式PID的C语言实现下面是我实际在项目里用的一段速度环代码注释尽量写清楚typedef struct { float target; // 目标速度cm/s float actual; // 实际速度cm/s float err; // 当前误差 float err_last; // 上一次误差 float err_prev; // 上上次误差 float kp; float ki; float kd; float output; // P结 float output_last; // 上一次输出 float output_max; // 输出限幅 } pid_inc_t; float pid_inc_calc(pid_inc_t *pid, float target, float actual) { pid-target target; pid-actual actual; pid-err target - actual; // 增量式计算 float increment pid-kp * (pid-err - pid-err_last) pid-ki * pid-err pid-kd * (pid-err - 2 * pid-err_last pid-err_prev); // 限幅 if (increment pid-output_max) increment pid-output_max; else if (increment -pid-output_max) increment -pid-output_max; pid-output increment; // 输出限幅 if (pid-output pid-output_max) pid-output pid-output_max; else if (pid-output -pid-output_max) pid-output -pid-output_max; // 循环更新误差 pid-err_prev pid-err_last; pid-err_last pid-err; pid-output_last pid-output; return pid-output; }在定时器中断里每一轮调用这个函数PID输出的结果就直接作为PWM比较值。注意正负号的处理如果输出为正电机正转输出为负电机反转。实际处理时我是把输出映射到TB6612的两个PWM引脚上——正转时IN1给高电平、IN2给低电平PWM引脚输出方波反转时反过来。还有一个容易忽略的地方电机的PWM频率要足够高否则电机转动会一顿一顿的。我用的是TIM4的PWM输出频率设在20kHzTB6612在这个频率下工作很稳定电机的电磁噪声也小很多。如果你发现电机有明显的“滋滋”声或者转动不顺大概率是PWM频率太低。4.3 PID参数整定从理论到实操参数整定是PID环节最磨人的部分网上公式不少真正好用的还是要靠手感。我分享一下我的整定流程实测有效第一步先把I和D都置为0只保留P。给一个较小的目标速度比如20cm/s观察小车实际速度响应。如果P太小电机拖拖拉拉达不到目标速度如果P太大速度会在目标值附近明显震荡甚至能听到电机一阵一阵的嗡鸣声。找到一个P值让速度反应灵敏但又不至于持续震荡。这个P大概是我最终P值的60%~70%。第二步在P的基础上加I。I的作用是消除稳态误差也就是“永远差那么一点点”的余差。把I从小到大慢慢加观察小车在匀速行驶时实际速度是否贴合目标值。I太大会导致系统低频率振荡表现出来就是小车速度忽快忽慢甚至低速时车辆“喘振”。I合适时速度基本贴住目标值误差在±1cm/s以内。第三步加D。D的作用是抑制超调如果小车从静止加速到目标速度时冲过头再回落说明需要加点D来“踩刹车”。但是增量式PID加D后对测量噪声非常敏感编码器信号抖动大或者滤波没做好的话D加多了反而会让输出变得毛糙。我的经验是D宁小勿大只要有轻微抑制超调的效果就够了D值占最终输出里的比例甚至不到10%。整定的过程中强烈建议用串口把目标速度、实际速度、PID输出这三个值实时打印出来用串口绘图工具比如VOFA、匿名上位机等看曲线。有了曲线图你才能真正看到系统是欠阻尼还是过阻尼只靠眼看小车跑很难判断问题在哪。5. 舵机转向与自动避障策略5.1 舵机PWM控制与角度映射舵机控制其实是整个项目里最简单、但也最容易出细节问题的地方。SG90舵机的工作周期是20ms也就是50Hz频率。在这个周期内PWM高电平持续时间决定了舵机转到的角度0.5ms - 0度1.0ms - 45度1.5ms - 90度中位2.0ms - 135度2.5ms - 180度所以在STM32里我把TIM4的另一个通道配置为PWM输出周期设成20ms然后通过改变比较寄存器的值来调整占空比。比如ARR自动重装载值设为2000则一个计数单位是10us90度对应的比较值就是150。有一点要特别注意不同品牌甚至不同批次的舵机同样的占空比对应的角度可能略有差异。而且机械结构安装时不可能完全居中——你发一个“理论上90度”的PWM装到小车上前轮可能还是歪的。所以舵机控制一定需要“零点校准”上电后手动把前轮拨正记下此时舵机的PWM值以这个值作为逻辑中位所有转向角度都基于这个中位来偏移。5.2 避障逻辑从简单到实用的分级策略避障策略我采用的方案是“单超声波 舵机摇头扫描”模式。把舵机安装时固定在车头超声波模块装在舵机臂上这样可以通过舵机转动让超声波扫描不同方向。整个工作流程是舵机转向左前方比如-60度测距舵机转向正前方测距舵机转向右前方比如60度测距根据三个方向的距离数据决定小车的下一步动作。如果三个方向都比较远都大于安全距离那就直行如果正前方有障碍物但两侧通畅就向通畅的一侧转向如果三个方向都有障碍物就原地后退或掉头。这里有一个关键设计点安全距离并不是一个固定值。车速快的时候制动距离长安全距离要放大车速慢的时候安全距离可以缩小。所以我根据当前目标速度动态调整安全距离速度大时阈值自动变远速度小时阈值变近。这样小车在高速行驶时的反应更保守低速时则更激进整体动作更自然。还有一点值得说单个超声波只能测一个方向舵机扫描三个方向需要时间如果车速太快还没扫描完就已经撞上了。所以执行避障动作时我会先把车速降下来速度规划下一节会讲再进行角度扫描等确认安全后再加速恢复行驶。这个“减速再观察”的行为逻辑其实参考了人类驾驶中的操作习惯小车上用起来效果也很不错。6. 速度规划与位置控制让小车“聪明”地跑6.1 梯形加减速与防抖如果你直接把目标速度设成30cm/s然后让PID去追会发生什么电机瞬间加速车轮打滑车身猛地前冲位置控制时还会产生严重超调。所以我们需要“速度规划”也就是把阶跃的目标速度变成一条平滑变化的曲线。最常用的就是梯形加减速曲线。简单说就是把速度变化过程分成三个阶段匀加速段、匀速段、匀减速段。在加速段每过一个控制周期目标速度增加一个固定增量到达设定速度后保持匀速接近目标位置时提前开始减速。这个“固定增量”就是加速度取值可以根据电机的扭矩和车体的重量来调节。我的小车加速度设的是20 cm/s²也就是从0到30cm/s需要1.5秒起步顺滑不蹿车。实现方式极其简单不需要复杂的运动规划库在控制周期中断里加一个斜坡函数就行if (target_speed current_cmd_speed) { current_cmd_speed accel_step; if (current_cmd_speed target_speed) current_cmd_speed target_speed; } else if (target_speed current_cmd_speed) { current_cmd_speed - accel_step; if (current_cmd_speed target_speed) current_cmd_speed target_speed; }把PID的目标值改成current_cmd_speed斜坡后的值而不是直接给target_speed。这样速度变化是渐进的避障时的“减速再观察”动作也变得平滑不会出现急刹车。6.2 位置环怎么让小车停到指定距离位置控制是速度控制的上层。思路是这样位置环的输出是“目标速度”速度环根据这个目标速度去控制电机。也就是说位置环吃的是“当前距离目标还有多远”的误差吐出来的是“应该以多快的速度开过去”速度环接着去执行。这就是串级控制结构。我在代码里用一个简单的比例控制P实现位置环float dist_error target_distance - current_distance; float target_speed pos_kp * dist_error; if (target_speed max_speed) target_speed max_speed; if (target_speed -max_speed) target_speed -max_speed;距离越远目标速度越大距离越近目标速度越小快到了的时候速度已经接近0几乎不会超调。current_distance怎么来利用编码器累计即可——把每次测速周期算出的速度乘以控制周期时间累加起来就是车辆从启动时刻开始走过的路程。配合方向判断就能知道当前位置了。位置控制我遇到的最大问题倒不是算法而是机械上的滑行误差。在小车快速行驶时松开PWM输出后车还会因为惯性滑行一小段距离导致最终停车位置超出目标。解决办法是位置环不等到误差为0才停车而是在误差小于某个阈值时就主动执行小幅反转制动让车有一个“点刹”的效果。阈值和反转强度的组合关系需要根据实际车重和电机特性调几次但方向就是靠这个思路解决惯性滑行。6.3 位置控制实测让小车走到指定距离我做了个简单实验来验证位置控制让小车从起点出发直线行驶到50cm处。实测数据大概是这个目标距离(cm)实际停止距离(cm)误差(cm)3029.60.45050.10.18079.50.5100100.30.3在没有专门做精细校准的情况下误差能控制在1cm以内对小车的应用场景来说相当不错了。这说明“位置环P控制 速度环PID 梯形加减速”这套组合是简单且有效的。如果误差偏大优先检查编码器的换算系数是否准确再看加减速的斜率是否合适最后才考虑位置环增益要不要调整。6.4 定时器PWM配置与电机驱动细节再补充一下定时器PWM配置的细节。STM32F103C8T6的TIM4是一个16位通用定时器它有4个独立通道我用CH1驱动电机的PWMCH2驱动舵机。配置时两个通道共用ARR值是不行的因为电机PWM需要20kHz舵机需要50Hz这两个频率差的太远所以我实际是把它们分开配置给电机PWM开一个定时器比如TIM4给舵机PWM开另一个定时器比如TIM3。STM32F103C8T6有TIM1、TIM2、TIM3、TIM4完全够用TIM2编码器接口模式接电机编码器TIM3PWM输出频率20kHz驱动电机TIM4PWM输出频率50Hz驱动舵机TIM1作为周期中断源产生10ms控制周期也可以用TIM3的更新事件但要避免彼此冲突。电机驱动的接线我也列一份方便你照着接模块引脚接STM32TB6612PWMAPA6 (TIM3_CH1)TB6612AIN1PB12TB6612AIN2PB13TB6612STBY3.3V编码器A相电机编码器APA0 (TIM2_CH1)编码器B相电机编码器BPA1 (TIM2_CH2)SG90舵机信号舵机PWMPA7 (TIM4_CH2根据实际选择)HC-SR04 Trig超声波触发PB0HC-SR04 Echo超声波回波PB1 (输入捕获通道)这里的引脚分配不是唯一的你可以根据自己的板子和扩展模块灵活调整重要的是理解每一个外设所需要的定时器资源以及它们之间不要发生引脚冲突。7. 常见问题与排查技巧实录7.1 编码器读数不为0 / 读数乱跳这个问题很经典。上电后什么都不做串口打印编码器计数发现数值一直在乱变或者电机没转但计数在增加。排查思路先检查编码器电源。编码器需要5V供电但信号输出可能是5V电平接到STM32的PA0/PA1时需要确认是否兼容3.3V输入。STM32F103的IO口是5V容忍的所以直接接问题不大但如果你用的是国产替代型号或者其它板子建议加限流电阻或用电平转换电路保险一点。再检查接线是否松动特别是编码器AB相的杜邦线。N20电机的编码器信号频率不算高但AB相线如果和电机电源线靠在一起电机转动时会产生感应噪声导致计数乱跳。解决方法是让信号线和电源线分开走线必要时用双绞线或屏蔽线传输编码器信号。如果排除硬件问题检查定时器编码器模式的极性配置是否正确。有些编码器输出的空占比是50%但相位方向可能和预期相反通过调整定时器计数方向即可修正。7.2 电机抖动但不动 / 输出占空比上不去如果PID输出已经挺大了但电机还是慢吞吞或者一直抖先别急着调参按这个顺序排查电机驱动模块的STBY脚是否拉高了TB6612的STBY如果不接高电平整个驱动芯片处于待机状态电机当然不动。这个是最低级的错误但也是最常见的。检查PWM引脚是否复用正确。很多人用CubeMX生成了代码但忘了在MX_GPIO_Init里把PWM引脚模式改对导致输出始终是低电平或高电平电机只有一个方向的动作。确认PWM频率。如果频率太低比如1kHz以下电机会有明显的顿挫感和抖动看起来就像“想转又转不动”。把PWM频率提到15kHz~20kHz这个问题基本消失。用手转动电机轴观察编码器计数是否变化。如果计数不变说明编码器未工作或接线错误即使PID输出正确闭环也是失效的——因为反馈量永远是0。7.3 超声波测距偶尔跳变超声波模块偶尔冒出的野值光靠中值滤波能压住大部分但不能完全避免。我再补充一个代码层面的“时效性检查”每次测距结果出来后和上一次的有效值比一下如果差距超过一个合理的物理范围比如25cm对应20ms内超声波最多变化0.68cm远小于25cm就判定这次测量无效直接丢弃继续用上一次的有效值。当然这个阈值要大于正常行驶时障碍物距离变化的物理上限否则会把正常测量误杀。这个“一阶限幅”配合中值滤波和滑动平均基本能彻底解决野值问题。7.4 舵机抖动 / 转向不线性舵机抖动的常见原因有三个电源电压不足、PWM信号有毛刺、机械结构干涉。SG90在工作时瞬间电流能达到几百毫安如果和电机共用同一个5V电源电机启动瞬间会把电压拉低舵机就会抽搐。我的方案是舵机独立供电或者至少在电源端并联一个大电容470uF以上做缓冲。PWM信号线尽量缩短避免和电机电源线并行必要时在舵机信号线上串一个100欧姆电阻效果很明显。7.5 整车跑不直“跑不直”可能有两种情况。第一种是硬件问题左右轮子直径不一致、车架不对称、前轮定位偏差这些都会导致车往一边偏。第二种是控制问题两个电机的特性不一致同样的PWM占空比下转速不同。解决思路是别让两个电机开环跑都用上编码器PID闭环然后在代码里给每个电机一个独立的PID实例再对其中一个电机增加一个速度偏置校准值让两轮空转时转速尽量一致。这一步做完直线行驶的稳定性会有质的提升。8. 调试工具与效率技巧调试这个项目的时候有一件事让我特别感慨如果没有好的调试手段光靠眼睛看车跑调PID就像闭着眼睛开车。我分享一下自己实际用下来的高效调试组合。第一是串口可视化。我写了一段简单的printf重定向代码把目标速度、实际速度、PID输出、超声波距离、舵机角度等关键变量用帧格式输出到串口然后配合PC端的VOFA软件实时绘图。在电脑上看着曲线P、I、D每个参数的影响一目了然。调参效率至少翻三倍。第二是状态机日志。我把小车的运行状态停止、直行、左转、右转、后退用枚举值表示每次切换时通过串口打印当前状态。这样等小车跑完一趟回看日志就能知道它在哪里做了什么样的决策避障逻辑有没有误判。这个调试方法在排查“为什么小车在这里突然停住”的时候特别好用。第三是在代码里加一个“调试模式”开关。平时正常跑打开开关后可以通过按键切换不同的测试项目只测电机开环、只测速度环、只测超声波、只测舵机。这样每个环节独立验证问题定位非常快不用每次都在整车环境下猜是哪里出的问题。9. 最终调试验收整车跑起来把上面所有模块整合到一起之后我做了一个完整的验收测试在一个约2米×3米的空地上随机放置几个纸箱作为障碍物开启小车的自动避障模式观察它能否自主通过整个区域。测试结果整体是让人满意的小车在距离障碍物约30cm开始减速舵机摇头扫描后判断可通行方向转向绕行通过后在直线上自动恢复高速行驶。全程基本没有碰撞偶尔在遇到连续密集障碍物时会有短暂的“犹豫”但这是因为单超声波扫描需要时间属于硬件方案的天然限制不是代码问题。这时候回头想想STM32F103C8T6这套方案虽然用的都是最基础的外设和算法但把它们组合起来后小车的智能程度已经能让人觉得“有点东西”了。尤其是PID闭环那一环调顺之后小车直线行驶那种稳定的感觉和开环状态下乱跑乱偏完全是两种体验。项目做完你对“中断”“定时器”“PWM”“闭环控制”这些概念的理解会远超单纯看芯片手册所能得到的。我还记得调完PID后第一次让小车稳稳跑完一段直线然后在障碍物前自动减速转弯的那一刻真的特别有成就感。说实话做这个项目的过程中踩的坑不少但每一个坑都对应着对单片机底层机制的一次更深理解。如果你自己做的时候卡住了别急着怀疑电路是不是烧了——先打印数据再分析波形问题总比你想的要简单。
返回列表