简介:面向STM32嵌入式初学者的巡线小车设计完整方案,以STM32F103VET6为核心,覆盖巡线检测、控制算法、电机驱动与电源管理等环节,适合高校课程设计、电子竞赛及个人嵌入式学习,也适合对机器人控制方向感兴趣的开发者拓展实践。资源压缩包共72个文件,以28个头文件、27个C源文件为主,另有8个汇编启动文件、Keil工程配置、hex烧录文件、说明文档及辅助脚本,整体大小仅314KB,目录按源码、标准外设库、CMSIS核心层、文档和工程输出划分,结构清晰,便于按模块查阅。工程可直接使用Keil MDK打开,包含完整的用户程序、标准外设驱动库、启动文件和编译生成物,有助于理解STM32外设配置、定时器PWM输出、ADC采样及PID闭环控制等关键知识点;readme说明和调试配置文件可帮助快速上手,工程整体层次分明,适合作为课堂项目与竞赛方案的参考。目前该资源已有1756人学习,适合希望从零实现一辆可循迹小车并掌握嵌入式开发流程的读者。
2. 项目整体思路与核心决策
拿到“基于STM32的巡线小车”这个题目,很多人的第一反应是“这不就是经典的入门项目嘛”。确实,巡线小车在嵌入式学习路径里的地位,差不多等同于编程界的“Hello World”,但你要是真把它想简单了,做起来照样会翻车。尤其是我看到这个压缩包名字里只有“巡线小车”四个字,没有提具体用了几路传感器、什么电机驱动、什么控制算法,说明这个项目留有相当大的自由发挥空间,同时也意味着里面有大量需要自己拍板的细节。
先说结论:我是用STM32F103C8T6最小系统板作为主控,搭配4路灰度传感器、TB6612FNG电机驱动、两路带编码器的直流减速电机,再加上一块7.4V锂电池组,把整车跑通了。控制策略用位置式PID做循迹偏差修正,方向通过差速转向实现,速度闭环用增量式PID做。整个系统不算复杂,但覆盖了嵌入式开发里最常用的几个模块:GPIO、定时器PWM、外部中断、串口、ADC采集,以及最关键的PID算法落地。
选择STM32F103C8T6而不是其他芯片,主要是三个原因:第一,资料多。不管是标准外设库还是HAL库,网上的教程一抓一大把,哪怕是零基础的新手,遇到问题也很容易搜到解决方案。第二,性价比高,一片芯片几块钱,配上最小系统板也就是十几块钱的事,用来做毕设或者课程设计完全不心疼。第三,性能够用。72MHz的主频、20KB RAM、64KB Flash,跑一个巡线算法绰绰有余,哪怕以后想加个蓝牙遥控、OLED显示、超声波避障,资源也还有富余。
2.1 为什么选4路灰度而不是2路或8路
传感器路数的选择,直接影响巡线的顺滑程度和代码复杂度,这个坑我得好好说说。
2路传感器的方案最简单,只能判断“偏左”还是“偏右”,输出结果是离散的,小车跑起来会是“走之字”的效果,左一下右一下,速度快一点就冲出赛道。8路传感器虽然能得到更细腻的偏差值,但对于一辆十几厘米长的小车来说,传感器分布太密,安装空间紧张,而且价格也上去了,对学生项目来说性价比不高。
4路是一个很好的平衡点。把4个探头等间距排列,中间两路负责“对准线”,两边两路负责“感知偏离”。这样既能通过加权平均算法算出连续的偏差值,又不会让硬件成本和算法复杂度失控。我自己实测下来,4路灰度在小半径弯道和十字交叉口的表现已经很让人满意,除非你要跑那种连续S弯加急转的竞速赛道,才需要上8路甚至16路的方案。
2.2 差速转向与舵机转向的取舍
巡线小车的转向方式,常见的有两种:一种是舵机控制前轮转向,类似真车的转向结构;另一种是两轮差速转向,靠左右轮的速度差实现转弯。
我做的是两轮差速方案,原因很简单:差速转向的结构更简单,不需要复杂的连杆机构,车体可以做得更紧凑,而且在原地转弯时机动性更强。代价是控制上需要更精细的算法——你不能让左轮和右轮傻傻地各跑各的,必须通过PID把两侧速度协调好。
还有一个选型细节容易被忽略:电机要不要带编码器?如果只是做个样子、让小车能跑就行,不带编码器的普通直流减速电机就够。但如果你想认真调一个速度闭环,让小车在不同负载、不同电量下都能稳定跑,那编码器几乎是必需品。我用的是带霍尔编码器的N20电机,减速比1:30,实测速度闭环之后,同样一段直道,左右轮速度差能控制在3%以内,这对巡线的稳定性帮助非常大。
3. 核心硬件选型与接线踩坑记录
硬件部分是很多人第一次做小车时最容易懵的地方,因为涉及的东西杂:主控、驱动、传感器、电机、电池,每一个都有坑,而且这些坑往往要到通电之后才暴露出来。
3.1 主控板、电机驱动与传感器的搭配建议
主控板我强烈建议用“最小系统板+面包板/洞洞板”的组合,而不是直接买一块集成好的开发板。开发板虽然方便,但体积大、接口冗余多,装在小车上很占地方,而且很多开发板直接把电机驱动也集成进去了,出了问题不好排查。
我的具体配置如下:STM32F103C8T6最小系统板一块,引脚全部引出;TB6612FNG电机驱动模块一个,负责把控制信号放大成功率信号驱动电机;4路灰度传感器模块一个,用LM393比较器输出数字信号,带电位器可以调节灵敏度;两个N20带编码器直流减速电机;一个7.4V 1800mAh锂电池组给电机供电,一个AMS1117-3.3稳压模块给主控和传感器供电。
这里必须提醒一个很多人会犯的错误:千万别把电机和主控共用一个电源而没有任何隔离。电机启动瞬间的电流冲击很大,会造成电压跌落,主控轻则复位,重则Flash数据错乱。我见过太多人把电池正极同时接到电机驱动和开发板VIN上,一上电小车疯狂复位,还以为是代码写错了。正确做法是:电池直接给电机驱动供电(VM引脚),主控和传感器通过稳压模块单独供电,两个电源的GND要连在一起,否则信号参考电位不一致,逻辑电平会乱。
3.2 TB6612FNG为什么比L298N更适合小车
老一代的教程都喜欢用L298N,因为它便宜、功率大、皮实。但用过之后你就会发现,L298N有两个非常烦人的缺点:一是压降太大,饱和压降接近2V,6V的电池经过L298N到电机上就只剩4V了,转速上不去;二是它的PWM频率响应不够快,做精密调速的时候力不从心。
TB6612FNG是东芝出的MOSFET驱动的H桥芯片,导通内阻只有0.5欧姆左右,同样6V供电,到电机端的电压几乎不打折。它的PWM响应频率能到100kHz以上,做速度闭环完全够用。而且模块只有拇指大小,可以直接插在面包板上,对小车这种寸土寸金的空间非常友好。
3.3 灰度传感器的原理与安装细节
灰度传感器的原理是利用红外LED发射光线,红外接收管检测反射回来的光强。黑线吸收光线,反射弱,输出低电平;白色地面反射强,输出高电平。这里有个细节:不同地面材质对红外线的反射率差异很大,所以阈值不是固定的,必须根据实际场地用模块上的电位器现场调。
安装高度和间距是另一个很容易被忽视的点。传感器离地面太近,小车稍微一颠簸就会误判;离得太远,红外光扩散严重,黑线和白底的区分度下降。我实测的经验是,探头离地面1cm到1.5cm是最合适的范围。4个探头之间的间距,要根据赛道黑线的宽度来定。标准巡线赛道黑线宽度一般是2cm左右,我的车体宽度12cm,4个探头均匀分布在车头下方,总跨度覆盖3cm左右,保证任何时候至少有两个探头能同时压到线上。
4. 软件架构与核心代码实现解析
软件部分才是这个项目的灵魂。很多人的小车跑不起来、跑起来乱晃,问题往往不在硬件,而在控制逻辑写得不够稳。
4.1 整体软件流程与控制周期设计
先梳理一下整个系统的运行逻辑:上电后系统初始化,包括时钟、GPIO、定时器PWM、编码器接口、串口;然后进入主循环,主循环里按照固定时间片执行三个任务——传感器采集、PID计算、PWM输出。
这里最核心的设计决策是控制周期。我用的控制周期是10ms,也就是100Hz。为什么选这个值?因为小车的机械响应时间常数在几十毫秒量级,控制频率太低(比如20Hz)会导致响应滞后,小车冲出去才反应过来;控制频率太高(比如1kHz)也没有意义,因为传感器和电机的物理响应根本跟不上,反而增加了CPU负担。
实际代码里我用定时器中断来做时间基准,在中断里置一个标志位,主循环检测到标志位才执行一轮控制。这样做的好处是控制节拍精准,不会因为主循环里其他代码的耗时差异导致PID计算间隔忽长忽短。
4.2 PID控制算法的两种应用场景
巡线小车里其实有两个闭环,很多人只做了一个,导致小车跑得不够稳。
第一个是方向闭环,用位置式PID。4路传感器按从做到右编号1到4,定义偏差公式:EPS = (1W1 + 2W2 + 3W3 + 4W4) / (W1+W2+W3+W4) - 2.5。当传感器恰好对准黑线中心时,这个加权平均值等于2.5,偏差为0。黑线偏左时,EP S为正(因为左侧传感器权重小),小车需要向左修正。PID输出值直接叠加到两个电机的PWM上,实现差速转向。
第二个是速度闭环,用增量式PID。每个电机都有一个霍尔编码器,用定时器输入捕获模式读取脉冲数,换算成实际转速(圈/秒),和期望速度做差,经过增量式PID后输出PWM调整量。这个闭环的意义在于:小车在转弯时外侧轮和内侧轮的负载完全不一样,如果没有速度闭环,两个轮子的实际转速会差距很大,方向闭环算得再好也白搭。
PID参数整定我用了最土但最有效的方法——先调P,再调I,最后调D。方向环的P先从小往大加,加到小车在直道上出现轻微振荡就往回退一点;I的作用是消除稳态偏差,车在长直道上跑偏的时候加I;D的作用是抑制振荡,过弯的时候车头甩得厉害就加D。速度环的整定类似,但PWM输出要注意设置饱和限幅,我限制在0到100%占空比,防止积分饱和导致电机满速失控。
4.3 关键代码片段解析
传感器采样代码,核心思路是读取4个GPIO引脚状态并转成偏差值:
uint8_t sensor1, sensor2, sensor3, sensor4; sensor1 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); sensor2 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1); sensor3 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2); sensor4 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3); uint8_t sum = sensor1 + sensor2 + sensor3 + sensor4; float error = 0; if (sum != 0) { error = (1*sensor1 + 2*sensor2 + 3*sensor3 + 4*sensor4) / (float)sum - 2.5; }当所有传感器都丢线时(sum == 0),说明小车已经完全脱离赛道,此时不能按照普通PID算,要进入“寻线模式”——要么保持上一次的转向方向继续走,要么原地旋转寻找黑线。我采用的方式是:记录丢线前的最后一次偏差值,按照这个方向继续转向一段时间,如果还没找到线,再加大转向力度。这个逻辑在过急弯时特别重要,否则小车飞出赛道就傻在原地了。
速度环的PID计算,我封装成了一个通用的函数,方便方向环和速度环共用:
int16_t PID_Calc(PID_Typedef* pid, float target, float current) { pid->error = target - current; pid->integral += pid->error; pid->derivative = pid->error - pid->last_error; pid->last_error = pid->error; int16_t output = (int16_t)(pid->Kp * pid->error + pid->Ki * pid->integral + pid->Kd * pid->derivative); // 输出限幅,防止积分饱和 if (output > pid->output_max) output = pid->output_max; if (output < pid->output_min) output = pid->output_min; return output; }PWM输出使用定时器的通道1和通道2产生两路互补的PWM波,分别控制左右电机。需要注意的是,TB6612的AIN1/AIN2和BIN1/BIN2是方向控制引脚,PWMA/PWMB是速度控制引脚。要让电机正转,方向引脚给高低电平组合,PWM引脚给占空比信号。千万别把方向引脚和PWM引脚搞混了,否则电机会出现“只能转不能调速”或者“只能朝一个方向转”的诡异现象。
最后说一下初始化部分的踩坑:我在用HAL库时发现,定时器PWM初始化后必须调用HAL_TIM_PWM_Start(),否则通道不输出波形。这个函数漏了不会报错,但电机就是不动,排查了我一下午。
5. 调试验收与常见问题排查实录
PID参数整定和硬件排障这个过程,才是整个项目最耗时间的环节。我把实际踩过的坑和排查思路整理成了一张速查表,希望能帮大家少走弯路。
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| 上电后主控无反应 | 电源接反或电压过高 | 检查稳压模块输出是否为3.3V/5V,用手摸主控芯片温度,发烫立即断电 |
| 电机不转但主控正常 | TB6612方向引脚未拉高/拉低 | 用万用表测AO1/AO2端口电压,确认逻辑输入与输出关系 |
| 电机只转不调速 | PWM波形未输出 | 检查HAL_TIM_PWM_Start是否调用,确认定时器预分频器和自动重载值配置 |
| 灰度传感器输出一直为高 | 电位器阈值未校准 | 对黑线调节电位器到临界翻转,对白底再次确认 |
| 小车走直线但缓慢跑偏 | 左右轮机械阻力不一致 | 检查轮子轴套润滑、底盘是否平整,先做开环对称PWM测试确认机械一致 |
| 小车高速过弯时飞线 | 速度环PID参数不合适 / 丢线逻辑不够积极 | 降低目标速度,增大D参数,优化丢线后的搜索策略 |
| 上电后反复复位 | 供电不足或电源冲击 | 用示波器看VCC波形,确认电机启动瞬时压降是否低于3.3V |
5.1 关于延时函数和中断的幻觉
调试过程中我还遇到过一个问题:程序里用了HAL_Delay()之后,整个巡线逻辑就变得卡顿,尤其是过弯的时候反应明显变慢。排查半天发现,问题出在延时函数阻塞了主循环,导致PID计算周期不再稳定。任何延时操作都会让控制节拍混乱,所以巡线主逻辑中绝对不要使用阻塞式延时,宁可牺牲一些非关键功能(比如状态指示灯的闪烁)也不能阻塞控制循环。这个道理很简单,但debug时容易忽略。
5.2 小车装完第一步要做的事
装完硬件之后,别急着烧巡线代码。我的习惯是:第一步先烧一个最简单的“点灯”程序,验证主控核心功能;第二步烧一个电机开环测试程序,确认左右电机的正反转方向和PWM调速是否正常;第三步单独测试灰度传感器的4路输出是否能在黑白之间可靠翻转;最后才把全部功能合在一起跑。
顺便分享一个小技巧:在调PID之前,先在小车上贴一张胶带标记车头朝向,然后手推小车模拟巡线路径,观察传感器输出和偏差值的计算是否合理。这一步能帮你快速发现传感器接线顺序是否反了、偏差方向是否正确,远比直接上电跑车更高效。
6. 这个项目还能怎么扩展
做完一个基础版巡线小车,很多人的第一反应是“终于跑通了,可以交差了”。但如果你有多余的精力,这个项目的扩展空间其实很大,而且每一个方向都能学到新东西。
第一,给小车加上无线模块,比如用ESP8266或者HC-05蓝牙模块实现手机遥控和参数实时调节。这样调PID参数就不用反复拔插ST-Link烧录了,直接在手机上改参数就能看效果。第二,加一块0.96寸OLED显示屏,实时显示当前偏差值、PID输出、电机转速。调试过PID的人都知道,能实时看到这些数据有多香,省去了接串口线的麻烦。第三,如果把赛道改成无轨形式,就是后来的“麦克纳姆轮全向移动平台”或者“视觉巡线小车”,把灰度传感器换成OpenMV摄像头,用颜色识别算法提取赛道线,然后通过串口把偏差发给STM32,这又是一个全新的研究方向。
还有一个我最近在尝试的方向:移植FreeRTOS操作系统。巡线、避障、蓝牙通信、OLED显示,如果全部塞进一个while(1)主循环,逻辑会越来越乱。用RTOS把不同任务分开调度,巡线任务保持最高优先级,通信任务和显示任务挂到低优先级,系统整体反而更稳定,响应更快。
回想起整个项目最让我有成就感的瞬间,不是小车第一次完整跑完一圈赛道,而是我把方向环和速度环的参数调好之后,故意在赛道上放了几张白纸模拟“障碍”,小车稳稳绕过去的画面。那一刻才真正感受到,之前折腾的每一个坑、看过的每一篇数据手册都没有白费。
如果让我给正在做类似项目的同学一个建议:不要怕踩坑,但要学会把坑记录下来。我给每个调试失败的操作都拍了照片、写了笔记,最后整理成了一份十几页的调试日志,这比你自己一遍一遍试错要值钱得多。巡线小车只是一个开始,它把嵌入式开发最核心的几块技能串了起来——GPIO操作、中断系统、定时器PWM、串口通信、PID控制,这些基础打牢了,后面玩什么开发板、做什么项目,都会顺畅很多。
本文还有配套的精品资源,点击获取