STM32循迹避障小车制作全攻略:从硬件接线到代码控制逻辑 简介本资源是一套完整的基于STM32的循迹避障小车开发资料面向嵌入式初学者、电子类专业学生及智能硬件爱好者解决智能小车从硬件设计、仿真验证到固件开发的全流程实践难题。压缩包含332个文件总大小3.66MB涵盖120个.h头文件与113个.c源码文件构成完整STM32控制逻辑含ADC采样、PID循迹、超声波避障等核心算法24个.s启动文件及5个原理图SchDoc与PCB工程文件辅以Proteus仿真工程、Keil项目配置uvprojx/uvoptx、Hex烧录文件及调试配置dbgconf结构清晰便于分模块学习与调试。已有7945人下载学习资料包含可直接编译运行的工程框架、传感器信号处理与电机驱动代码、典型避障与循迹策略实现以及关键外设初始化与中断服务例程显著降低入门门槛助力快速掌握ARM Cortex-M平台开发、嵌入式实时控制与软硬协同调试能力。 把积灰的STM32F103C8T6最小系统板翻出来连上TB6612电机驱动、四个TCRT5000循迹探头、一个HC-SR04超声波模块捣鼓了不到一个周末一辆能沿着黑线跑、遇到障碍物自己绕开的循迹避障小车就这么落地了。市面上的STM32小车资料很多但大多只讲单个模块真正要把循迹和避障组合到一辆车上还是有不少坑要踩。这篇文章把我整理过的STM32循迹避障小车资料重新过一遍从硬件选型、接线原理、控制逻辑到调试排错一次性讲清楚。适合正在做课程设计、准备电子竞赛或者纯粹想体验单片机项目的朋友参考。1. 项目整体设计与硬件选型思路1.1 先想清楚小车要干什么做项目的第一步不是买器件也不是写代码而是把需求定清楚。我这个循迹避障小车的目标很明确在室内浅色地面上沿着黑色轨迹线行驶当前方出现障碍物时能够自动绕开并回到原来的轨迹上继续跑。听起来简单但“循迹”和“避障”两件事在逻辑上是有冲突的——循迹要求小车贴着线走避障要求小车离开线绕行。如果不先把这两者的优先级处理清楚写出来的程序就会变成小车一会儿追线一会儿躲障碍最后原地打转。我采用的方案是避障优先。因为障碍物直接关系到安全问题一旦超声波检测到前方距离小于阈值就立即进入避障状态等障碍物绕过去之后再通过循迹传感器找线、回线。这个“避障优先、循迹保底”的思路是这个小车能不能稳定跑起来的关键。另外还要限定工作场景地面不能太反光轨迹线用黑色电工胶带贴障碍物放在轨迹前方速度不要追求极限这些都是为了降低调试难度。第一次做的话先让它在简单场景下跑顺比一上来就挑战复杂赛道更实际。1.2 STM32主控为什么选F103C8T6主控选型上我直接选了STM32F103C8T6。这颗芯片算是国内入门单片机的“默认选项”了72MHz主频32位Cortex-M3内核64KB Flash、20KB RAMGPIO、定时器、ADC、USART、I2C、SPI这些外设一应俱全做一辆循迹避障小车绰绰有余。现在F103C8T6的国产兼容板价格很低十几块钱就能买到坏了也不心疼非常适合反复折腾。很多朋友会纠结用标准外设库还是HAL库。我的看法是两个都能做出小车但新手更推荐从标准库入手因为网上老教程、老例程大量基于标准库遇到问题容易搜到答案。如果你喜欢用STM32CubeMX初始化那也可以用HAL库原理是一样的。我这篇的思路和代码框架以标准库为主但关键逻辑换到HAL库也很容易。F103系列性能虽然不像F4那样猛但它的定时器足够输出多路PWM串口足够调试打印GPIO足够接传感器完全不会成为瓶颈。选它的另一个原因是生态成熟从最小系统板到电机驱动模块基本上是插上就能用的程度。1.3 硬件清单与供电方案完整的硬件清单我整理成了表格照着买基本不会错。别小看这些基础物料选错一个就可能让调试时间翻倍。部件型号/规格数量作用主控板STM32F103C8T6最小系统板1核心控制电机驱动TB6612FNG模块1驱动直流电机电机N20减速电机或TT马达2提供动力万向轮金属/尼龙万向轮1支撑车体循迹传感器TCRT5000四路模块1检测黑线避障传感器HC-SR04超声波模块1检测障碍物云台舵机SG90舵机可选1超声波扫描方向电池7.4V锂电池或2节186501组整机供电降压模块AMS1117-3.3或MP15841给STM32供电车架亚克力三层板1固定所有模块供电这块是新手最容易翻车的地方。我的建议是电机电源和单片机逻辑电源一定要分开但必须共地。电池电压直接进TB6612的VM脚给电机供电STM32则通过降压模块单独供电不要把单片机直接接到7.4V上也不要用L298N板载的5V稳压给单片机供电。L298N的线性稳压压差大、输出不稳定电机一启动会把单片机电压拉低轻则复位重则烧板。共地就是所有模块的GND最终要连在一起否则电平参考不一致信号会乱跳。如果实测电机启动瞬间电压跌落明显就在电机电源两端并联一个470uF电解电容和一个0.1uF瓷片电容能吸收不少尖峰。2. 传感器和电机驱动模块接线与原理2.1 四路循迹探头TCRT5000循迹的核心是TCRT5000红外反射传感器原理很简单模块上的红外发射管持续发出红外光光线照到白色地面会被反射回来接收管导通如果照到黑色轨道线红外光被吸收接收管状态变化。板载比较器会把这种变化转换成数字高低电平所以单片机只需要读GPIO就能知道当前某个探头下面是不是黑线。四路探头在车头的排列方式要讲究。我是按“左外、左内、右内、右外”排成一排每个探头间距大概1.5cm左右让中间的探头间距比黑线宽度稍小。这样小车跑在线上时至少有两个探头能压到黑线判断左右偏差才准确。接线方面VCC接3.3VGND接GNDOUT分别接STM32的PA0到PA3。注意不同模块输出的有效电平可能不一样有的模块黑线输出低电平有的输出高电平。上手第一件事就是拿万用表测一下黑线时对应引脚是多少伏白线时是多少伏然后在代码里把逻辑写对别想当然。模块背面一般有一个蓝色电位器用来调节比较器阈值。调的时候把车放在黑线上用小螺丝刀慢慢旋转直到对应探头输出翻转。再放到白地上试一次两边都能稳定区分就算调好了。探头离地高度控制在0.8到1.5cm之间太高了红外光发散容易误判太低了车一震就蹭地也会误判。2.2 超声波避障模块HC-SR04避障我用的是HC-SR04超声波模块成本低、原理直观。模块工作时先给Trig引脚一个10us以上的高电平触发信号模块内部会发出8个40kHz的超声波脉冲然后等待回波。当Echo引脚从低电平变成高电平说明已经发出去了等到高电平结束高电平持续的时间就是超声波往返的时间。距离的换算公式是距离(cm) 高电平时间(us) / 58这个58us/cm是怎么来的呢声速在空气中约为340m/s也就是0.034cm/us往返距离要除以2。所以距离 时间 * 0.034 / 2 时间 / 58.8简化成除以58误差不大工程上够用。接线很简单Trig接PA4Echo接PA5VCC接5VGND接GND。这里要特别提醒一句很多HC-SR04的Echo输出是5V电平虽然STM32很多引脚标称兼容5V但为了稳妥最好在两个引脚之间串一个电阻分压或者直接买电平转换版本。我一开始图省事直接接后来偶尔出现Echo引脚读数异常加了电阻分压之后问题解决。还有超声波模块的探测周期不要低于60ms也就是说不要在短时间内连续触发否则上一次的余波会干扰下一次的测量数据会乱跳。程序里我习惯每隔80ms测一次。2.3 电机驱动TB6612与PWM驱动电机用的是TB6612FNG而不是很多人想象的L298N。TB6612是MOS管驱动导通压降小、发热低、体积小非常适合5V到12V的小功率直流电机。L298N虽然便宜、电流大但是三极管压降太大跑一会儿模块烫手PWM频率上去了噪声也大做小车体验并不好。TB6612的控制方式很清晰每一路电机有两个方向引脚和一个PWM引脚。比如左电机对应AIN1、AIN2和PWMAAIN11、AIN20时电机正转AIN10、AIN21时反转两个都是1时刹车两个都是0时惰性滑行。还有一个STBY引脚必须拉高否则整个驱动芯片不工作。这个STBY是新手最容易漏掉的地方电机不转先查它。我使用TIM2的CH1和CH2输出两路PWM频率设置在10kHz左右。为什么选10kHz这个频率人耳基本听不到太大的电机噪声同时对于常见的直流减速电机来说开关损耗也不大。频率太低会有刺耳的啸叫频率太高驱动模块发热。接线如下PWMA接PA8TIM1_CH1AIN1接PB12AIN2接PB13PWMB接PA9TIM1_CH2BIN1接PB14BIN2接PB15。如果工程里TIM1配置比较复杂也可以改用TIM2的CH1/CH2对应PA0/PA1但PA0/PA1又被循迹传感器占了所以要统一规划引脚。2.4 完整接线表与下载口把所有模块的接线合并成一张总表现场对照着插能省很多事。模块模块端引脚STM32引脚TCRT5000四路OUT1~OUT4PA0、PA1、PA2、PA3HC-SR04TrigPA4HC-SR04EchoPA5TB6612PWMAPA8TB6612AIN1PB12TB6612AIN2PB13TB6612PWMBPA9TB6612BIN1PB14TB6612BIN2PB15TB6612STBY3.3VST-LinkSWDIOPA13ST-LinkSWCLKPA14ST-LinkGNDGNDST-Link3.3V3.3V下载调试直接用ST-Link的SWD模式就行只用SWDIO、SWCLK、GND、3.3V四根线。很多最小系统板上默认有RESET引脚也可以接上ST-Link的RST方便调试时一键复位。要注意的是SWDIO和SWCLK分别占用了PA13和PA14编程时别复用这两个引脚做其他功能。同样的道理尽量避开PB3、PB4这些默认JTAG复用脚否则下载和运行都容易出奇怪问题。3. 控制逻辑与代码框架3.1 状态机循迹和避障怎么切换程序的核心不是“背代码”而是状态机设计。我定义了几个状态TRACK循迹、AVOID_TURN避障转向、AVOID_FORWARD避障前进、BACK_TO_LINE找线回正。默认状态是TRACK循环里做两件事先测量前方距离如果距离小于阈值并且连续几次都检测到障碍才切到避障状态否则老老实实执行循迹逻辑。为什么要连续几次检测到障碍而不是一次就响应因为超声波偶尔会有尖峰误报或者小车经过一个很小的纸片、一颗螺丝压根不算障碍一次误判就会让小车主线都不走。我习惯连续5次检测到距离小于阈值才触发避障相当于一个软件滤波简单有效。每次循环间隔20ms5次就是100ms对低速小车来说响应速度完全够。进入避障状态之后也不能一直卡在里面。每个状态都要有超时保护比如AVOID_TURN最多转1秒超过就强制切换到下一步BACK_TO_LINE状态下如果超过3秒还没找到黑线就原地旋转找线。这样即使避障策略出了问题小车也不会因为死循环而失控。3.2 循迹转向的差速计算循迹转向我用了最简单的比例控制。四路传感器输出读进来之后我把它看成一个bit序列比如第1路到第4路分别是L1、L2、R1、R2。通过编码映射可以知道黑线在车的哪一侧传感器状态L1 L2 R2 R1含义目标转向0 1 1 0直线直行1 1 0 0车偏右左转0 0 1 1车偏左右转1 0 0 1十字或复杂岔路直行0 0 0 0完全离线找线这里要注意不同的循迹模块输出极性可能相反代码里要按实际读取结果调整。然后我定义一个误差量error比如最理想是误差为0黑线偏右时error为正偏左时error为负。差速计算公式很简单left_speed BASE_SPEED Kp * error; right_speed BASE_SPEED - Kp * error;误差为正说明车偏右需要加大左轮速度、减小右轮速度车就会向左修正。Kp是比例系数调得太大车子会左右摆动调得太小车子反应迟钝拐弯不及时。我从Kp2开始调再根据实际跑线情况慢慢放大。还要给速度设定基数下限因为PWM占空比太低时电机可能根本不转尤其是带减速箱的电机启动扭矩不够会抖动。比如BASE_SPEED设定成5000到1000的千分比左右差值最大200这样最低速度也有300能保证电机持续转动。如果PWM精度更高可以把占空比映射成百分比逻辑不变。3.3 避障绕行的判断与动作最简单的避障策略是检测到障碍停车右转固定时间前进固定时间左转固定时间恢复循迹。这种“盲绕”在固定场地里可能有效但换个环境就大概率翻车因为每次障碍物大小和位置不一样固定时间根本绕不过去。我更推荐加一个SG90舵机云台让超声波探头可以左右转。避障时先控制舵机依次指向左侧、前方、右侧分别测出距离选距离最大的方向作为绕行方向。比如左侧距离最大就左转右侧最大就右转。然后前进一段时间同时不断检查循迹传感器的状态一旦发现中间两个探头压到黑线说明小车已经回到轨迹附近立即退出避障状态回到TRACK状态。这样比固定时间绕障灵活多了。绕行过程中还要设置一个总超时时间比如3秒。如果转了这么久还没找到线说明很有可能已经远离轨迹这时候要做的是原地旋转找线而不是继续往前乱跑。找线逻辑同样用循迹传感器判断中间两个探头同时压线时停止旋转切换回循迹。3.4 完整代码骨架下面给出一个可以运行的代码骨架。这个骨架把状态机、循迹和避障的关键逻辑都放在里面具体的GPIO和定时器初始化可以用STM32CubeMX生成也可以按你的工程手动配置。先看主循环#define TRACK_THRESHOLD 25 #define OBSTACLE_CNT_TH 5 typedef enum { TRACK, AVOID_TURN, AVOID_FORWARD, BACK_TO_LINE } State; State state TRACK; uint8_t obstacle_cnt 0; int main(void) { SystemInit(); GPIO_Config(); TIM_PWM_Config(); USART1_Config(); OLED_Init(); // 可选 while (1) { if (state TRACK) { uint16_t dist get_distance(); if (dist TRACK_THRESHOLD) { if (obstacle_cnt OBSTACLE_CNT_TH) { obstacle_cnt 0; state AVOID_TURN; set_left_motor(400, BACKWARD); set_right_motor(400, FORWARD); // 开始右转绕障 set_avoid_timeout(250); } } else { obstacle_cnt 0; track_line(); } } else if (state AVOID_TURN) { if (avoid_timeout_expired()) { state AVOID_FORWARD; set_left_motor(600, FORWARD); set_right_motor(600, FORWARD); set_avoid_timeout(800); } } else if (state AVOID_FORWARD) { // 不断读循迹找到黑线就回TRACK if (read_track_sensors() 0x06) // 中间两路压线 { state TRACK; } else if (avoid_timeout_expired()) { state BACK_TO_LINE; set_left_motor(500, FORWARD); set_right_motor(500, BACKWARD); // 原地旋转找线 set_avoid_timeout(1500); } } else if (state BACK_TO_LINE) { if (read_track_sensors() 0x06) { state TRACK; } else if (avoid_timeout_expired()) { state TRACK; // 防止彻底跑飞先回循迹再说 } } delay_ms(20); } }循迹函数和超声波函数也要补上。循迹函数里读四路传感器换算误差然后输出PWMint get_error(uint8_t sensors) { switch (sensors) { case 0x06: return 0; // 0110 直线 case 0x04: return -1; // 0100 偏右 case 0x02: return 1; // 0010 偏左 case 0x0C: return -2; // 1100 大偏右 case 0x03: return 2; // 0011 大偏左 default: return 0; } } void track_line(void) { uint8_t sensors read_track_sensors(); int error get_error(sensors); int16_t left_speed BASE_SPEED KP * error; int16_t right_speed BASE_SPEED - KP * error; if (sensors 0x00) { // 完全离线保持上一帧的方向修正 left_speed (last_error 0) ? BASE_SPEED 200 : BASE_SPEED - 200; right_speed (last_error 0) ? BASE_SPEED - 200 : BASE_SPEED 200; } else { last_error error; } if (left_speed 300) left_speed 300; if (right_speed 300) right_speed 300; if (left_speed 1000) left_speed 1000; if (right_speed 1000) right_speed 1000; set_left_motor(left_speed, FORWARD); set_right_motor(right_speed, FORWARD); }set_left_motor、set_right_motor、get_distance这些函数对应你工程里的引脚操作实现起来不难。这个骨架不是最终完整工程但把主流程表达得很清楚照着抄完基础就能跑。4. 调试方法、常见问题与避坑4.1 调试环境与串口打印开发环境方面我用的还是Keil MDK5加ST-Link。Keil需要安装STM32F1系列器件支持包ST-Link要装好驱动。工程可以用标准库新建也可以直接用STM32CubeMX生成再改逻辑。很多新手卡在标准库新建工程这一步最容易出问题的是启动文件、器件宏定义和时钟配置这几个地方错一个编译就过不去。我的建议是第一次做就别执着于纯手工搭工程用CubeMX生成一个带串口和定时器的初始化工程然后在这个基础上加代码效率高很多也少踩坑。等熟悉了再去手工建标准库工程。串口打印是我调试小车用最多的工具没有之一。把USART1重定向到printf然后在不同状态下打印距离值、传感器值、当前状态、PWM输出小车跑起来之后连上USB-TTL模块到电脑实时看数据流问题定位会快很多。比如小车总是往右偏你不需要追着车跑直接看左右PWM值就知道是不是速度分配的问题。超声波距离异常时也能看到是数据总是跳变还是固定错误。串口通信的电平要留意如果USB-TTL是5V输出而STM32串口不兼容5V最好加电阻分压或者干脆选3.3V版本的USB-TTL。4.2 常见故障速查表把我在调试中遇到过的典型问题整理成了一张速查表遇到类似现象可以直接对着查现象可能原因排查方式电机完全不转STBY没拉高电源没共地PWM占空比太低先查STBY是否3.3V再测电机两端电压只有一侧电机转TB6612对应通道接错另一侧电机线松动交换左右电机PWM验证硬件电机抖动或异响PWM频率太低占空比过低电池电压不足把PWM频率调到10kHz提高BASE_SPEED循迹传感器输出不变供电问题探头离地太高电位器没调好万用表测输出重新校准小车走S形Kp过大左右电机速度不一致轮子打滑串口打印左右PWM降低Kp检查轮子超声波距离乱跳测量周期太短Echo接线不稳模块供电不足增大间隔到80ms重新接线测试供电跑着跑着单片机复位电机瞬间拉低电源共地不良降压模块扛不住独立供电加电容检查电池接触下载失败/连接不上SWD引脚被复用BOOT0状态不对板子没供电按住复位再点下载检查Keil调试设置4.3 推荐排查顺序我给所有找我咨询小车问题的朋友一个统一的建议先模块后联调先开环后闭环。具体来说下载程序后先跑一个LED闪烁程序确认最小系统和下载链路没问题。然后单独写电机测试程序让两个电机正转反转确认方向逻辑和PWM输出没问题。接着用串口打印循迹传感器的原始值把车分别放在黑线和白地上确认读到的电平正确。再测超声波距离人站在车前晃一晃看数据是否跟真实距离一致。这几步都通过了才进入循迹逻辑调试最后再合并避障。千万别把所有功能一次性写完再拿到地上试。一旦出了问题你根本不知道是传感器坏了、接线错了、还是代码逻辑错了。我见过太多朋友把代码写完就往地上一放小车跑飞了就开始调Kp结果调了半天发现是循迹模块有一路没接好这种情况纯粹是浪费时间。循迹模块的稳定判断建议在断电状态下先把车放到黑线上手动推着走几步看传感器能不能跟上这一步能省很多事。5. 扩展玩法和我的实操心得5.1 状态显示和遥控扩展基础功能跑通之后可以给小车加一块OLED显示屏I2C接口一屏显示当前状态、超声波距离、左右PWM值。调试的时候不用一直连电脑直接低头看小车上的屏幕就行。OLED用的是3.3V供电SDA和SCL分别接STM32的PB7和PB6程序里用软件I2C或硬件I2C都行网上例程很多移植起来不难。再加一个HC-05蓝牙模块的话可以通过手机APP发送指令切换自动循迹、手动遥控、原地旋转等模式。对课程设计来说这些额外功能很容易成为加分项。如果对无线控制有进一步想法可以把ESP8266模块接到STM32串口上通过Wi-Fi和局域网通信在网页上控制小车甚至回传状态数据。不过要提醒一句扩展功能虽然诱人但一定要放在基础功能稳定之后再做。我见过很多项目摄像头、蓝牙、云平台都上了但循迹都没跑稳最后演示的时候翻车得不偿失。5.2 通过编码器加PID闭环基础版小车用开环PWM控制跑直线时如果两个电机转速不一致或者电池电压下降小车会慢慢跑偏。想提高稳定性最有效的升级是给电机换带霍尔编码器的N20减速电机然后用STM32定时器的编码器模式测速再通过PID调节PWM让左右轮速锁定在目标值。编码器A/B相分别接定时器的两个通道STM32的TIM2、TIM3、TIM4都支持编码器接口。读取脉冲数之后速度PID的输出作为PWM占空比。这个升级不复杂但效果立竿见影小车对电池电压变化的抵抗能力强很多跑出来的轨迹明显更直。如果你有足够时间建议直接给小车配上编码器电机从头就做闭环。5.3 我踩过最深的坑最后分享几个真实教训。第一个教训来自供电。早期我用L298N板载5V给STM32供电电机一启动单片机立刻复位整个小车像抽风一样反复重启。后来改成独立的AMS1117降压模块给主控供电再把所有GND共地问题彻底消失。第二个教训是超声波模块没有加测量间隔连续触发导致回波干扰距离数据乱跳小车一接近障碍就乱转。后来在测距函数里加了80ms的最小间隔数据稳定下来。第三个教训是循迹探头高度没有固定好跑了几圈之后支架松动探头离地高度变了小车直接冲出跑道。后来用热熔胶把支架和传感器全部固定住才算安心。做小车这件事最花时间的往往不是写代码而是跟硬件较劲。希望这份STM32循迹避障小车资料能帮你少走弯路把时间花在真正有意义的调参和优化上。本文还有配套的精品资源点击获取