STM32智能小车红外循迹实战:原理、硬件与代码全解析

发布时间:2026/9/2 7:52:29
STM32智能小车红外循迹实战:原理、硬件与代码全解析 简介面向STM32入门及智能车爱好者的红外循迹小车完整工程基于HAL库实现覆盖GPIO输入配置、外部中断、PID调速和UART串口监控等关键开发环节。项目以四个红外传感器检测路面黑线为核心详细演示硬件连接、中断服务函数编写与电机纠偏逻辑既适合毕业设计参考也适合竞赛前快速搭建原型。压缩包共72个文件以45个.h头文件与18个.c源文件为主配合Keil工程配置.uvprojx/.uvoptx、CubeMX初始化文件.ioc、启动文件.s、hex烧录文件及一个bat清理脚本整体仅515KB目录结构清晰便于按模块阅读与修改代码采用模块化封装注释完整硬件引脚需根据实际连接调整门槛较低。目前已有5634人学习下载对希望从寄存器编程转向HAL库开发或正在调试循迹算法的读者是一份有实际价值的参考示例。 这个标题一看就知道是玩到第二步了车架、电机、HAL库点灯这些基础已经打通这期给小车装上“眼睛”——红外循迹。红外循迹算是智能小车从“能动”跨向“能自己走”的第一道坎成本低、逻辑直观、调试起来也很有成就感。这篇文章我会把这套方案从原理、硬件选型、CubeMX配置到完整代码逻辑全部拆开讲顺便把我在实际调试中踩过的坑都摆出来给正在搞第一辆循迹小车的朋友一个可以直接抄作业的参考。项目基于STM32F103C8T6最小系统板HAL库开发传感器用TCRT5000五路循迹模块电机驱动是TB6612这套组合在各类竞赛和小车入门里非常常见。1. 项目整体思路与方案选型1.1 红外循迹到底解决什么问题循迹小车的核心任务就一句话让车沿着地面上的一条线自动行驶。这条线通常是黑色胶带贴在白色地面或者反过来白色线贴在深色地面。要实现这个效果小车需要不断检测自己相对黑线的位置偏移然后通过左右轮差速把偏移纠正回来。红外循迹是目前最简单、最便宜、也最适合入门的位置检测方案。它不需要复杂的图像处理不需要大量算力几个传感器加上一个比较器就能给出“在线/离线”的数字信号单片机只需读GPIO电平就能判断当前位置。这也是为什么工创赛、电子设计竞赛、物联网毕设里循迹小车几乎都是红外方案的天下。1.2 为什么选红外而不是摄像头做循迹也有人用OpenMV、K210加摄像头做视觉循迹或是用灰度传感器走模拟量但红外方案在多数场景下依然是最优解方案成本开发难度环境适应性适合场景红外循迹数字量低模块几块钱一个低读GPIO即可怕强光深色/浅色对比明显即可入门、竞赛初赛灰度循迹模拟量中中需要ADC和阈值标定可识别灰度等级需要连续位置反馈时摄像头视觉循迹高高涉及图像处理和通讯受光线影响也比较大但灵活性高需要识别复杂路径、二维码、路障时对小车的“第一版”来说红外数字量方案能在最短时间里跑通闭环控制把差速转向的逻辑练熟。等你把这套玩明白了后续再往视觉方案迁移控制部分的代码照样能用只是换了“眼睛”而已。1.3 从“能跑”到“跑得稳”的三步走我习惯把循迹小车的实现拆成三层感知层红外传感器模块持续检测地面反射输出5路数字信号。决策层单片机根据5路信号组合判断当前相对黑线的位置状态居中、偏左、偏右、丢线。执行层根据位置状态输出对应占空比的PWM波控制左右电机差速转向。很多新手一上来就盯着传感器怎么调或者代码怎么写结果忽略了最关键的决策逻辑设计。事实上决定小车跑得稳不稳的不是传感器精度而是状态判断和转向力度之间的配合。这一步需要在实测中反复调参没有一劳永逸的参数。2. 红外循迹原理读透再动手2.1 TCRT5000反射式红外传感器的工作方式市面上常见的循迹模块核心器件是TCRT5000一个集成了一对红外发射管和光电晶体管的反射式传感器。模块工作的时候发射管持续发出红外光波长约950nm光线照射到地面后发生反射反射光被接收管检测到接收管的等效电阻会随光照强度的变化而变化。如果地面是浅色比如白色红外光反射率高接收管接收到大量反射光模块板上比较器输出低电平同时模块上的指示灯点亮。如果地面是深色黑色红外光被吸收接收管接收不到反射光比较器输出高电平指示灯熄灭。模块上的电位器用于调节比较器阈值实际效果是控制“多黑才算黑”。2.2 灵敏度、安装高度与传感器间距这是调试红外循迹最核心的三个参数也是新手最容易忽略的地方。灵敏度调节模块上蓝色电位器顺时针拧阈值变高传感器更容易判断为“深色”即更严格。判断标准是把传感器悬空对准白色地面指示灯应该亮对准黑线指示灯应该灭。如果两个状态灯没有明显变化就说明阈值不合适。建议在不开车、上电状态下用一张白纸和一截黑胶带分别贴在传感器下面做测试调好再装车。安装高度TCRT5000的推荐检测距离是5mm到20mm超过这个范围信号会呈指数级衰减。装车时传感器表面离地面10mm到15mm比较理想。太近了容易剐蹭地面凸起或胶带接头太远了检测不到。这个高度直接决定了后续循迹的稳定性。传感器间距五路传感器中间的传感器负责“居中”左右两侧的传感器负责“纠偏”。整车探测宽度最好能覆盖黑线的宽度。比如黑线宽度2cm五个传感器一字排开间隔1.5cm到2cm总探测范围6cm到8cm这样既不会漏线也不会因为探测范围太宽而频繁误判。我把安装做个示意图说明车头朝下俯视 车辆前进方向 →上方是车头 [左1] - [左2] - [中间] - [右2] - [右1] \_______ 黑线位置 ______/车正对黑线时中间传感器压线左右两侧传感器在白色地面上这是最理想的状态。如果车偏了黑线就会移动到某一个侧边传感器下方对应传感器输出低电平程序据此判断该往哪边转。2.3 数字量还是模拟量这是个取舍TCRT5000模块一般引出两个信号引脚DO数字量输出和AO模拟量输出。DO是经过LM393电压比较器处理后的数字信号直接输出高/低电平AO输出的是传感器原始电压值反映反射光的强度。对于入门方案直接用DO就够了。但有一点要注意DO输出的“黑/白”判断是二值的无法区分“已经偏了一大半”和“刚刚偏出去一点”。所以纯DO方案只能做离散的转向控制路面偶尔有色差、反光差异时容易抖动。如果你手头模块带AO引脚其实把它接在STM32的ADC上会更爽你可以实时读到每一路的反射强度通过ADC值做更细的偏置判断甚至做比例转向。我建议第一版先用DO跑通等觉得“不够顺滑”了再升级到ADC读取AO代码改动量并不大收益却很直观。3. 硬件接线与CubeMX工程配置3.1 模块选型与接线清单我用的是五路TCRT5000循迹模块它把五个传感器集成在一块PCB上引出5路DO和一个电源接口省去了自己搭电路的麻烦。如果你买的是单个传感器也可以自己拼成多路但焊接和供电都要自己处理工程量会大一些。整体接线参考如下模块/外设信号引脚STM32引脚说明循迹模块 VCC3.3V3.3V直接用STM32板载3.3V供电循迹模块 GNDGNDGND共地循迹 左1 DOOUT1PB0输入循迹 左2 DOOUT2PB1输入循迹 中 DOOUT3PB2输入循迹 右2 DOOUT4PB3输入循迹 右1 DOOUT5PB4输入TB6612 AIN1/AIN2方向PA4/PA5左电机方向TB6612 BIN1/BIN2方向PA6/PA7右电机方向TB6612 PWMAPWMPA8TIM1_CH1TB6612 PWMBPWMPA9TIM1_CH2TB6612 STBY使能PA10高电平使能上面是我的接线习惯你不一定照搬只要记住几个原则传感器DO引脚选用普通GPIO输入即可电机PWM一定要选带定时器输出比较通道的引脚STBY如果接了记得给高电平否则电机永远不转。3.2 CubeMX配置要点GPIO与PWM打开STM32CubeMX芯片选STM32F103C8T6按以下步骤配置配置时钟RCC中HSE选择Crystal/Ceramic ResonatorClock Configuration里把系统时钟拉到72MHz注意APB1总线的定时器时钟是72MHz虽然没有倍频但这里也不需要太高频率APB2也是72MHz。配置GPIO输入PB0到PB4全部设置为GPIO_Input。考虑到模块输出是推挽数字信号不需要内部上拉设置为No pull即可。但如果你的模块是开漏输出型就一定要把内部上拉打开否则读不到高电平。配置PWM以TIM1为例Channel1和Channel2都选择PWM Generation CHx。参数设置Prescaler设为71Counter Period设为999这样PWM频率为72MHz / (711) / (9991) 720Hz频率偏低但适合普通直流电机如果电机有噪音可以调成72-1和999频率就到1kHz了。实际我常用Prescaler71Period1999频率360Hz左右电机响应和噪音平衡得比较好你可以按需微调。配置调试端口如果是买的最小系统板通常只有SWD下载口CubeMX里默认Debug选项为Serial Wire这里必须保持为Serial Wire否则下载一次程序后第二次连不上。生成代码后main.c里需要手动加PWM启动代码。HAL库不会自动启动所有外设需要自己在main函数里调用HAL_TIM_PWM_Start这个操作很多人会忘。3.3 电源问题别忽视五路传感器模块如果每个传感器工作电流按20mA算五路就是100mA加上STM32最小系统板和电机驱动逻辑部分的电流直接从USB取电容易供不上。我建议传感器模块VCC直接接3.3V因为它的比较器输出高电平可能是3.3V接5V虽然也能工作但DO高电平会抬高到5V如果STM32引脚不耐5V就有烧毁风险。动力电源电池单独给TB6612的VM供电逻辑电源VCC接STM32的5V或3.3V具体看模块要求。TB6612的STBY和AIN、BIN都是逻辑电平必须和STM32共地。实测中发现如果电池电压掉到6V以下电机转速明显下降转向力度变小循迹会变得迟钝。所以电池电量也是影响循迹效果的一个隐藏变量。4. 核心代码实现与逻辑讲解4.1 传感器读取与状态映射HAL库读取GPIO就一句HAL_GPIO_ReadPin()但直接裸读五路会让主循环代码变得很乱。我习惯把五路合并成一个uint8_t状态值每一位代表一个传感器#define SENSOR_LEFT1 0x01 // PB0 #define SENSOR_LEFT2 0x02 // PB1 #define SENSOR_MID 0x04 // PB2 #define SENSOR_RIGHT2 0x08 // PB3 #define SENSOR_RIGHT1 0x10 // PB4 uint8_t read_line_status(void) { uint8_t status 0; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) GPIO_PIN_RESET) status | SENSOR_LEFT1; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) GPIO_PIN_RESET) status | SENSOR_LEFT2; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) GPIO_PIN_RESET) status | SENSOR_MID; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) GPIO_PIN_RESET) status | SENSOR_RIGHT2; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) GPIO_PIN_RESET) status | SENSOR_RIGHT1; return status; }注意这里我判断的是GPIO_PIN_RESET因为模块输出低电平表示检测到黑线。如果你的模块逻辑相反把RESET改成SET就行这是最容易被拷贝代码后坑到的地方。4.2 电机控制接口封装电机控制放在一个独立函数里主循环只调用接口不直接操作定时器这样后续想换成闭环速度控制也方便void motor_set(int16_t left_speed, int16_t right_speed) { if (left_speed 0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, left_speed); } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, -left_speed); } if (right_speed 0) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, right_speed); } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, -right_speed); } }这个函数的好处是在整个循迹逻辑里我只需要关心“左轮转多快、右轮转多快”至于方向引脚怎么组合全部封装在底层。后续如果需要加编码器做闭环也只需要替换这个函数的实现不影响上层逻辑。4.3 五种循迹状态的转向策略五路循迹的状态最多有32种组合但实际运行中常见状态就那么几种我用一个switch处理主要状态剩下的统一按丢线急停或原地旋转处理状态位组合含义策略00010中间检测到车正对黑线直行左右轮同速01100偏右黑线在传感器阵列偏左右轮提速/左轮减速向左修正00110黑线在中间偏左轻微右转修正11000偏左黑线在传感器阵列偏右左轮提速/右轮减速向右修正10010黑线在中间偏右轻微左转修正注意状态值来自前面的bit定义比如“中间检测到”就是SENSOR_MID为1其他位为0也就是二进制00010而“偏右”意味这黑线往右跑右侧传感器先压到线对应的是SENSOR_RIGHT1为1这要看你怎么摆放传感器命名。我上面定义RIGHT1是最右边那么SENSOR_RIGHT1置位就是最右侧检测到线车需要往右修正方向就让左侧轮速提高。转向力度建议不要一上来就原地打死先用差速小角度修正。基础版本我这样定义#define BASE_SPEED 680 // 基础占空比60%~70%左右 #define TURN_SPEED 300 // 差速补偿值 void handle_line_following(uint8_t status) { switch (status) { case SENSOR_MID: motor_set(BASE_SPEED, BASE_SPEED); break; case SENSOR_MID | SENSOR_LEFT2: motor_set(BASE_SPEED - TURN_SPEED, BASE_SPEED TURN_SPEED); break; case SENSOR_LEFT2 | SENSOR_LEFT1: motor_set(BASE_SPEED - TURN_SPEED, BASE_SPEED TURN_SPEED); break; case SENSOR_MID | SENSOR_RIGHT2: motor_set(BASE_SPEED TURN_SPEED, BASE_SPEED - TURN_SPEED); break; case SENSOR_RIGHT2 | SENSOR_RIGHT1: motor_set(BASE_SPEED TURN_SPEED, BASE_SPEED - TURN_SPEED); break; default: // 丢线情况先减速尝试原地转向找回黑线 if (status (SENSOR_LEFT1 | SENSOR_LEFT2)) motor_set(-BASE_SPEED, BASE_SPEED); else if (status (SENSOR_RIGHT1 | SENSOR_RIGHT2)) motor_set(BASE_SPEED, -BASE_SPEED); else motor_set(0, 0); break; } }我的策略是检测到中间传感器压线时直行检测到黑线在左侧传感器范围时减小左轮速度同时加大右轮速度让车向左回正反之向右回正。这里的TURN_SPEED不是固定死的它的值直接决定了修正的强度。如果小车过弯时总是冲出赛道就增大TURN_SPEED或减小BASE_SPEED如果小车沿线走蛇形大幅摆动就减小这个差值。4.4 完整主循环逻辑主循环里就三件事读状态、处理循迹、加延时。这个延时比较有讲究太短会导致传感器抖动被直接放大太长会导致转向响应滞后。我试下来2ms到10ms的延时比较合适既不影响实时性又能滤掉一部分传感器毛刺int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET); // STBY使能 while (1) { uint8_t status read_line_status(); handle_line_following(status); HAL_Delay(5); } }这里有几个容易出问题的地方上电后电机没反应先检查STBY引脚有没有拉高。TB6612的STBY不是默认使能的很多模块需要外部拉高才工作。用了HAL_TIM_PWM_Start后如果中途想改变占空比用__HAL_TIM_SET_COMPARE宏不要重新调用PWM_Start。重复Start在某些HAL版本里会导致输出异常。如果电机一边转一边不转检查另一路的PWM通道配置和引脚是否正确。TIM1的CH1是PA8CH2是PA9但如果你的板子有重映射或者引脚冲突需要回到CubeMX里确认。5. 常见问题排查与调试经验5.1 传感器“全亮全灭”的排查五路循迹模块上电后指示灯可能出现的情况和对应解法我整理成一张表现象可能原因排查思路所有灯常亮阈值太低白色地面也被判定为“黑”顺时针微调电位器或检查模块供电是否正常所有灯常灭阈值太高黑线也反射不过去逆时针微调电位器检查安装高度是否过高某一路灯一直不变该路传感器损坏或遮挡把模块拿到白纸和黑线上分别测试判断是硬件还是阈值问题指示灯闪烁不定模块供电不足或电位器处于临界点检查3.3V供电单独接一个稳压模块稍微调一下阈值让状态更分明实测里最常见的问题就是电位器没调好导致传感器把白色地面当成黑线小车一上电就在原地疯狂打转。不要嫌麻烦上电后用一张白纸和黑胶带挨个传感器测试确定每路都能正确区分黑白再装车。5.2 走线抖动与钝化策略循迹小车跑起来之后最常见的现象是“画龙”——车沿着线走但是左右大幅摆动轨迹不是平滑曲线而是锯齿状。主要原因有这几个速度太快转向修正的速度跟不上偏移累积的速度车冲出线才检测到偏差于是大幅回正又冲过中线。解决方法是降低BASE_SPEED。采样频率不适如果主循环延时太短比如1ms以内传感器噪声会被放大系统频繁切换转向状态如果延时太长比如50ms车早就偏出去了。5ms到10ms是经验值。转向修正力度过强TURN_SPEED设置过大车一检测到偏移就猛烈修正导致过冲。可以适当减小差值或者引入“分级转向”思路即检测到中间偏侧时小力度修正检测到最外侧传感器时大力度修正。传感器间距过宽或过窄间距宽了车只有在偏得比较多时才能检测到间距窄了黑线细的时候中间传感器可能抓不住线。按黑线宽度调整。我自己的调试顺序是先低速BASE_SPEED 400测试直线和直角弯确认逻辑方向正确再逐步提速每提一次速度就重新观察车的走线形态看是过弯冲出还是直线抖动针对性调TURN_SPEED。5.3 串口打印辅助调试没有上位机辅助全靠肉眼看小车转向很多时候只能靠猜。我强烈建议在调试阶段接一个串口把状态值打出来看。用HAL库配置一个UART在main循环里把status的二进制输出出来uint8_t msg[32]; sprintf((char *)msg, status: %d\r\n, status); HAL_UART_Transmit(huart1, msg, strlen((char *)msg), 100);这样你能直观看到小车在直道、弯道时五路传感器分别是什么状态。我在调一个直角弯时发现车过弯的瞬间状态会跳变到0x00也就是五路全丢线说明转向修正偏慢车已经彻底冲出线了。这时候加一个记忆逻辑只要上一次状态是偏左那么丢线时默认继续向左转直到重新看到黑线。这个“丢线记忆”功能很实用尤其对直角弯和十字路口处理逻辑是记住丢失前一拍的状态按原方向继续旋转而不是傻傻停下。6. 个人体感与后续扩展6.1 实际测试中的几个心得红外循迹这个项目看起来简单真正调起来能把人整得没脾气。以前我在实验室调一辆五路循迹小车第一次下地的时候车一上电就是360度原地转圈原因就是电位器拧太过了五个传感器把白色地面全判成黑色。后来我总结出一条原则先调传感器再调电机方向最后调转向参数每步单独验证完再组合测试。调电机方向也有一个坑左右电机安装方向相反导致同一组AIN/BIN信号下两个轮子转向相反。如果你发现小车不是直行而是原地打转先检查两个电机是否同向。最简单的验证方法用固定占空比比如300驱动两个电机把车架空看两个轮子是否都往前走。如果不一致要么调整电机线序要么在代码里把某个方向引脚逻辑反过来。6.2 还能怎么继续升级第一版红外循迹跑通之后升级路径其实很清晰用ADC采模拟量替代数字量五路AO接定时器的ADC采样可以拿到0到4095的连续灰度值根据反射强度做线性偏差计算转向力度可以连续变化小车走线会顺滑很多。引入PID控制当有了连续偏差值就可以套一个位置式PD控制器把偏差映射成左右轮差速这是很多纯数字量方案无法做到的。加编码器做速度闭环给电机配上霍尔编码器用STM32定时器编码器模式采集转速再用PID把左右轮实际速度稳在目标值。这样即使电池电压变化、坡道负载变化车速依然稳定。和K210通讯扩展视觉如果后续要参加竞赛识别红绿灯、数字、二维码可以用串口把K210的识别结果传给STM32循迹仍然由红外负责STM32在红外基础上叠加视觉规则处理。红外循迹是整个智能小车DIY链路里最值得反复打磨的一环。它不复杂但把感知、决策、执行三个环节的配合吃透之后再看后面更复杂的视觉、导航、机械臂控制思路都会清晰很多。我到现在调试新平台时还会先用一套红外循迹验证电机和差速逻辑跑通了再往上叠功能。本文还有配套的精品资源点击获取