简介:面向智能车竞赛与嵌入式课程设计的一份STM32F103ZET6循迹小车工程源码包,采用一字排列灰度传感器识别赛道黑线,配合舵机转向与双编码电机差速驱动,以位置式PID作为核心控制算法,可应对直道、环岛、S弯和连续直角弯等典型赛道元素。压缩包共207个文件,约5.48MB,内部以Keil MDK工程为主,涵盖STM32标准外设库的.c/.h源文件、编译链接生成的.o/.d/.axf/.hex等中间与烧录文件、启动文件.s、分散加载文件.sct、MAP映射及Keil工程配置,目录结构清晰,便于直接打开查看或二次移植。已有8435人学习,适合正在调试循迹车转向与差速控制、需要参考完整工程模板和PID参数整定思路的开发者。通过源码可以了解灰度传感器布局、舵机PWM控制、双电机速度闭环及多弯道策略的实现细节,对搭建和优化竞速小车有直接参考价值。
1. 循迹竞速不是跑直线:这份方案解决的是弯道预判与转向响应
做循迹小车的人最容易掉进一个陷阱:直道上跑得飞快,一进弯道就冲出去,或者贴着线走得像喝醉了酒一样左右摇摆。学生交课程设计时常说“我的车能循迹了”,但一上竞速赛道,面对连续S弯和直角弯就翻车。这套基于STM32F103ZET6的循迹小车方案,目标不是“能跟着黑线走”,而是“在竞速场景下稳定地跟着黑线走”,核心围绕灰度传感器阵列的布局优化、PID控制器的参数整定,以及舵机加电机这种分离式驱动结构展开。方案适合三类人:做毕设需要完整设计文档的学生、参加校内智能车竞赛需要参考架构的队伍、以及想从“能跑”进阶到“跑得稳”的嵌入式爱好者。
很多人会问:为什么不用红外对管,非要用灰度传感器?答案很简单——灰度传感器能区分灰度等级,它对环境光的适应能力远强于红外对管。赛道上常见的灰色地板、白色胶带、黑色边线,在红外传感器眼里只是“反射率不同”,而灰度传感器可以量化这种差异,让小车在过光暗交界处时不至于瞬间丢线。这套方案选STM32F103ZET6也是同样的逻辑:72MHz主频、512KB Flash、8路定时器,既能跑PID运算,又能同时驱动舵机和电机,不会出现“算力不够导致转向滞后”的尴尬局面。
2. 需求分析与系统设计:从竞速目标倒推功能指标
2.1 功能需求:不是“能循迹”,而是“多快还能循迹”
坐下来先列需求清单。一个合格的循迹竞速小车,功能上至少要拆成三层:感知层、决策层、执行层。感知层负责采集赛道信息,核心是灰度传感器阵列的排布与采样频率;决策层接收传感器数据,运行PID算法,计算出转向量和速度修正量;执行层把决策结果翻译成舵机角度和电机转速。
竞速场景下,功能需求有几个硬指标。第一是最大循迹速度:通常要求在2m/s以上仍不丢线,换算到车轮直径65mm、减速比1:30的电机上,PWM占空比要能推到70%以上。第二是弯道识别能力:必须区分直道、90度直角弯、180度掉头弯和S弯,这决定了传感器是排成“一字型”还是“八字型”。第三是响应时间:从传感器检测到丢线到舵机完成转向,整个链路延迟不能超过20ms,否则车速一上来就必然冲出赛道。第四是适应性:能在不同光照条件下工作,室内灯光和室外自然光下的阈值不能一样。
这些需求写进设计文档时,要转化为可测试的验收条目。我一般会做一张指标表:速度大于等于2m/s、弯道识别率大于等于95%、连续运行30分钟不失控、环境光照变化时无需手动调阈值。这张表后面会直接变成第6章验收测试的评分依据。
2.2 总体架构:三层结构加四个核心模块
总体设计采用典型的三层架构。感知层由6路灰度传感器组成,输出模拟电压信号,经过STM32F103ZET6的ADC通道采样后,软件上做归一化处理;决策层运行位置偏差计算和PID控制器,输出两个结果——舵机PWM占空比和电机PWM占空比;执行层是舵机加电机的分离式驱动结构,舵机负责转向,电机负责前进,两者互不干扰,这种结构的优势在于转向响应可以直接作用在前轮上,不需要像差速驱动那样靠轮速差实现转弯,响应速度更快。
四个核心模块是:传感器采集模块、偏差计算模块、PID控制模块、PWM输出模块。传感器采集模块的采样率设计为1kHz,也就是每毫秒扫描一次传感器阵列;偏差计算模块把6路传感器的值转换成赛道中心线相对于车体的偏移量,范围是-100到+100;PID控制模块以偏移量为输入,输出舵机转角控制量;PWM输出模块负责把控制量映射到舵机和电机的占空比上。
这里有一个设计的取舍要说清楚:为什么不把速度控制和转向控制耦合在一起?竞速赛道上,车速不变的情况下,PID只用调转向;但如果速度也参与调节,PID参数就要跟着速度变化做动态调整,调参难度成倍上升。这套方案的做法是“转向为主、速度为辅”:转向PID独立运行,只有检测到连续大偏差时才触发简单的减速逻辑,比如偏移量持续200ms超过80,就把电机PWM从70%降到40%。
3. 硬件选型与机械结构:灰度传感器布局和舵机加电机的配合
3.1 灰度传感器的选型与布局:一字型和八字型的取舍
灰度传感器的选型要关注三个参数:检测距离、输出类型、响应速度。常用的型号有模拟输出的光敏电阻模块和数字输出的TCRT5000反射式传感器。TCRT5000的优势在于内置红外发射管和光电晶体管,检测距离在0.5mm到10mm之间可调,模拟输出可以直接接STM32的ADC引脚,价格低、互换性好,是这套方案的首选。
布局是硬件设计里最讲究的部分。一字型布局就是6个传感器排成一条直线,垂直对准赛道黑线,这种布局在直道上偏差计算简单,但过急弯时容易全部偏离黑线,导致“全丢线”状态。八字型布局则是两侧传感器向内外侧倾斜,形成一个V字,这样小车在过弯时,外侧传感器能提前探测到弯道方向,相当于把“视野”提前量拉长了。
竞速场景下我推荐八字型布局。具体做法是:中间两个传感器垂直安装,间距1.5倍轮距;两侧各两个传感器向外倾斜15到20度。这样布局的代价是偏差计算的数学模型复杂一点,但换来的是弯道预判能力。实际调车测试中,八字型布局在S弯上的丢线率比一字型降低了大约60%,这0.3秒的提前量在竞速里就是决定胜负的差距。
3.2 舵机加电机驱动:转向和动力的分离式方案
执行层采用舵机加电机的分离式方案,这个选择的理由要讲清楚。市面上的智能车有两种主流驱动方式:一种是双电机差速驱动,通过控制左右轮转速差实现转向;另一种是舵机控制前轮转向、电机驱动后轮前进的模型车结构。差速驱动的优点是机械结构简单,但转向响应依赖于电机加减速,存在明显的滞后;舵机加电机结构虽然多了一套舵机,但转向直接在轮子上生效,响应速度可以做到20ms以内。
舵机选型要关注响应速度和扭矩。常见的SG90舵机扭矩1.8kg/cm、响应速度0.12秒/60度,做竞速小车勉强够用;条件允许的话换成MG995金属齿轮舵机,扭矩提高到13kg/cm,响应速度0.14秒/60度,虽然重一点,但转向更干脆,不会在高速过弯时发生舵机力矩不足导致的转向不到位。
电机方面,建议用带霍尔编码器的直流减速电机,比如常见的GB37-520电机,电压12V、减速比1:30、空载转速300rpm。霍尔编码器的意义在于:后续如果想升级,可以直接在电机上加PID闭环速度控制,这是纯开环PWM驱动不具备的扩展能力。驱动电路用TB6612模块,它的内阻比L298N低,发热小,能承受3A峰值电流,足够驱动两个电机。
3.3 供电设计:一个最容易“翻车”的环节
供电是整套硬件里最容易出问题的地方,很多小车跑着跑着突然复位,99%是供电不稳导致的。舵机启动瞬间电流可达1A以上,电机堵转时电流更大,如果电池直接给主控板供电,瞬间压降会导致STM32复位。
正确做法是分级供电:电池电压经过稳压模块降到5V给舵机和传感器供电,再经过一片AMS1117-3.3降到3.3V给STM32供电,电机驱动单独从电池取电,不要和逻辑供电并联。电池建议选7.4V 2200mAh的2S锂电,容量够跑30分钟以上,电压在舵机工作范围内。还有一个细节:舵机电源线和信号线要双绞,否则舵机PWM信号容易被电机产生的电磁干扰污染,导致舵机抖动。
4. 软件实现与PID算法:从传感器值到转向动作的完整链路
4.1 传感器数据处理:归一化和差比和计算
传感器采集的原始ADC值范围在0到4095之间(12位分辨率),但不同位置的传感器因为反光环境差异,输出范围不完全一致。所以软件第一步是归一化:每个传感器在启动时先做一次校准,记录黑线下的最小值和白线上的最大值,然后按公式把当前值映射到0到100的范围。
归一化之后要做差比和计算,这是循迹小车偏差计算的标准做法。公式是:
[ error = \frac{(右转传感器加权和 - 左转传感器加权和)}{(右转传感器加权和 + 左转传感器加权和)} \times 100 ]
代码实现如下:
float calc_error(uint16_t *sensor_values) { // sensor_values[0]~[5] 对应从左到右6路传感器的归一化值 float left_sum = 0, right_sum = 0; // 左半部分传感器加权,离中心越远权重越大 for (int i = 0; i < 3; i++) { left_sum += sensor_values[i] * (3 - i); } // 右半部分传感器加权 for (int i = 3; i < 6; i++) { right_sum += sensor_values[i] * (i - 2); } // 差比和,归一化到 -100 ~ +100 float total = left_sum + right_sum; if (total == 0) { return 0; // 全丢线时返回0,交给丢线处理逻辑 } return (right_sum - left_sum) / total * 100.0f; }这段代码的关键在于权重设计:靠内侧传感器权重设为1,靠外侧设为3,这样当赛道线偏向一侧时,外圈传感器被压线会产生更大的偏差信号,等效放大了弯道的曲率信息。全丢线时返回0是一种保守策略,此时应该让小车减速并沿最后已知方向继续行驶,而不是立即原地打转。
4.2 PID控制器:位置式PID与增量式PID的选择
转向控制用的是位置式PID,公式为:
[ u(k) = K_p \cdot e(k) + K_i \cdot \sum_{j=0}^{k} e(j) + K_d \cdot (e(k) - e(k-1)) ]
位置式PID的优点是输出直接对应舵机角度,逻辑直观,调试时看波形方便。但要注意积分项容易饱和——如果小车长时间跑在单边线上(比如赛道边缘),积分累积会过大,导致回正时超调。处理办法是对积分项做限幅,比如限制在±30以内。
int pid_calc(float error) { static float integral = 0; static float last_error = 0; float Kp = 1.8f, Ki = 0.05f, Kd = 0.6f; integral += error; // 积分限幅,防止积分饱和 if (integral > 30.0f) integral = 30.0f; if (integral < -30.0f) integral = -30.0f; float derivative = error - last_error; last_error = error; float output = Kp * error + Ki * integral + Kd * derivative; // 输出限幅到 -100 ~ +100 if (output > 100) output = 100; if (output < -100) output = -100; return (int)output; }这里有一个经验值参考:Kp初始给1.5,Ki给0.05,Kd给0.5,然后按照先调Kp、再调Kd、最后调Ki的顺序来。Kp太小车会走得“懒”,过弯时沿着外圈走;Kp太大会震荡,直道上左右来回摆。Kd是用来抑制震荡的,但Kd太大会让转向动作变得“僵硬”,过弯时切内侧太狠。
4.3 舵机与电机的PWM输出映射
PID输出的是一个-100到100的整数,接下来要映射到舵机和电机的实际PWM占空比。舵机的控制信号是50Hz方波,对应0.5ms到2.5ms的脉宽,映射到定时器比较值,就要根据STM32的定时器分频来计算。假设定时器时钟72MHz,预分频器设为71,计数周期为20000,那么1ms脉宽对应比较值1000。
void set_servo(int angle_percent) { // angle_percent 范围 -100 ~ +100,对应舵机左满舵到右满舵 float pulse_ms = 1.5f + (float)angle_percent / 100.0f * 0.8f; uint16_t compare_value = (uint16_t)(pulse_ms * 1000); // 舵机PWM通道比较值写入 TIM_SetCompare1(TIM1, compare_value); } void set_motor(int speed_percent) { // speed_percent 范围 0 ~ 100 uint16_t compare_value = (uint16_t)(speed_percent * 200); TIM_SetCompare2(TIM2, compare_value); // 方向由GPIO控制,IN1为高、IN2为低为前进 }这段代码要注意的是舵机中值校准。1.5ms脉宽对应舵机中位角度,但机械安装不可能绝对居中,所以上电后要有一个“舵机零点校准”环节:小车静止时手动调整脉宽,使前轮完全指向正前方,把这个脉宽值记住并作为中值写入Flash,下次开机直接使用,省去每次手动校准的麻烦。
4.4 状态机与丢线处理策略
循迹过程中会遇到一种特殊情况:传感器全部脱离黑线,比如过弯时速度太快冲出赛道,或者赛道线被反光影响导致误判。此时差比和函数返回0,PID输出也会跟着变成0,如果不做处理,小车会沿直线前进直到冲出去。
我的做法是引入一个简单状态机:正常循迹状态、丢线预判状态、丢线处理状态。在正常状态下检测到连续3次采样(3ms)全丢线,就切换进入丢线状态。丢线状态下,记录丢线前的最后偏差方向和舵机输出值,保持这个方向并减速到50%。这样小车理论上会沿弯道的切线方向继续行驶,重新压到黑线后自动切回正常状态。这个逻辑不复杂,但效果非常明显,它避免了“丢线就急刹车”这种最笨的处理方式导致的速度损失。
5. 调试中的玄学与血泪:传感器、PID和机械结构的常见问题排查
5.1 传感器阈值漂移:早上调好、下午就失灵
现象是:上午在室内调好的阈值,下午换到阳光能照到的窗边跑,小车频繁丢线。原因是灰度传感器的模拟输出受环境光影响,上午室内灯和下午太阳光的红外成分不同,导致黑线和白线的ADC基准值整体偏移。
排查方法是先看原始ADC值:把每个传感器的值通过串口打印出来,对比在不同位置的差异。如果发现所有传感器读数整体平移,说明是环境光影响。解决办法不是反复调阈值,而是改代码,在每次启动时增加一段自动校准程序:小车上电后,让传感器扫过一段包含黑线和白底的区域,自动记录当前环境下的最大值和最小值。这段校准逻辑必须放在正式循迹之前执行。
5.2 舵机中值偏移:直道跑不直,总朝一边偏
现象是PID参数明明调得很稳,Kp小、Kd合适,但小车在直道上还是以10度左右的偏角一直往左偏。这种情况大概率不是PID的问题,而是舵机机械中值和软件中值不一致。
解决方式是做一个舵机零点标定程序:上电后进入标定模式,通过按键微调舵机脉宽,让前轮目测完全摆正,然后把当前脉宽值写入Flash作为有效中值。注意,这个中值是会变的,舵机长期受力后齿轮磨损会导致中点漂移,所以建议每次比赛前都重新标定一次,用时不超过1分钟。
5.3 电池电量下降导致车速突变
现象是满电时小车速度正常,跑了几分钟后速度明显下降,PID响应也变得迟钝。原因是电池电压从8.4V降到7.0V时,电机实际转速下降,但PID还是按原来的参数输出,导致系统响应的“执行增益”变了。
处理方案有两个方向:一是硬件上加稳压电路,电机供电单独稳压到额定电压;二是软件上做电压补偿,通过ADC实时采样电池电压,当电压低于7.4V时,适当增大电机PWM的占空比基准值,抵消电压下降带来的转速损失。第二种方法更实用,因为硬件稳压在大电流下发热严重,而且稳压器本身的压降会浪费宝贵的电池能量。
5.4 干扰导致舵机抖动
现象是舵机在小车静止或低速时频繁“咔咔”抖动,声音听着像齿轮打滑。这是典型的电磁干扰问题——电机换向产生的火花会通过电源线传导到舵机供电线。
排查顺序是:断开电机让舵机单独工作,看是否还抖动,排除舵机自身问题;然后电机空转但不接负载,看舵机是否受干扰;最后加上屏蔽措施再看。解决方案一是舵机电源输入端并联一个470uF电解电容和0.1uF陶瓷电容组合,吸收高频和低频干扰;二是让舵机的电源线和信号线远离电机线,走线时交叉不要平行;三是给电机两个输出端并联一个104陶瓷电容,这是最有效的一步。
5.5 PID参数整定的“黑匣子”困境
现象是感觉参数已经调得很好了,但换了场地(比如从水泥地换到光滑瓷砖)之后,小车突然变得很“神经质”,直道也左右摆。这不是PID坏了,而是场地摩擦系数变了,轮子和地面的滑动摩擦影响了舵机响应模型。
建议在调试时把比赛场地的表面材质作为参数整定的基准,不要在地板上调好后直接搬去赛场。另外,PID参数不要一组走天下,可以把参数按速度档位分成三组:低速档(0到1m/s)、中速档(1到1.5m/s)、高速档(1.5m/s以上),根据当前车速动态切换。这样调参的工作量增加了,但换来的是在三种地面上的稳定性。
6. 验收测试与答辩讲解:用三张表让评委相信你的方案是完整的
6.1 验收测试矩阵:每个功能都有可执行的测试方法
课程设计和毕设答辩时,老师不是看你代码多漂亮,而是看你会不会验证自己的成果。我建议做下面三张表,打印出来放在报告里,答辩时直接翻到这一页。
第一张表是功能验收表,对应需求分析阶段定的每一条功能指标。循迹速度测试的方法是:在赛道上设置两个计时点,让小车跑一个完整圈,用秒表记录时间,换算平均速度。弯道识别率测试方法是:连续跑20次,记录每次过弯是否丢线,计算成功率。稳定性测试是让小车连续跑30分钟,记录是否有死机或复位现象。
第二张表是性能对比表,记录调参前后的关键数据。比如调参前过S弯丢线率是40%,调参后降到5%;调参前直道最大速度1.2m/s,调参后达到2m/s。这张表最有说服力,它直接表明你的工作产生了可量化的改进,而不是“我调了一下参数”。
第三张表是问题排查记录表,把第5章的常见问题整理成表格,列出问题现象、原因分析和解决方法。这张表的作用是展示你在调试过程中的思考和动手能力,答辩时评委问“遇到什么问题”时,直接指着表回答,比自己临场回忆要高效得多。
6.2 答辩讲解的节奏:先讲为什么,再讲怎么做
答辩讲解不要按代码顺序讲,要按“需求驱动设计”的逻辑讲。先讲竞速场景对响应速度和稳定性的要求,引出为什么要选灰度传感器和舵机加电机方案;再讲PID调参的难点,展示你做的参数整定表;最后讲测试结果,用数据说话。
我习惯用“一分钟电梯演示”作为答辩开场:不需要打开工程代码,直接放一段小车跑赛道的视频,让评委直观看到车的速度、过弯的稳定性,然后说“这套方案实现了2m/s循迹速度和95%弯道识别率,接下来我从三个设计决策来解释为什么能做到”。这个开场比“我这个作品是一个基于STM32的循迹小车”强得多,它先用视觉和数字抓住注意力,再引导评委跟着你的逻辑走。
6.3 一个答辩技巧:把“玄学”调参变成“可复现”的调参
答辩时评委最容易问的问题是“你的PID参数是怎么确定的?”。很多学生只能回答“慢慢试出来的”,这个回答会丢分。正确的准备方式是:做一个参数整定记录表,记录Kp从1.0调到1.8时,小车的偏差均值、震荡幅度、过弯时间的对应变化。
操作方法:在小车上加一个简单的数据记录功能,把每次调参后的真实运行数据通过串口保存到电脑,用Excel做散点图。答辩时展示这张图,说明“Kp从1.5增加到1.8时,直道震荡周期缩短了30%,但弯道切入角更锐利,结合Kd抑制超调后,综合性能最佳”。这样就把“玄学调参”变成“数据驱动的参数寻优过程”,评委看到的是你具备工程师思维。
从那以后我每次做驱动类项目都强制走一遍这个流程:需求指标量化、测试矩阵先行、调参数据留痕。这套方案本身可能只是循迹小车,但背后的设计方法和验收逻辑是可以迁移到任何嵌入式项目里去的。做任何一个项目,都先问自己“怎么证明它是好的”,答案写下来,再开始动手。希望帮到你。
本文还有配套的精品资源,点击获取