拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32循迹避障小车完整设计:从传感器选型到PID调参与硬件避坑

简介一份完整的STM32智能循迹避障小车课程设计报告面向嵌入式初学者、电子竞赛参赛者及单片机课程设计学生解决智能小车循迹、避障、PWM调速等核心设计问题。报告以STM32F103ZE为主控芯片详细阐述红外对管循迹检测、超声波避障、H型可逆PWM变换器调速及MDK(Keil)编程实现内容涵盖方案论证、硬件电路、仿真设计和主程序代码结构完整可直接参考。资源共1个PDF文档约819KB内容精炼便于阅读适合毕设或竞赛方案借鉴。目前已有18146人学习下载这份从原理到代码的设计报告可帮助读者快速掌握智能小车从硬件搭建到软件调试的完整思路。 很多做嵌入式课设或者电子设计竞赛的同学第一次接触“基于STM32的智能循迹避障小车”这个题目时都会有个错觉这不就是一辆小车加几个传感器让它沿着线走、遇到障碍停一下嘛听起来好像没什么难度。但等真正把元器件焊到板上、把代码烧进去、把车放到赛道上试跑的时候各种问题就全冒出来了——车为什么不走直线、为什么一到强光下就乱拐、为什么明明检测到障碍了却还是撞上去、为什么电机一转单片机就重启。这些问题设计报告里基本不会写却是决定小车能不能稳定跑完全程的关键。这篇内容我打算把一套真正能落地、能跑稳定的方案完整拆开讲从整体架构和器件选型开始到循迹和避障两大核心模块的原理与代码逻辑再到调参过程中的经验和坑。不管你是准备交课程设计报告还是想拿这套东西去打比赛都应该能从里面找到直接能用的东西。1. 一套能稳定跑完全程的小车器件到底该怎么选先说结论STM32F103C8T6这颗芯片完全够用不用再往上加预算换F4系列。很多初学者觉得F4主频高、性能强用起来肯定更稳但实际上循迹避障小车这种任务核心难点根本不在算力而在传感器信号的处理逻辑和控制策略。F103的72MHz主频跑循迹PID和超声波避障逻辑绰绰有余而且C8T6的封装小焊接和布线都方便网上参考资料也最多出了问题好排查。电机驱动这块最常见的搭配是TB6612FNG或者L298N。我自己的实际体验是TB6612比L298N好用得多。L298N压降大、发热严重两路电机全速跑一会儿散热片就烫得不敢摸而且它的逻辑电平兼容性还容易出问题。TB6612的MOSFET架构压降小体积也小直接让STM32的PWM引脚控制不需要额外的电平转换电路。唯一要注意的是TB6612的供电电压范围是2.7V到5.5V千万别直接给它接12V。传感器选型也是有讲究的。循迹模块市面上一堆什么单路、双路、四路、八路红外都有。如果要跑比较复杂的赛道十字路口、直角弯、断线我建议至少上四路灰度传感器如果只是基础直线加简单弯道三路也够。但我个人不太推荐那种特别便宜的单个红外对管模块它的检测距离和环境光抗干扰能力都很差太阳底下跑一圈阈值全漂了。循迹这块我后面会给出更具体的选型和布局建议。避障这块市面上最常用的是HC-SR04超声波模块。它的测距范围大概2cm到400cm精度在3mm左右对小车避障场景来说绰绰有余。要注意的是HC-SR04是5V供电的但它的TRIG和ECHO引脚返回的电平也是5VSTM32的GPIO是5V容忍的直接接没问题但保险起见还是建议用电阻分压把ECHO的电平降到3.3V再接进PA0之类的引脚防止某些体质比较弱的芯片出现引脚损伤。最后是电源方案。这是整个小车最容易翻车的地方必须单独说。如果用两个18650锂电池串联供电7.4V千万别直接拿这个电压给STM32供电。最稳妥的做法是7.4V进电机驱动板的VM引脚给电机供电同时从7.4V串联一个AMS1117-3.3稳压模块给STM32供电。电机和单片机必须共地否则PWM信号的电平参考不一致电机跑起来会各种乱抖。提示电机的启动电流峰值很大两个电机同时全速启动时瞬间电流可能冲到2A以上。如果单片机和电机共用同一个稳压源电压会被瞬间拉低轻则复位重则程序跑飞。所以“电机供电”和“逻辑供电”分区是个非常好的习惯。2. 循迹模块不是“检测到黑线就行”灰度值的二值化处理才是关键循迹的原理听起来很简单红外发射管发射红外光红外接收管根据反射光强度输出不同的电平或模拟电压黑线吸光反射弱白底反射强以此判断当前传感器是在线上还是在线上以外。但实际跑起来问题就出在这个“判断”上。市面上很多便宜的循迹模块输出的直接是数字信号0或1灵敏度电位器调到一个固定位置后它的判断阈值就被固定死了。问题是不同环境下环境光的强度不一样电池电压的变化也会影响红外发射管的发射强度所以这台车上调好的阈值换块电池、换个场地可能就不准了。这也是我建议用带模拟输出的灰度传感器比如五路灰度模块的原因。它输出的是一路模拟电压值你可以通过STM32的ADC实时采集然后在代码里做动态二值化。简单说就是每次上电的时候先做一次环境采样记录下当前环境下“白底”和“黑线”各自对应的ADC值然后取两者的中间值作为动态阈值。这样即使场地光照变了也能保证判断的准确度不需要每次跑之前都手动拧电位器。循迹传感器的布局也是有讲究的。如果是三路传感器最理想的状态是让中间那一路始终压着黑线走左右两路作为纠偏参考。如果是五路左右两侧还可以多出两个“急弯检测”位提前判断赛道是往左拐还是往右拐响应速度比三路快一个档次。我做这套系统时用的循迹逻辑是最经典的“查表法 PID纠偏”五路传感器从左到右编号1到5把它们的数字值组合成一个5位的二进制数然后在代码里维护一张表把这个组合值映射到“当前偏差”。// 五路循迹传感器状态映射表1为压线0为离线 // 状态码计算方式: (S1 4) | (S2 3) | (S3 2) | (S4 1) | S5 // 偏差定义: 0表示正中负数偏左正数偏右 const int track_map[32] { // 全空或全满的情况 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 单独压线 0, -3, -2, 0, -1, 0, 0, 0, 0, 1, 0, 2, 3, 0, 0, 0 };这张表看起来不复杂但手写两张表容易错位调试的时候很折磨。更省事的方法是在初始化阶段写一段自检程序把五个传感器分别用手挡一下把对应的状态码打印到串口然后对着状态码填表。这样能保证每个状态码映射到的偏差值跟实际位置是对应的。PID纠偏是让小车稳定走直线的核心。循迹场景下一般用PD控制就够了比例项负责给出转向力度微分项负责抑制摆动。I项在很多小车上反而容易引发振荡因为循迹是动态连续过程误差积分很容易过头导致小车在直线段“蛇形走位”。int last_error 0; float Kp 25.0f, Kd 8.0f; int pid_control(int current_error) { int delta_error current_error - last_error; int output (int)(Kp * current_error Kd * delta_error); // 限幅防止PWM值超界 if (output 500) output 500; if (output -500) output -500; last_error current_error; return output; }这个output参数最终会叠加到左右电机的PWM占空比上如果output为正说明车偏右了那就左轮提速、右轮减速output为负则相反。Kp和Kd的整定我建议先只给Kp把小车的速度放在一个比较低的值比如20%占空比看它的反应如果小车在直线段来回摆就加大Kd如果过弯时反应迟钝就适当加大Kp。这套参数调好后可以让小车在1.2m/s的速度下稳定走完一个标准十字赛道不会冲出轨道。3. 避障逻辑不能“检测到就躲”要设计一个简单的状态机超声波避障看起来比循迹还简单——测距如果太近就转向。但如果真的只写一个“距离小于阈值就转弯”小车的动作会非常僵硬走到障碍前停下来原地转个90度再往前走结果刚转完又检测到墙又停下来转……最后原地卡死。这种逻辑只能算“能躲”完全谈不上“智能”。真正可用的避障策略至少要是一个简单的状态机把整个运行过程拆成几个明确的阶段直线行驶状态超声波连续测距如果前方距离大于安全阈值一般30cm保持直行。接近障碍状态前方距离小于30cm时不急着立刻转弯先减速再根据超声波的读数判断障碍物的范围。决策转向状态减速后检测左侧和右侧的距离选择空旷的一侧作为转向方向。绕过恢复状态转向完成后重新进入直线行驶状态但此时需要结合循迹传感器判断是否已经绕过了障碍回到原来的轨迹上。这套状态机的思路本质上就是靠“延时 距离记忆”来模拟简单的记忆能力。我在实际写代码的时候用了几个全局变量记录转向方向和转向持续的时间保证小车不会在刚绕过障碍物的瞬间马上又判定“前方有障碍”。超声波测距本身也有不少细节要注意。HC-SR04的触发方式是给TRIG引脚一个10us以上的高电平模块内部会自动发送8个40kHz的超声波脉冲然后拉高ECHO引脚ECHO高电平持续的时间就是超声波从发射到接收的往返时间。距离cm 高电平时间us / 58。// 超声波测距函数单位cm float get_distance_cm(void) { uint32_t time_us 0; // 拉高TRIG引脚至少10us HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(15); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待ECHO变为高电平 while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); // 用定时器记录高电平持续时间 __HAL_TIM_SET_COUNTER(htim2, 0); __HAL_TIM_ENABLE(htim2); while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET) { if (__HAL_TIM_GET_COUNTER(htim2) 12000) // 超时保护约2m { __HAL_TIM_DISABLE(htim2); return 999; } } __HAL_TIM_DISABLE(htim2); time_us __HAL_TIM_GET_COUNTER(htim2); return (float)time_us / 58.0f; }这段代码里有几个非常关键的点第一直接用一个死循环while等待ECHO引脚跳变是可行的但一定要加超时保护。如果超声波模块没接好或者前方就是一片空旷的墙ECHO引脚可能一直保持低电平这时候程序就会卡死在while循环里小车的其他任务全部停摆。我加了一个2m的超时保护三秒之内测不到就返回一个很大的值当作前方没有障碍。第二利用定时器计数来测量高电平时间比用delay循环精度高得多。STM32的定时器在主频72MHz下一个计数周期大概是13.8ns而一个delay空循环的时间很难精确控制测出来的距离波动会很大。第三超声波模块的触发间隔不能太小。每次发射超声波后声波还有余振如果连续测量间隔太短会出现信号串扰测出来的距离忽大忽小。我的做法是每次测距之间至少间隔50ms实测下来数据稳定很多。避障状态机还有一层需要考虑的就是转向的幅度。很多初学者用的是一个非常粗暴的逻辑左轮正转、右轮反转原地转90度。这种转向方式在直线赛道上问题不大但在有循迹线的场地上就会出现问题——车转完之后循迹传感器已经彻底脱离黑线了这样整个系统就在循迹和避障两个模式之间打架。我当时想到的折中方案是当超声波前方距离小于30cm时先不脱离循迹线而是让车减速同时用左右轮差速的方式缓慢地往空旷的一侧偏离。也就是说避障时车是以一个较大半径的弧线绕过障碍物而不是原地直角转弯。这样等绕过障碍之后循迹传感器大概率还在线的附近可以比较平滑地切回循迹模式。4. 传感器供电和信号冲突最容易翻车的地方如果上面那些逻辑代码写得没问题但小车跑起来还是各种诡异那八成是硬件电路的问题。做嵌入式项目软件出bug通常还能通过调试日志查到硬件隐患有时候是查都查不着的。最常见的问题就是共地。STM32的PWM信号送到TB6612的PWMA/PWMB引脚如果STM32的GND和TB6612的GND没有连在一起PWM信号的电平参考点就不一致轻则电机转速忽快忽慢重则电机根本不动。直接在面包板上用一根杜邦线把两边GND连起来就能解决这几乎是我每次帮人调试必查的一个点。第二个坑是超声波模块和电机驱动同时使用时电源纹波的问题。HC-SR04对电源的纹波比较敏感电机的碳刷或者无刷电机的换相会引入很多高频噪声如果超声波模块的电源直接从电机电源那一路取测距数据会出现随机性的跳变。解决方案很简单给超声波模块单独用一个100uF电解电容和一个0.1uF陶瓷电容滤波靠近模块电源引脚放置。别小看这两个电容实测下来能明显减少数据的抖动。第三个坑是舵机与电机的电流冲突。如果避障方案用的是舵机云台带动超声波模块左右扫测这种方案在小车上也很常见舵机启动的时候会有一个比较大的电流冲击如果电源余量不足这个冲击会把STM32的供电电压拖低导致芯片复位。最典型的现象就是小车一转弯单片机和舵机同时重启OLED屏幕闪一下一切从头开始。做避障小车至少要保证电池放电能力在3A以上同时把舵机和电机的供电从逻辑供电分开。第四个坑相对隐蔽一些那就是GPIO引脚电平的冲突。循迹模块的信号输出如果模块的供电电压是5V输出高电平就是5V而STM32的引脚虽然大多是5V容忍的但是仍有部分引脚尤其是ADC输入引脚不是全范围容忍的直接接5V有一定风险。最好给所有传感器的信号输出都串一个1k欧姆电阻或者用两个电阻做分压把高电平拉到3.3V再进单片机。这个做法看起来很“保守”但对芯片的保护是非常实在的。5. 调试卷参的工程方法别靠玄学要有记录循迹小车这套系统代码写完只算完成了一半剩下的一半全在调参。很多同学调参是靠“感觉”Kp不对就调大点试试不行再调小点调了半天也调不明白最后干脆死磕一个看起来还行的数值发论文交课设。但真正有工程素养的调参方式是靠记录和单变量原则。我自己的习惯是先在串口上加一个调试接口把循迹传感器的原始ADC值、二值化后的状态码、PID计算出的output值、左右PWM值、超声波测距值全部通过串口调试助手或VOFA实时打印出来。这样小车在跑的时候你可以在电脑上实时看到它的每一项参数变化出问题的时候能快速定位是传感器问题、计算问题还是执行机构问题。单变量原则很重要。调参数的时候一次只改一个变量改完记录下来跑一圈记录结果再改下一个。不要Kp和Kd同时调否则你根本不知道是哪个参数让小车变稳了。我一般是这样操作的第一步固定速度在比较低的值PWM占空比25%左右把所有Kp、Kd清零。第二步只加大Kp让小车能沿着线走出S形但允许它小幅摆动。第三步在Kp的基础上加Kd用来抑制摆动。Kd的值一般是Kp的三分之一到二分之一所以我通常从Kp * 0.3这个值开始试。第四步确认直线稳定后逐步提高速度每提升一档速度就重复一遍第二步和第三步。第五步如果某一档速度下怎么调都稳不住说明速度已经超出这套机械结构的极限了不要硬扛降回上一档速度。避障的阈值参数同样需要记录。安全距离设得太大小车频繁转向根本没机会走直线设得太小刹车距离不够直接撞上障碍。这个值跟车速和轮胎的摩擦系数都有关没有一个通用的标准值。我的做法是先在电脑上模拟把超声波测距值和电机的PWM值同步记录分析一下从检测到障碍到小车完全停稳最短需要多少距离然后把这个距离留出20%的余量作为安全阈值。调参还有一个特别容易被忽视的因素电池电量。锂电池在满电和快没电的时候电压差能到0.5V以上这会直接导致电机PWM占空比相同的情况下实际转速不一样。如果你用一整个下午调好的参数第二天换了一块满电的电池再去跑可能就完全不是那回事了。所以我建议调参前先把电池充满并且在调参过程中定时测量电池电压如果电压掉了0.2V以上就换一块满电的电池再继续。竞赛中很多队伍在赛前发现车变“飘”了十有八九就是电池电量的问题。6. 循迹与避障的模式切换两个功能如何共处不少课程设计的要求是“既能循迹又能避障”这就涉及两个功能怎么切换的问题。最常见的方案是加一个拨码开关或者按键通过GPIO读取电平来切换模式。这个方案简单可靠适合交课设但不够“智能”。稍微进阶一点的方案是“循迹优先避障打断”的优先级机制循迹模式下超声波模块始终在工作但避障逻辑只有在距离小于安全阈值时才介入。一旦介入循迹PID的输出会被挂起避障转向逻辑接管控制权等障碍绕过去之后再恢复循迹逻辑。这个方案比较考验状态机的设计但只要把第三章那套状态机写顺了加一个“中断标志位”就能实现。这里有个很常见的误区想把避障做成外部中断直接在中断服务函数里操作电机。超声波测距本身耗时比较长一次测距从触发到收到回波最多能到20ms放到中断里很容易把其他更紧急的中断搞乱而且触发超声波的时间间隔不确定放在中断里逻辑会很难调。我建议把所有传感器数据采集都放到主循环里用一个定时器中断做10ms的时基主循环每次循环检查一下时基标志再决定是否采集数据和更新控制量。这种“对时时间片轮询”的结构在写代码的时候会稍微多一点工作量但好处是逻辑清爽后期调试效率高得多。尤其当你想给小车加显示功能用OLED显示实时距离和模式、加蜂鸣器提示、加蓝牙遥控的时候这种结构能让所有功能都有条不紊地协同不会出现“加一个功能另一个功能卡死”的情况。循迹和避障还有许多同学没考虑到的一个联动细节绕障之后的循迹恢复。如果你用的是“大半径弧线绕障”绕过障碍之后小车的位置可能已经偏移出了黑线循迹传感器读到的全都是白底偏差值反复跳动小车就会在线的边缘左右迷茫地摆动。这种情况我建议的做法是绕障结束后不要立刻切回循迹而是让小车继续直线走一小段距离比如50ms同时不断读取循迹传感器的状态一旦检测到有传感器压到黑线再切回循迹PID。这个“滞后切换”的小技巧实测能很大程度减少绕障后的震荡。调试过程中还有一些细节值得注意比如在小车上放一块OLED显示当前状态能让你在跑动过程中快速判断车处于哪个模式再比如给传感器加一个遮光罩防止强光直射红外接收管否则在窗边或者强光灯下循迹传感器很容易误判。还有万用表测量各路供电的电压值——电机转动瞬间电压是否跌落严重——这个习惯可以帮助你提前发现供电瓶颈而不是等到小车现场罢工才去排查。我在实际做这套系统的时候最大的感受是这种东西难不在知识点本身而在于把所有小问题全部叠加在一起以后整个系统能否稳定运行。STM32的代码逻辑其实都是固定的PID算法网上随便一搜就有超声波测距的例程也到处都是真正能拉开差距的是你有没有提前意识到供电要分区、传感器要滤波、阈值要动态校准、调参要记录数据。把这些工程细节处理好了小车自然跑得又直又稳处理不好哪怕代码逻辑写得再漂亮上了赛道照样会翻车。如果你正准备做类似的题目我建议拿到板子先别急着写代码先把硬件平台的每一路电源都测量一遍确认供电稳定、共地可靠之后再开始写驱动和逻辑你会发现后期调试顺利得多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门