STM32循迹避障小车完整实战:从硬件搭建到调参避坑
2026/9/1 9:16:06 网站建设 项目流程

简介:基于STM32F103C8T6的循迹避障小车完整工程源码包,面向嵌入式入门学习者与电子设计竞赛备赛学生,能够帮助理解循迹、避障两种工作模式的代码组织与实时切换思路。压缩包共292个文件、约7.73MB,内部除.c/.h源码外,还包含.uvproj/.ewp工程文件、.hex烧录文件、.bat批处理脚本及.map/.axf等编译链接产物,各模块按功能拆分并配有详细注释,直接打开工程即可阅读和编译。主函数中循迹循环与避障循环彼此独立,注释掉一个即可选用另一个,已实测可正常运行;源码结构清晰,适合在此基础上二次开发,如增加蓝牙遥控、舵机转向等。目前已有21867人学习下载,对有课程设计或竞赛项目需求者是一份易于上手的参考实现。 把基于STM32F103C8T6的循迹避障小车标上“完成”两个字,看起来像是收工了,但真正调过车的人都知道,这恰恰是最磨人的阶段才刚刚开始。我手里的这版小车,功能上确实做到了循迹和避障都能跑,但每一个“能”字背后,都是PWM波形、传感器阈值、电源纹波和状态机逻辑反复拉锯的结果。

这篇记录不打算贴整包代码就完事,而是把这辆小车的设计思路、硬件选型、代码结构、调参过程和踩过的坑完整梳理一遍,给准备做同类型项目的朋友一份可以直接参照的“完成版”路线图。如果你手头正好有一块STM32F103C8T6最小系统板,或者正被课程设计里“循迹+避障”的需求折磨,这篇内容应该能帮你少走不少弯路。我不讲那些大而全的理论,只讲从硬件搭建到路上实测这段最真实的经历。

1. 项目拆解:循迹避障小车到底考的是什么

1.1 两个任务背后其实是三套机制

循迹和避障看起来是两个功能,真正做完以后你会发现,它们是三套机制在同时工作:感知、决策、执行。

感知层依靠两类传感器。循迹我用的是4路TCRT5000红外对管,靠黑线和白底对红外光的反射率差异来检测路径;避障用的是HC-SR04超声波模块,通过声波往返时间测出前方障碍物距离。决策层是STM32F103C8T6读取传感器状态,按预设逻辑决定下一步动作;执行层则是通过PWM控制两个直流减速电机的转速差,实现直行、转向、后退和原地掉头。

很多新手一上来就到处找“循迹函数”和“避障函数”,但真正需要先理清楚的是这三层之间怎么配合。比如车速越快,传感器采样周期就要越短;超声波测距本身有响应时间,小车不能等到撞上了才做反应;循迹传感器离地高度偏差两三毫米,判定结果就完全不一样。这些都属于机制设计层面的问题,不是单纯堆代码能解决的。

1.2 为什么主控选STM32F103C8T6

市面上做小车的主控很多,51、Arduino、ESP32都有人用。我选STM32F103C8T6不是因为它最便宜,而是这个选择在性能和折腾空间之间取了一个很实用的平衡。

先看性能。STM32F103C8T6是Cortex-M3内核,主频最高72MHz,Flash 64KB,RAM 20KB。对一辆巡线小车来说,“循迹+避障”并行处理,还要输出多路PWM,硬件定时器刚好够用。我的实际资源分配是:TIM1输出两路PWM控制左侧电机,TIM2输出两路控制右侧电机,TIM4给超声波测距做计时,剩下的GPIO读传感器。算下来资源非常紧凑,但不至于捉襟见肘。

更关键的是生态。这块芯片的资料和例程多到爆炸,中文手册、标准外设库、HAL库版本都齐全。网上关于定时器PWM输出、串口调试的案例数不胜数,遇到问题基本都能搜到解决办法。对做一个课程项目或者入门作品来说,这种可查可问的生态比芯片本身的纸面参数重要得多。

1.3 功能优先级:循迹为主、避障为辅

这辆小车的功能定义是“循迹避障”,听起来是并列关系,但实际设计里必须明确优先级。我的方案是:无障碍时按循迹模式跑,遇到障碍物时临时切入避障逻辑,绕过障碍后再重新寻线。

这个设计意味着主循环不能简单写成“循迹”和“避障”两个if分支互相独立,而要有一个状态变量标记当前处于什么模式。状态切换写得好不好,直接决定小车在真实路径上稳不稳定。后文我会专门用一节来讲这套状态机的具体实现。

2. 硬件底座的搭建复盘:从电机驱动到传感器布局

2.1 电机驱动模块选L298N还是TB6612

循迹避障小车最常用的两种驱动方案是L298N模块和TB6612FNG模块,两者我都用过,结论比较明确:

对比项L298NTB6612FNG
逻辑电压5V2.7~5.5V
输出压降较大,约2V较小
体积很小
发热明显较低
价格便宜稍贵
适合场景大功率电机小型减速电机

我的小车用的是两个N20减速电机,标称电压6V,电流不大。最初装的是L298N,结果发现模块压降把电机端电压吃掉不少,高速直行时转速提不上去,转弯响应也偏慢。后来换TB6612FNG,同样一个PWM占空比,车速明显不一样。所以如果你用的是小功率电机,建议直接上TB6612FNG,电源利用率和动态响应都会好很多。

2.2 循迹传感器的数量和安装位置

循迹原理并不复杂:红外发射管发出红外光,白色路面反射强,黑色轨迹线吸收强,接收管收到的反射量不同,输出电平就不一样。我用的是4路TCRT5000模块,一字排开安装在车头下方。

这里有个关键点:传感器间距要跟轨迹线宽度匹配。如果线宽2cm,传感器探测中心点间距最好在1.5cm到2cm之间,这样才能保证小车压线行驶时,至少有一两路传感器落在黑线上。间距太大,转弯时容易四路全部离开黑线;间距太小,又对直线上的微小偏移过于敏感。我最终把4路传感器总宽度控制在底盘宽度的一半左右。

安装高度同样有讲究。TCRT5000的有效检测距离通常在0.5mm到10mm之间,实际使用中离地5mm到8mm效果最好。太高反射信号衰减,误判率上升;太低又容易蹭到地面凸起。我的做法是用铜柱把传感器支架悬空固定在底盘前面,再通过模块上的电位器调节灵敏度,直到“压到黑线时输出电平翻转”这个临界状态稳定可靠。

2.3 超声波模块的避障视角问题

避障我选了HC-SR04超声波测距模块,测距范围大约2cm到400cm,对本项目来说足够用。它的工作方式是:给Trig引脚一个10微秒以上的高电平脉冲,模块自动发出8个40kHz的超声波,然后Echo引脚输出一个高电平,高电平持续时间就是声波往返的时间。距离 = 高电平时间 × 声速 / 2。

不过超声波模块有个特点:它的探测波束角比较大,测到的是前方一个圆锥形区域内的最近物体,分辨不了障碍物偏左还是偏右。为了补上这个方向盲区,我做了两件事。

一是把超声波模块安装在车头正前方偏上位置,避开前面循迹传感器支架的遮挡。二是在程序里做了分档判断:距离大于25cm按正常循迹跑;小于25cm且大于12cm时减速并准备转向;小于12cm时直接执行避障转向。这样单个超声波模块虽然只能测距,但配合固定转向逻辑,已经能应付大多数简单障碍场景。

3. 代码核心逻辑逐段拆解:PWM、测距与状态决策

3.1 引脚分配与定时器PWM配置

这辆小车的引脚分配是这样的:

  • 左电机PWM:PA8(TIM1_CH1)、PA11(TIM1_CH4)
  • 右电机PWM:PA0(TIM2_CH1)、PA1(TIM2_CH2)
  • 循迹传感器输入:PB0~PB3
  • 超声波Trig:PB4,Echo:PB5
  • 串口调试:PA9(TX)、PA10(RX)

为什么PWM要交固定时器输出,而不是用普通GPIO翻转模拟?因为直流电机调速需要频率稳定的PWM波形,用GPIO手动翻转会导致占空比不稳定,而且极度浪费CPU时间。STM32的定时器自带PWM输出,配置好周期和占空比后,输出完全由硬件完成,主循环只需要改比较寄存器的值。

配置用的标准外设库,核心代码大致是这样:

// TIM1 PWM输出初始化关键部分 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 时基配置:72MHz / 72 = 1MHz,计数1000次 => 1kHz PWM TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 通道1 PWM模式1 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 高级定时器主输出使能,少了这一步PA8永远没有波形 TIM_CtrlPWMOutputs(TIM1, ENABLE);

TIM1属于高级定时器,除了配置CCER里的输出使能外,还要配置BDTR寄存器里的MOE主输出使能位。我调这个坑调了很久,后面调试章节会细说。

3.2 循迹状态判定:从4路传感器到方向决策

4路循迹传感器从右到左编号为R1、R2、L2、L1,输出电平是“检测到黑线时为低”。我把4路信号组合成几个关键状态:

  • 全白(0000):车在直线段上,直行
  • 左偏(0001或0011):车头偏右,需要左转修正
  • 右偏(1000或1100):车头偏左,需要右转修正
  • 中间压黑(0110等):正常行驶,小幅修正方向
  • 全黑(1111):遇到十字交叉或终点,停止或进入特殊处理

代码实现时,优先判断L1、R1这两个边缘传感器,因为它们一旦压黑,说明小车已经偏得比较厉害,必须加大转向时的PWM差值。中间两路传感器对应小幅修正。这种分级修正比单阈值反馈稳定得多,也更容易调参。

3.3 超声波测距与模式切换

超声波测距的代码分两步:先发送Trig脉冲并测量Echo高电平时间,再把时间换算成厘米。

// HC-SR04测距函数,返回距离(cm) float HCSR04_GetDistance(void) { uint32_t t1, t2; GPIO_SetBits(GPIOB, GPIO_Pin_4); Delay_us(12); GPIO_ResetBits(GPIOB, GPIO_Pin_4); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0) {} t1 = GetTimerTick(); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 1) {} t2 = GetTimerTick(); return (t2 - t1) * 0.034 / 2.0; }

高电平时间测量我用的是查询方式。逻辑上没问题,但要注意:如果距离很近,Echo高电平时间很短,主循环里一旦有耗时操作就容易漏掉跳变。所以我单独封装了测距函数,测距期间不让其他任务干扰。

模式切换方面,我在主循环里维护了一个mode变量:MODE_TRACK表示循迹,MODE_AVOID表示避障转向。循迹模式下每个循环先读一次超声波,距离小于阈值就切到MODE_AVOID;在MODE_AVOID里小车不再读循迹传感器,按固定差速转向,同时持续测距,直到前方障碍物消失再切回MODE_TRACK。用状态机代替if嵌套,逻辑清晰,后期加功能也不需要大改主循环。

3.4 转向策略:差速控制是核心

差速转向的原理不复杂:左右轮速度不一样,车体就会向慢的一侧转弯。我的左右电机各由两路PWM控制,一路用于正转,一路用于反转,组合起来能实现正转、反转、停止。

最简单的转向是左转时左轮减速、右轮保持。但实测发现,直行速度较高时,这种单侧减速的转向半径偏大,急弯容易冲出去。后来我改成“一侧正转一侧反转”的原地转向:需要左转时,左轮反转、右轮正转,差速最大,小车几乎原地掉头。缺点是地面太滑时轮子会空转。最终我在代码里留了一个转向模式切换参数,根据比赛场地情况手动选择,直线多就单侧减速模式,急弯多就原地转向模式。

4. 实测调试记录:阈值、速度和干扰的反复拉锯

4.1 TIM1启用PWM后引脚没波形的坑

这是本项目里最莫名其妙的一个问题。TIM1配置完成后,用逻辑分析仪看PA8引脚,始终没有波形。检查时钟、GPIO复用、CCR和ARR全都没问题,最后翻了芯片参考手册才发现:TIM1和TIM8是高级定时器,PWM要真正输出到引脚,必须设置BDTR寄存器的MOE主输出使能位。不加这一步,定时器其实在工作,但输出引脚一直处于无效电平。

解决办法就是在初始化函数末尾加一行主输出使能操作。这个坑只针对高级定时器,普通定时器不会遇到。以后但凡用TIM1或者TIM8做PWM,记住先确认MOE,不然排查起来非常浪费时间。

4.2 循迹传感器阈值的现场标定方法

TCRT5000模块上的电位器不是随便拧的。我的标定步骤是:把小车放到纯白色路面上,观察模块指示灯,拧电位器直到指示灯刚好翻转,记住临界位置;再把小车放到黑线上,确认指示灯能够反向翻转。反复几次,找到一个“白面出一种电平、黑线出另一种电平”的稳定窗口。

关键还在于,标定必须在实际光照环境下做。室内灯光、太阳光、地面材质差异都会影响红外反射强度。我的习惯是标定完后不马上封箱,先在跑道上推着走一圈,看四路传感器在压线、离线、过弯时是否都能可靠翻转。如果某一路反应慢,就单独微调那个模块的电位器,别只调一路就默认全车都好用。

4.3 速度与采样周期的匹配

代码刚跑通的时候,小车在直线段表现还可以,一进弯道就往外飘。后来排查发现是车速太快,而主循环每执行一次循迹判断的时间相对固定,等于传感器采样频率不够。车速上去后,同样的采样周期对应更长的行驶距离,弯道里车已经冲出一截才执行转向,自然就飘。

我做了两个调整:一是把基础PWM占空比从70%降到50%左右,二是压缩测距函数里不必要的延时。最终测下来,车速大约0.3m/s时稳定性最好,弯道也能顺畅通过。这个数值不一定是标准答案,跟你用的轮径、电机减速比、底盘重量都有关系,但调试思路是一致的:先降速跑通,再逐步提速找极限。

4.4 电源波动引发的传感器误触发

有个现象很有代表性:电池电压充足时小车跑得很好,跑一会儿电压下降,超声波测距值就开始跳变,偶尔还会误判成“前方有障碍”然后原地打转。用万用表测模块供电脚,发现电压跌落明显。

原因是超声波模块和电机驱动共用一组电源,电机启动瞬间电流很大,把电源电压拉低了几百毫伏。单片机还能工作,但超声波模块对电压波动比较敏感,于是产生误触发。解决办法是把电机电源和控制电源分开布线,或者至少加大容量电解电容做储能滤波。我用的是一个470uF电解电容并联一个100nF陶瓷电容,并尽量靠近电源输入端放置。电容加上之后,电机启动时电压跌落幅度明显减小,误触发基本消失。

5. 项目收尾复盘:这套平台还能延展出什么

5.1 低成本可扩展的功能模块

这套小车的底层平台其实是一个标准的移动机器人底盘,传感器和控制逻辑都可以继续扩展。循迹传感器可以换成灰度传感器阵列,识别灰度和颜色,实现更精确的线检测;超声波可以并排加两个,用两端测距值差来判断障碍物偏左还是偏右;通信方面,串口预留出来,后续接蓝牙模块或无线透传模块就非常方便,手机远程遥控只需要改一个模式状态。

算力方面,F103C8T6的64KB Flash在跑完当前逻辑后还有不少余量。我个人的建议是,如果加OLED显示和状态机日志,辅助调参会非常舒服,跑起来也不会占用太多资源。

5.2 对新手做同类项目的几点建议

第一,不要并行推进硬件和软件。先把底盘拼好,确认电机能转、传感器能读到值,再开始写逻辑。第二,调参时一次只调一个变量,比如先把循迹跑顺,再上避障;上来就同时调速度和阈值,出了问题完全分不清是哪个环节。第三,务必留串口日志。我在调试时通过串口把传感器状态、mode变量、PWM占空比实时输出,很多偶发问题都是靠日志定位的,单靠看小车跑姿猜原因太难了。

把“完成”标在项目名里,对我来说更像是一个阶段句号。这版到此为止,但下一版随时可以从现在这辆车上长出来。希望这篇记录,对正在做同款小车或者正准备入坑STM32小车项目的朋友,能有一点实际的参考价值。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询