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

STM32电赛H题自动驾驶小车完整方案:原理图、源码与PID调试全解析

简介本资源是2024年全国大学生电子设计竞赛H题‘自动驾驶小车系统’的高分参赛方案面向计算机、自动化、电子信息等专业学生专为课程设计、毕业设计及电赛备赛实战打造。项目经导师指导并获评审99分代码完整可直接编译运行配套原理图与详细注释零基础学习者亦能快速上手调试。压缩包共144个文件10.26MB含84个头文件.h定义硬件接口与模块功能、38个C源文件.c实现主控逻辑、传感器融合、电机驱动与路径识别等核心算法另有PDF原理图、Keil工程配置文件.uvprojx/.uvoptx、内存映射.map、启动脚本.s及系统配置.syscfg等关键开发资料。目前已有2163人下载学习提供从底层驱动如inv_mpu、dl_timer、dl_flashctl到应用层main.c的全栈实现结构清晰、模块解耦便于理解嵌入式实时控制系统的整体架构与工程组织方式。 2024年的电赛H题自动驾驶小车系统陆陆续续折腾了大半个月从原理图到源代码再到实车调试最后总算是拿到了一个满意的分数。这套东西做完之后我把原理图和源代码都重新整理了一遍发现网上虽然有很多零散的方案但真正能把“为什么这么设计”讲清楚的不多。这篇文章就把我这个项目从零到一整条链路完整拆开把里面的门道、参数、踩坑点都交代清楚给后面再做同类题目的朋友一个可以直接参考的底子。先说清楚这套东西是什么。整个系统以STM32为主控底盘是两个带编码器的直流减速电机配合TB6612驱动循迹部分用的是8路灰度传感器阵列控制算法上转向用了PD、速度用了PI整个程序的骨架是一个有限状态机用来管理起跑、循迹、路口处理、停车入库这些不同的任务阶段。这套方案能解决的问题很直接在电赛要求的黑色引导线赛道上让小车稳定地跑完全程能处理十字路口、弯道、停车区这些典型元素并且整个过程不飞线、不震荡、停车位置准。对于正在备赛电赛、做智能车毕设、或者刚接触嵌入式PID控制的朋友来说这篇文章的参考价值最大。1. 赛题拆解与系统设计思路1.1 H题到底在考什么电赛的题目看起来年年变但H题这一类自动行驶小车核心考核点其实非常集中。你去翻最近几年的题也能发现无论赛道形式怎么改它本质上就考三件事能不能沿着引导线稳定地走、能不能在关键位置做出正确的状态切换、能不能在规定区域精准停下来。很多队伍在备赛的时候把精力全花在“跑得快”上这是一个非常常见的误区。我实测下来的感受是电赛的评分逻辑不是竞速逻辑而是“完成任务度”逻辑。你的车跑得再快只要在某个路口判断失误冲出了赛道或者停车的时候越过了终点线该扣的分一点都不会少。反过来一台速度平平但每一步都走得非常稳的车反而能拿很高的分数。所以我在设计这套系统的时候第一原则就是可靠性优先。在这个前提下所有方案选型都围绕“怎么让车稳定地完成任务”来展开而不是“怎么让车跑得最快”。这一点我希望你在看后面的方案时能一直记着因为很多设计细节你单独看会觉得保守但放在稳定性这个目标下它就是合理的。1.2 为什么用灰度传感器而不是摄像头这是所有做这类小车的人面临的第一个选择也是第一个大坑。在2023年之前很多队伍喜欢上摄像头方案OpenMV也好、OV7670也好听起来很高级但实际放到比赛现场光线一变化颜色阈值一飘参数整个就废了而且摄像头的数据处理对单片机资源的占用非常惊人留给控制算法的算力就不够了。我的选择是8路灰度传感器阵列。理由很直接第一灰度传感器的原理是红外发射管发射光线接收管根据反射光强判断黑线还是白底这个信号是模拟比较器直接输出的数字电平单片机读起来就是一组0和1延时可以控制在微秒级实时性比摄像头好太多。第二它几乎不受可见光干扰因为红外发射管自己有主动光源对赛场灯光的依赖程度很低。第三代码逻辑简单出现问题时排查起来也快得多。主控我选的是STM32F103RCT6没有上F4系列也没上F103C8T6。C8T6的Flash只有64K代码稍微膨胀一点就装不下了而RCT6有256K的Flash和48K的RAM这个富余量在后期调代码的时候非常宝贵。F4确实性能更强但这套系统的运算量根本用不到F4F103系列在72MHz主频下完全扛得住而且大家手头的库函数代码、参考工程也最多遇到问题好搜好问。1.3 电机与驱动方案的确定电机这块我直接推荐装编码器的直流减速电机俗称“带码盘的电机”。为什么强调带编码器因为你要做精确的速度闭环就必须知道电机当前实际转了多少。没有编码器的开环方案电池电压一变、路面阻力一大速度就飘了PID没法闭环稳定性无从谈起。驱动芯片选的是TB6612FNG没有用经典的L298N。L298N的优点是便宜、皮实但它的导通压降太大了能达到2V以上意味着同样的电池电压真正送到电机上的电压要打不少折扣而且芯片发热严重。TB6612的导通压降只有0.5V左右效率高体积小集成度也高两个H桥刚好驱动两个电机对这类小车来说是性能更好的选择。整套系统做下来硬件的物料成本控制在200元以内不含电池和车架这在电赛硬件方案里算是性价比很高的了。2. 硬件设计从原理图到实物的关键细节2.1 电源系统整个系统的生命线电源设计是这套系统里最不该省心思的地方。我先说结论然后解释为什么。电池我用的是7.4V两节18650串联也就是标称电压7.4V的锂电池组。这个电压选择有讲究TB6612的供电范围和逻辑电平都兼容这个电压而单片机、传感器模块这些5V器件我通过降压模块从7.4V降下来供给。如果你用的是3.7V单节锂电池带两个电机在起步的瞬间电压会被拉得很低单片机和传感器很容易复位重启这是比赛现场最常见的“莫名奇妙的故障”之一。降压方案上我推荐用MP1584这类开关降压模块而不是AMS1117线性稳压。原因很简单线性稳压是把多余的电压直接以热的形式耗散掉7.4V降5V中间白白烧掉将近三分之一的能量而且电流一大的时候芯片烫得能煎鸡蛋。开关降压效率高发热小。实测下来MP1584模块在0.5A负载下温升很小完全能满足整车的供电需求。这里要特别强调一个很多新手容易踩的坑电机驱动必须单独供电或者至少要在电机电源和逻辑电源之间做充分的滤波隔离。最保险的做法是电池直接给TB6612的VM电机供电端供电单片机、传感器这些逻辑部分走降压后的5V两者只在电池正极处做共地连接其他地方不共用电源线。这样才能避免电机启动和刹车瞬间的电流冲击把单片机拉死机。2.2 电机驱动原理与散热细节TB6612内部实际上是两个独立的H桥电路。H桥这个名字你可以简单理解成四个开关管组成的“桥式”结构通过对这四个开关管开关状态的组合控制让电流可以正向流过电机、反向流过电机或者切断电流。正向导通时电机正转反向导通时电机反转快速来回切换就实现了电机的调速。PWM调速的本质就是控制一个周期内导通和关断的时间比例即占空比。实际接线的时候有几个细节必须注意。第一TB6612的逻辑电源VCC和电机电源VM是分开的VCC接3.3V或5V和单片机电平匹配VM接电池电压。第二GND必须和单片机共地否则控制信号是浮空的芯片工作完全乱套。第三A01、A02这两个输出端接左边的电机B01、B02接右边的电机两个电机的地不需要额外单独接TB6612内部已经处理了。关于续流和保护TB6612芯片内部已经集成了续流二极管所以外部不需要再加二极管了这一点和纯分立件搭的H桥不一样。但为了保险起见我还是在电机电源两端并了一个470uF的电解电容和一个小容量的陶瓷电容用来吸收电机换向时产生的尖峰电压。实测加上这些电容之后单片机复位的问题基本绝迹了。2.3 灰度传感器的布局差之毫厘谬以千里灰度传感器的布局是整个硬件设计里最容易被忽视、但对性能影响最大的环节。这套系统用的8路灰度模块每路的原理都是红外发射管持续发光红外接收管根据反射光强度产生不同电压这个电压再送去和一个可调阈值电压比较输出数字高电平或者低电平。一般默认规则是黑白反差分明的赛道传感器在白色地面输出高电平在黑线上方输出低电平。但这里有一个很容易踩的坑阈值比较器的翻转点不是自动的需要手动调节模块上的电位器也就是在“黑白交界”处调出一个清晰的翻转区间。如果阈值调得太高或者太低传感器就可能出现“白底误判成黑线”或者“黑线漏判成白底”的情况。安装布局上我的8个传感器排成一条直线间距在10mm到15mm之间总宽度大约能覆盖80mm以上。这个宽度怎么定关键是要覆盖住一条赛道黑线的完整宽度以及两侧的余量。电赛赛道的黑线宽度通常是20mm到25mm我这样的排布方式可以保证小车即使在弯道上有一定的横向偏移黑线也始终落在传感器的覆盖范围内。安装高度方面传感器离地高度控制在10mm到15mm太远了红外反射信号衰减严重很容易误判太近了又容易蹭到地面影响车速。2.4 原理图设计经验原理图我用的是嘉立创EDA画的免费且支持立创EDA专业版和标准版而且和嘉立创打板无缝衔接。对于电赛级别的项目不需要追求所谓的“专业感”关键是结构清晰、便于检查。整理一下这个项目原理图里最核心的几个模块连线给需要画板子的朋友一个参考单片机最小系统STM32F103RCT6 8MHz晶振 两个22pF负载电容 BOOT0下拉到GND NRST上拉复位电路 3.3V供电和去耦电容每个VDD引脚旁边放一个100nF。电源入口电池正极接串一个防反接二极管然后分两路一路进TB6612的VM一路进降压模块的输入。电机驱动接口PA0和PA1输出两路PWM到TB6612的PWMA和PWMBPC0到PC3作为AIN1、AIN2、BIN1、BIN2方向控制引脚直接连GPIO输出高低电平即可。灰度传感器接口8路传感器的数字输出分别接PB0到PB7每根信号线在单片机侧加上拉电阻到3.3V确保默认电平确定。这里有一个非常重要的原则画原理图的时候不要图省事把传感器接口做成固定焊死的连线一定要预留排针或者杜邦线接口。比赛现场环境千变万化你需要随时调整传感器的间距、左右位置如果焊死了就只能重新打板相当被动。3. 软件架构与源代码实现3.1 程序总体架构有限状态机整个程序我采用的是有限状态机的结构。为什么要用状态机因为小车的整个比赛过程不是单一动作而是一连串不同的任务阶段。起跑之前要等待触发信号起跑之后进入正常的循迹模式遇到路口要特殊处理最后看到终点区要开始减速停车。如果把这几个阶段的逻辑全部揉在主循环里平铺直叙地写代码很快就会乱成一锅粥而且调试的时候没法定位问题。状态机的思路是把每个阶段拆成一个独立的状态每个状态内部有自己独立的逻辑状态之间通过条件触发完成跳转。这套程序里我定义了这么几个核心状态初始化上电后等待按键按下同时完成传感器阈值检测和电机零位校准。等待起跑按键按下后发送“准备”提示延时0.5秒后进入循迹状态。循迹行驶这是核心状态持续读取灰度传感器、计算偏差、执行PD转向控制和速度PI控制。路口处理当检测到特定传感器模式的时候写入当前路口类型执行直行/转弯/停车等动作。停车完成全车断电或者指示灯提示比赛结束。主循环的伪代码大概是这样的while (1) { switch (current_state) { case STATE_INIT: // 按键检测、传感器校准 break; case STATE_WAIT_START: // 等待按键延时后切换 break; case STATE_DRIVING: // 读取传感器 // 计算偏差 // 转向PD控制 // 速度PI控制 // 路口状态检测 break; case STATE_INTERSECTION: // 路口处理逻辑 break; case STATE_STOP: // 停车、完成 break; } }这种结构的好处非常明显每个状态只管自己的事情逻辑边界清晰出问题的时候只要看当前状态在哪问题就在哪一段代码里。3.2 循迹算法加权平均法循迹的核心是“把位置误差算出来”。8路传感器输出的是一个8位二进制数每一位对应一个传感器0或者1表示当前传感器下面有黑线还是白底。但这个原始数据不能直接用因为你需要的是“黑线相对于车体中线的位置偏差”而不是“哪几个传感器看到了黑线”。我用的算法是加权平均法也叫重心法。具体思路是给每个传感器分配一个固定的位置权值处在最左边传感器权值为-4最右边为4中间依次递增然后把所有检测到黑线的传感器的权值加起来再除以检测到黑线的传感器个数得到一个从-4到4的连续偏差值。这里有一个细节值得说一下用公式sum(n*value)/sum(value)得到的是“重心位置”本质上是对黑线位置在传感器平面上的线性估计。这个估计在传感器间距均匀的情况下是相当准的偏差值可以直接用作PID控制器的误差输入。另外需要处理“全白”和“全黑”这两个特殊情况。全白说明车已经完全丢线了这时候位置偏差是无效的我会让程序进入“丢线处理”逻辑而不是继续盲目计算偏差。全黑则通常意味着车在十字路口或者斑马线需要走路口处理逻辑。3.3 PID控制为什么转向用PD、速度用PIPID是整个控制系统的灵魂这套系统的控制逻辑分两层转向环和速度环。转向环我采用的是PD控制。也就是只用了比例P和微分D没有积分I。原因很简单转向偏差的期望值永远是0车始终希望直直地沿着黑线走。比例项的作用是让车“看到偏差就反向打方向”偏差越大转向力度越大。但单纯用P会出现一个问题车总是会冲过头在走S弯的时候会左右震荡体现在赛道上就是蛇形走位、甩来甩去。这时候需要D项D项本质上是“偏差的变化率”它起到的是阻尼作用。当车开始快速朝偏差方向运动时D项产生的修正力会抑制这种快速变化让整个系统更稳定。速度环我采用的是PI控制也就是比例P加积分I没有用D。速度控制的目标是让实际速度稳定在一个给定的目标速度上。PD控制在这里的问题在于当车长期带负载或者电池电压下降时单纯的P控制会留下一个固定的速度偏差永远到不了目标值。积分项会把这个小的稳态误差逐渐累积起来直到误差被消除。这也是经典的“PD用于跟随、PI用于定值控制”的经验适用场景。参数整定方面我给出一组在这套系统上实测可用的参考初值转向环Kp12Kd8速度环Kp15Ki0.02积分限幅控制在±50。需要注意这个只能作为起点不同的车架、传感器布局、电机特性都会导致最优参数出现明显差异到了现场一定要根据实际跑圈效果进行微调。3.4 路口识别与停车逻辑路口识别是小车顺利完赛的关键也是最容易翻车的地方。典型的赛场元素包括十字路口、起点区域、终点停车区域。我的处理方式是基于传感器模式的特征识别。比如十字路口的特征是所有传感器全部检测到黑线也就是全黑状态。这时候车是直行还是转弯取决于这个路口在赛道路径中的位置。最简单的做法是给不同的路口做编号车跑到第几个路口执行什么动作比赛前根据赛道地图预先配置好。更实用的一种做法是“按键记录法”在赛前试跑的时候让车跑一圈程序自动记录每个全黑状态对应的时间戳或偏移量然后根据先后顺序决定第几个路口做什么动作。这种方式的好处是现场适应性极强赛道稍微有点出入也能应对。当然最保险的还是“地图配置法”直接根据题目给出的赛道图把每个路口的动作提前写入配置表。终点停车的处理逻辑是这个项目里我花时间最多的地方之一。单纯靠“看到终点停车线就立刻刹车”是不可行的因为车有惯性从传感器看到线到车真正停下来还有一段不小的滑行距离而且滑行距离和车速直接相关。我的方案是进入终点标志区前提前把目标速度从正常巡航速度降到一个很低的值然后当检测到停车线时执行“PWM全停 短时间反接刹车”的双重停车策略。反接刹车的本质是给电机制动让电机快速停下来而不是自然滑行这个技巧对停车精度的提升非常明显。3.5 代码文件结构源代码我按功能模块拆分成多个文件每个文件只干一件事这样调试和阅读都很轻松main.c主循环、状态机调度、初始化。sensor.c灰度传感器读取、模式识别、偏差计算。motor.cPWM输出、电机方向控制、速度读取。pid.cPID计算函数转向PD和速度PI各一个实例。control.c控制逻辑包含循迹控制、路口处理、停车逻辑。debug.c串口调试打印用于实时输出偏差、速度、状态机信息。调试串口是这套系统里一个非常实用的“外挂”。我在代码里把当前偏差值、实时速度、当前状态机的状态这三个变量通过串口以固定频率输出到电脑上然后用串口助手绘图工具显示成曲线。这样一来车跑的时候我虽然看不到车的真实状态但看曲线就能判断参数调得合不合理。这个方法在调PID的时候尤其好用。4. 调试过程与问题排查实录4.1 跑偏和震荡怎么治第一轮试跑的时候车出现了非常典型的蛇形走位问题在直线段还可以一进弯道就左右甩头严重的时候直接甩出赛道。我当时的排查思路是这样的先把速度环的参数临时设成开环只让转向PD工作。然后跑直线赛道观察车的偏移情况。发现车在直线段也会出现轻微但是持续的左右摆动这就说明转向环的D项不够或者Kp过大。把Kp从12降到8之后直线段已经比较稳了但弯道还是不够好。接着我把Kd从8增加到12弯道的情况明显改善。这里我踩过一个很具体的坑D项是“误差的微分”如果直接用原始误差来计算微分传感器的离散跳变会让微分项产生极大的尖峰车反而会抽搐。正确的做法是给微分加一个简单的低通滤波每次只取20%的新微分值和80%的旧微分值做加权平均这样D项的输出就很平滑了。这个细节网上很少有教程会提但实测下来非常关键。4.2 丢线和飞线比较头疼的是高速过急弯时丢线的问题。弯道太急车身速度又比较快传感器的覆盖范围不够整个传感器阵列有一瞬间全都压到了白色地面上输出全白。这个时候如果程序还在算偏差得到的结果完全是错的车就会乱打方向冲出去。解决这个问题我分了两步。第一步是硬件层面把传感器支架加宽了一点同时适当调低了传感器离地高度让覆盖范围更宽对地面的判读也更稳定。第二步是软件层面加了“丢线保护”逻辑当检测到全白状态时不再计算偏差而是沿用上一次有效的偏差方向并配合一个固定的“找回角度”让车缓慢转向丢线的方向。这个逻辑的本质是让车在丢线的瞬间“相信记忆”而不是“瞎猜”。4.3 电池电压下降引发的速度漂移这个问题藏得很深第一次做的时候完全没有意识到。满电的时候车跑得很正常跑了几圈之后速度明显慢下来了但看起来好像还“能完成任务”可实际上速度一慢过弯时的离心力变了转向的参数表现也变了整个车的操控手感完全不一样。原因就是开环控制。开环状态下PWM占空比固定但电池电压降低之后电机的实际转速也降低了机器人的速度就慢了。这个问题的根治方法是速度闭环也就是前面提到的编码器速度PI控制。用编码器实时读取电机转速和目标速度做比较有偏差就自动加大或减小PWM占空比。这样即使电池电压从8.4V掉到6.8V只要在电机驱动能力范围内小车的速度都能稳定在目标值附近。这个改造做完之后整车的稳定性提升了一个台阶。我之前一直以为速度闭环是锦上添花的功能实际做完才发现它是“必需品”尤其是比赛要跑好几轮的情况下电池状态对车的影响太大了。4.4 现场环境的坑比赛现场和实验室最大的不同是环境光。实验室是可控的日光灯光线均匀赛场可能有大窗户透进来阳光在某个位置形成强光斑。灰度传感器虽然用的是主动红外光但如果环境中有红外成分比较强的光源或者地面材质对红外反射率差异很大阈值就可能失效。我的应对方案有两个。第一在代码里支持一键校准功能小车静止放在赛道的白色区域上程序自动读取16次传感器数据取平均值作为“白电平”参考然后自动计算阈值。这个校准过程只需要几秒钟到现场之后就现场校一次在准备区就把阈值设定好。第二赛前准备一个备用传感器阈值参数组通过按键切换。这样万一现场光线变了不用重新下载代码直接切换到另一组参数就能继续跑。这个小功能在比赛当天帮我省下了大量的时间和精力。5. 代码与原理图的工程化整理决定分数的最后一环5.1 代码注释与命名规范可能有人觉得电赛只看车跑的最终成绩代码写得乱一点无所谓。这个想法从我两次参赛的经验来看是很吃亏的。电赛有一个重要环节就是作品测试现场的演示和答辩评委老师会直接查看你提交的源代码和设计文档。如果你的代码全局变量满天飞、函数名全部是a1、a2、b3这种毫无意义的命名评委会直接质疑你的工程能力和代码规范性这个印象分对最终成绩的影响比你想象中大得多。我整理这套代码的时候遵循了几个很简单的原则。第一所有变量和函数用英文加下划线命名做到“读名字就知道意思”比如read_sensor_data()、calculate_position_error()、motor_set_speed()。第二每个文件的头部写清楚这个文件的作用和核心变量说明。第三关键算法部分一定要写注释不是那种废话注释而是写清楚“这一段在干什么”和“为什么这么干”。第四宏定义的可调参数全部集中放在一个config.h文件里包括PID参数、传感器阈值、PWM频率、目标速度等方便现场快速调整。5.2 原理图检查清单这是我在整理原理图时整理的一份自查清单每一条都是用代价换来的经验防反接电源入口有没有加防反接二极管或者自恢复保险丝比赛现场有很多人借电池用一旦正负极接反整块板子直接冒烟。去耦电容每个芯片的电源引脚附近有没有放100nF的电容没有的话芯片在电源波动时很容易莫名复位。测试点关键电源节点、PWM输出、传感器信号有没有留出测试点或者排针调试的时候示波器表笔没地方接只能飞线很痛苦。传感器接口方向灰度传感器模块的排针在原理图上有没有标注清楚引脚顺序现场插反了传感器模块直接烧掉。电机接线端子电机线有没有用可插拔的端子这样比赛现场换电机、换车架都方便不用动烙铁。5.3 比赛现场的快速适配手段代码里我预留了好几个“现场适配”的功能它们都不会增加太多工程量但能在关键时刻救你一命。第一个是遥杆式按键调参。通过板载的按键和OLED屏幕直接在车上就能调整PID参数、目标速度、路口动作配置完全不需要连接电脑。现场临时发现某个弯过不去直接按钮把速度降个20%马上就能重跑不用回到工位重新编译下载程序。第二个是串口日志开关。平时调试的时候打开串口输出能看到PWM、偏差、速度、状态等所有数据。比赛正式跑的时候通过一个宏定义直接关掉所有串口输出省出串口占用造成的CPU时间和干扰让控制循环更干净。第三个是多组参数存储。上面1.4提到的把传感器阈值和PID参数做成多组存储现场一键切换。这个功能实现起来很简单就是把一组结构体变量写入片内Flash的某个固定地址运行的时候读取。备赛阶段可能体会不到它的价值到了赛场你就知道多一套备用参数就是多一条命。最后再分享一个实际的小技巧在停车刹车的时候不要一次性把PWM从100%直接跳到0%那样惯性冲击很大停车位置也不好控制。我用了“两段式刹车”先减到30%持续几十毫秒然后才全停并反接刹车。这个做法比直接急刹的停车精度要高不少赛道冲刺的时候特别好用。这套系统从头到尾做下来我最深的体会就是电赛这类项目比到最后其实比的是谁的细节处理得更到位。传感器布局多留一点余量、电源多一个滤波电容、代码里多一个丢线保护、现场多一套备用参数这些单个来看都不起眼但加起来就是一台稳定完赛的高分车和一台只能在实验室里跑的样机之间的差距。希望这篇复盘能帮你少走一些我走过的弯路等你的车在赛道上稳稳冲过终点的时候你就明白这套设计里每一分用心都值了。本文还有配套的精品资源点击获取
分享:

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

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