简介本资源面向嵌入式初学者与STM32开发者聚焦智能小车红外循迹功能的完整实现基于HAL库讲解GPIO输入配置、外部中断响应、定时器周期采样与电机控制等关键环节帮助读者理解从传感器信号采集到轨迹纠偏的控制闭环。压缩包共72个文件约515KB以45个h头文件与18个c源文件为核心辅以uvprojx、uvoptx工程配置、ioc初始化文件、hex烧录固件及启动汇编文件覆盖CMSIS与STM32F1xx_HAL_Driver驱动层工程结构完整可直接编译。目前已有5644人学习下载适合作为课程设计、竞赛入门或自学练手项目。读者可从中获得可运行的循迹工程模板、模块化函数划分思路、中断与轮询两种检测方式的对比参考以及串口调试输出与PID控制算法的落地示例便于在此基础上扩展避障或无线通信功能。1. 红外循迹不是玄学从五路探头到差速转向的工程拆解很多人第一次做红外循迹小车都会遇到同一个场景明明探头指示灯亮灭正常电机也能转可小车一上赛道就画龙要么原地打转要么直接冲出弯道。问题往往不在代码而在于把「循迹」当成了一个纯软件逻辑问题。实际上红外循迹是一套从传感器阈值、GPIO采样、误差计算到差速控制的完整链路任何一环没对齐车都不会听话。这篇内容面向已经用 STM32 HAL 库点过灯、调过 PWM 的开发者把红外循迹从原理到落地讲透。读完之后你应该能独立完成五路红外循迹的阈值标定、误差计算和差速控制并且知道车跑偏时先查哪里。2. 五路红外传感器怎么接、怎么读从物理量到数字量的第一道关红外循迹的底层逻辑很朴素红外发射管照地面接收管根据反射强度输出高低电平。黑线吸光反射弱白底反光反射强。模块上的电位器就是在调这个判决门限。听起来简单但实际接线和采样方式决定了后面所有控制逻辑的上限。2.1 传感器布局与 GPIO 分配常见的五路红外模块五路探头一字排开间距大约 10 到 15 毫米具体取决于你的黑线宽度。线宽 15 毫米以上间距可以放到 12 毫米左右线宽 10 毫米以下间距要收窄到 8 毫米附近否则正常直行时可能同时压到三路误差计算会变得很粗糙。我一般把五路输出接到同一组 GPIO 上方便一次性读取。以常见的 STM32F103 为例可以选 PA0 到 PA4配置成上拉输入或浮空输入具体看模块输出类型。大多数红外模块是数字输出检测到黑线时输出高电平或低电平这个极性必须在代码里统一。/* 红外循迹 GPIO 初始化五路输入 */ void IR_Track_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; /* 模块开漏输出时启用上拉 */ GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }这段代码只做了一件事把五个引脚配成输入。Pull选上拉还是下拉取决于模块输出级。如果模块是推挽输出GPIO_PULLUP和GPIO_NOPULL都能读但建议统一用上拉避免浮空引脚引入随机跳变。Speed设成低频就够红外信号变化远达不到高速要求。2.2 采样消抖与状态编码直接读 GPIO 不是不行但电机换向、PWM 开关瞬间会在电源和地上产生毛刺单次采样很容易读到错误状态。我的习惯是连续采样三次取多数结果或者用简单的移位寄存器做状态确认。/* 读取五路红外状态返回 5 位编码bit4 到 bit0 对应最左到最右 */ uint8_t IR_Read_State(void) { uint8_t state 0; uint8_t i; for (i 0; i 3; i) { /* 连续采样三次 */ state 0; if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_SET) state | (1 4); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_SET) state | (1 3); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) GPIO_PIN_SET) state | (1 2); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) GPIO_PIN_SET) state | (1 1); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) GPIO_PIN_SET) state | (1 0); HAL_Delay(1); } return state; }这里用三次采样取最后一次结果严格来说不是多数表决但配合 1 毫秒延时已经能滤掉大部分开关噪声。如果你追求更稳可以把三次结果做按位与或按位或但要注意极性。返回的 5 位编码是后面误差计算的基础每一位对应一路探头顺序必须和物理布局一致。注意HAL_Delay在中断里不能用如果循迹采样放在定时器中断中要换成计数延时或直接单次采样加软件滤波。3. 从五路状态到转向量误差计算与差速控制的落地实现传感器读回来的是离散状态电机需要的是连续变化的转向量。中间这层映射决定了小车过弯是平滑还是抽搐。常见做法有两种查表法和加权法。查表法直观但占空间加权法计算量小、参数好调我更倾向加权法。3.1 加权误差计算与中心对齐给五路探头分别赋权重最左为 -2依次 -1、0、1、2。检测到黑线的探头权重相加再除以检测到黑线的探头数量得到当前偏移量。全白或全黑时做特殊处理。/* 根据五路状态计算偏差范围约 -2 到 2 */ float IR_Get_Error(uint8_t state) { int weight[5] {-2, -1, 0, 1, 2}; int sum 0; int count 0; float error 0.0f; uint8_t i; for (i 0; i 5; i) { if (state (1 (4 - i))) { /* bit4 对应最左 */ sum weight[i]; count; } } if (count 0) { return 999.0f; /* 全白丢线标志 */ } if (count 5) { return 888.0f; /* 全黑可能十字或停车线 */ } error (float)sum / (float)count; return error; }权重数组和物理顺序必须一一对应。count是当前压线的探头数正常直行时通常只有中间一路或两路压线error接近 0。弯道时外侧探头压线error绝对值增大。返回 999 和 888 是约定标志调用处要单独判断不能直接拿去算 PWM。3.2 差速 PWM 输出与死区处理拿到误差后最直接的控制是比例控制左右轮基础速度相同根据误差加减转向量。误差为正说明车偏右需要左轮加速、右轮减速误差为负则相反。/* 根据偏差调整左右电机 PWM基础速度 60%转向系数 25 */ void Motor_Differential(float error) { int base_speed 60; int turn_gain 25; int left_speed, right_speed; if (error 800.0f) { /* 丢线或全黑停车或进入特殊处理 */ Motor_Set(0, 0); return; } left_speed base_speed - (int)(error * turn_gain); right_speed base_speed (int)(error * turn_gain); /* 限幅防止超出 PWM 范围 */ if (left_speed 100) left_speed 100; if (left_speed -100) left_speed -100; if (right_speed 100) right_speed 100; if (right_speed -100) right_speed -100; Motor_Set(left_speed, right_speed); }base_speed是直行时的占空比turn_gain决定转向灵敏度。这两个参数需要联调base_speed太高过弯来不及调整太低直道速度上不去。turn_gain太大车会在直道上左右抖太小弯道转不过来。我的经验是先定base_speed在 50 到 70 之间再调turn_gain从 15 开始往上加直到能稳定过弯但不抖。Motor_Set是封装好的电机驱动函数接收 -100 到 100 的数值正数正转负数反转0 停止。如果你的驱动不支持反转就把负值截断到 0但这样急弯会转不过来建议还是用带 H 桥的驱动。提示PWM 频率建议设在 10 kHz 到 20 kHz 之间太低电机会啸叫太高部分驱动芯片发热明显。4. 避坑与排查红外循迹最常见的五个翻车点红外循迹的坑大多不在代码逻辑而在电气特性和机械安装。下面这五条是我和身边开发者踩过的典型问题按「现象 → 原因 → 解决」整理。4.1 现象小车在直道上左右画龙原因通常是turn_gain过大或者传感器间距太宽导致误差量化太粗。五路探头间距 15 毫米以上时直道上可能只有一路压线误差在 0 和 ±1 之间跳变转向量突变。解决先把turn_gain降到 10 左右观察如果仍然抖把探头间距缩小到 10 毫米以内或者改用七路模块。另一个办法是对误差做低通滤波但会引入相位滞后弯道响应变慢要权衡。4.2 现象过弯时直接冲出去完全不转向先查传感器极性。如果代码里认为高电平是黑线实际模块输出低电平是黑线误差符号就反了车会往反方向打。用串口把state和error打出来手动把车放在黑线上看数值变化是最快的排查方式。另一个可能是base_speed太高虽然打了转向但外侧轮速度仍然太快车来不及转。把base_speed降到 40 再试。4.3 现象同一赛道上午跑得好下午就丢线红外接收管受环境光影响很大。下午阳光斜射或者室内灯光变化都会让模块的输出门限漂移。解决在赛道现场重新标定电位器让五路探头在白底上输出一致在黑线上输出一致。标定时用万用表或串口看状态不要只看指示灯。如果环境光变化剧烈可以考虑给探头加遮光罩或者改用调制式红外模块抗干扰能力更强。4.4 现象电机一启动红外读数就乱跳这是典型的电源干扰。电机启动瞬间电流大地线弹跳导致 GPIO 读到错误电平。解决电机电源和单片机电源分开走线共地但不要共用一段细线。在电机两端加 0.1 微法陶瓷电容在电源入口加 100 微法电解电容。软件上把采样放在 PWM 关断期间或者连续采样取稳定值。4.5 现象全黑十字路口车停不下来IR_Get_Error返回 888 时如果调用处没有单独处理直接拿去算 PWMerror * turn_gain会得到一个很大的值限幅后左右轮一个满速一个反转车会原地打转。解决在Motor_Differential里先判断error 800全黑时让车保持上一状态或直行一小段全白时减速并往最后有效误差方向修正。具体策略取决于赛道规则但一定要显式处理这两个特殊值。5. 进阶技巧用状态机处理丢线和十字让循迹更稳基础的比例控制在简单赛道上够用但遇到丢线、十字、急弯组合时纯比例控制会显得力不从心。我一般会加一个轻量状态机把「正常循迹」「丢线找回」「十字通过」分开处理每种状态用不同的速度和转向策略。5.1 状态划分与切换条件状态机不需要复杂三个状态就够状态触发条件行为正常循迹1 到 4 路压线比例控制基础速度 60丢线找回连续 3 次全白减速到 30按最后误差方向猛打十字通过连续 2 次全黑直行固定时间忽略误差切换条件里的「连续次数」是防抖关键。单次全白可能是赛道上的反光点连续三次才认为是真丢线。十字同理单次全黑可能是粗线连续两次才确认。/* 简化的循迹状态机 */ typedef enum { STATE_NORMAL, STATE_LOST, STATE_CROSS } TrackState; TrackState track_state STATE_NORMAL; float last_error 0.0f; uint8_t lost_count 0; uint8_t cross_count 0; void Track_Update(void) { uint8_t state IR_Read_State(); float error IR_Get_Error(state); switch (track_state) { case STATE_NORMAL: if (error 800.0f) { lost_count; if (lost_count 3) { track_state STATE_LOST; lost_count 0; } } else if (error 700.0f) { cross_count; if (cross_count 2) { track_state STATE_CROSS; cross_count 0; } } else { lost_count 0; cross_count 0; last_error error; Motor_Differential(error); } break; case STATE_LOST: /* 按最后误差方向慢速找回 */ Motor_Set(last_error 0 ? 40 : -20, last_error 0 ? -20 : 40); if (error 800.0f) { track_state STATE_NORMAL; } break; case STATE_CROSS: /* 直行固定时间忽略误差 */ Motor_Set(50, 50); HAL_Delay(200); track_state STATE_NORMAL; break; } }这段代码里STATE_LOST的找回逻辑是让车往最后误差方向转速度压低避免冲出去。STATE_CROSS直接直行 200 毫秒时间取决于你的车速和十字宽度需要实测调整。HAL_Delay在状态机里会阻塞其他逻辑如果系统里还有别的任务要改成非阻塞计时。5.2 参数整定顺序与验证方法调参不要一上来就改代码按这个顺序来第一步静态标定。把车放在赛道上手动移动用串口打印state和error确认五路探头顺序正确、极性正确、全白全黑标志正确。第二步低速直道测试。base_speed设 30turn_gain设 10看车能不能沿直线走。如果画龙降turn_gain如果反应迟钝升turn_gain。第三步弯道测试。逐步提高base_speed到 50 到 60观察过弯是否丢线。丢线就降速或升turn_gain但turn_gain太高会抖优先降速。第四步特殊路段测试。专门找十字、急弯、断线组合验证状态机切换是否及时。如果十字误判成丢线把cross_count阈值从 2 降到 1或者调整全黑判断的error阈值。我自己的习惯是每次只改一个参数改完跑三圈记录丢线次数和单圈时间。丢线次数优先于速度先稳再快。这套方法不新鲜但能省下大量反复烧录的时间。希望帮到你。本文还有配套的精品资源点击获取