STM32红外循迹避障小车程序调试与状态机设计 简介这份资源是面向嵌入式初学者与智能小车爱好者的STM32红外循迹避障小车完整程序围绕路径跟踪与障碍物避障两大核心功能展开适合用于课程设计、竞赛备赛或自学练手。压缩包共92个文件约318KB以C语言源码与头文件为主体包含35个.h、33个.c另有汇编启动文件、Keil工程文件、HEX烧录文件及readme说明覆盖初始化配置、中断服务、传感器数据处理与电机控制等模块。目前已有10787人学习下载热度较高。读者可从中获得一套可直接编译调试的工程框架理解红外信号采集、PWM调速与避障决策的实现思路并借助工程目录与说明文档快速上手实物验证积累硬件驱动与实时系统开发经验。1. 红外循迹避障小车为什么你的程序一上电就跑偏做嵌入式入门项目红外循迹避障小车几乎是绕不开的一道坎。硬件搭好了电机能转传感器指示灯也亮可程序烧进去之后小车要么原地打转要么冲出赛道要么遇到障碍物直接撞上去。问题往往不在硬件而在程序逻辑——红外对管的阈值判断、PWM调速的占空比映射、避障优先级和循迹优先级的冲突处理这三块只要有一块没理顺车就“不听话”。这个标题讲的是基于STM32的红外循迹避障小车程序核心是用STM32的GPIO读取红外传感器状态用定时器输出PWM控制电机再通过状态机协调循迹和避障两种行为。它适合已经学过STM32基础外设、想找一个综合项目练手的人也适合带学生做课程设计的工程师。下面按“传感器怎么读→电机怎么控→逻辑怎么编→坑怎么避”的顺序把可复现的方案拆开讲。2. 红外传感器与电机驱动先让轮子听你的话2.1 红外对管的两种输出形式与GPIO配置常见红外循迹模块分两种输出数字量和模拟量。数字量模块自带LM393比较器输出高低电平STM32端配成上拉输入或浮空输入即可模拟量模块输出随反射距离变化的电压需要接ADC。循迹场景下数字量足够用因为只需要判断“黑线”和“白底”两个状态。我一般把红外传感器的DO引脚接到STM32的GPIO上配置成上拉输入。为什么上拉因为多数模块输出是开漏或弱上拉STM32内部再上拉一次能避免悬空时读到随机值。以常见的五路循迹模块为例从左到右依次接PA0到PA4避障红外接PA5。// gpio.c 红外传感器GPIO初始化 void IR_GPIO_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_PIN_5; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; // 内部上拉防止悬空误读 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }这段代码把PA0到PA5全部设为上拉输入。参数说明GPIO_MODE_INPUT是输入模式GPIO_PULLUP启用内部上拉电阻GPIO_SPEED_FREQ_LOW对输入引脚无实际影响但保持低速度可以降低噪声。注意如果你的模块是模拟输出这里要改成GPIO_MODE_ANALOG并配合ADC使用不能直接读高低电平。读传感器时黑线反射弱红外接收管导通程度低多数模块输出高电平白底反射强输出低电平。但不同模块逻辑可能相反所以上电后先串口打印一遍五路状态手动把车放在黑线和白底上各读一次确认极性再写逻辑。这一步省不得我见过太多人直接抄代码结果模块极性和代码假设相反车一上电就疯跑。2.2 TB6612与L298N的选型差异及PWM接线电机驱动常用L298N和TB6612。L298N便宜、耐压高但压降大电池电压低时电机没劲TB6612效率高、发热小适合锂电池供电。循迹小车一般用两节18650或四节镍氢电压在6到8伏之间TB6612更合适。如果手头只有L298N也能用但要注意它的使能跳线帽——很多人忘了拔掉跳线帽PWM根本进不去电机只有全速和停两种状态。以TB6612为例PWMA接STM32的TIM1_CH1PA8PWMB接TIM1_CH4PA11AIN1、AIN2、BIN1、BIN2接普通GPIO控制方向。STBY接高电平使能。// motor.c PWM初始化与电机控制 void Motor_PWM_Init(void) { TIM_HandleTypeDef htim1; TIM_OC_InitTypeDef sConfigOC {0}; __HAL_RCC_TIM1_CLK_ENABLE(); htim1.Instance TIM1; htim1.Init.Prescaler 71; // 72MHz/72 1MHz计数频率 htim1.Init.Period 999; // 1MHz/1000 1kHz PWM频率 htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim1); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 0; // 初始占空比0 sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_4); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_4); }参数说明Prescaler71把72MHz主频分频到1MHzPeriod999让PWM频率为1kHz。1kHz是电机驱动的常用频率太低电机会啸叫太高驱动芯片发热。Pulse决定占空比范围0到999对应0%到100%。实际调试时我把Pulse设到300左右电机才开始转这是死区低于这个值电机不响应所以代码里要加一个最小占空比补偿。方向控制用GPIO写高低电平比如AIN11、AIN20为正转反之为反转。这里有个血泪经验换向时一定要先让PWM归零再切换方向引脚否则瞬间电流冲击可能烧驱动芯片。我一般写一个Motor_Set(speed, dir)函数内部先关PWM延时1毫秒再设方向最后开PWM。3. 循迹与避障的状态机别让两个任务打架3.1 五路循迹的偏差计算与舵机/差速转向五路循迹模块输出五个数字量从左到右记为L2、L1、M、R1、R2。常见偏差计算方法是加权求和L2权重-2L1权重-1M权重0R1权重1R2权重2。偏差值范围-2到20表示居中。根据偏差调整左右轮速度差偏差越大差速越大。// track.c 五路循迹偏差计算 int Track_GetError(void) { int error 0; if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) BLACK) error - 2; // L2 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) BLACK) error - 1; // L1 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) BLACK) error 0; // M if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) BLACK) error 1; // R1 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) BLACK) error 2; // R2 return error; }逻辑说明BLACK是宏定义根据实际模块极性设为0或1。偏差为负表示车偏左需要左轮减速或右轮加速偏差为正则相反。实际调车时我用比例控制就够了不需要PID因为循迹对响应速度要求不高比例系数Kp取30到50之间通过试凑确定。比如基础速度500左轮速度500 - Kperror右轮速度500 Kperror最后限幅到0到999。注意如果五路全黑或全白说明车冲出赛道或悬空这时候不能继续按偏差走要进入丢线处理全黑时停车或原地转向找回全白时减速直行一小段再判断。这个逻辑不写车一旦丢线就会乱冲。3.2 避障优先级与状态切换的防抖处理避障红外一般装在车头检测到障碍物输出低电平或高电平看模块。当避障触发时无论循迹偏差是多少都要优先执行避障动作停车、后退、转向。但这里有个坑——如果避障和循迹同时触发且避障信号有抖动车会在“避障转向”和“循迹回正”之间反复横跳看起来像抽搐。解决办法是加状态机和防抖。状态机设三个状态TRACK、AVOID、RECOVER。TRACK下正常循迹避障触发且持续超过50毫秒进入AVOIDAVOID执行后退和转向持续300毫秒后进入RECOVERRECOVER下低速直行如果避障信号消失且循迹有偏差回到TRACK。// state.c 状态机与防抖 typedef enum { TRACK, AVOID, RECOVER } CarState; CarState state TRACK; uint32_t avoid_tick 0; void Car_Update(void) { uint8_t obs HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); // 避障信号 switch (state) { case TRACK: if (obs OBSTACLE) { if (HAL_GetTick() - avoid_tick 50) { // 防抖50ms state AVOID; avoid_tick HAL_GetTick(); } } else { avoid_tick HAL_GetTick(); Track_Run(); // 正常循迹 } break; case AVOID: Motor_Set(0, 0); // 先停 HAL_Delay(100); Motor_Set(400, -1); // 后退 HAL_Delay(200); Motor_Set(400, 1); // 右转 HAL_Delay(300); state RECOVER; break; case RECOVER: Motor_Set(300, 1); // 低速直行 if (obs ! OBSTACLE HAL_GetTick() - avoid_tick 300) { state TRACK; } break; } }参数说明防抖时间50毫秒是经验值太小滤不掉抖动太大避障反应迟钝。AVOID里的延时用HAL_Delay会阻塞整个循环实际产品中应该用非阻塞计时但入门项目这样写能快速跑通。RECOVER的300毫秒是让车离开障碍物区域避免刚退出又立刻触发。这里的关键是避障优先级高于循迹但避障结束后不能立刻回到循迹否则车还在障碍物旁边循迹偏差可能把车又带回障碍物。RECOVER状态就是给车一个“冷静期”。4. 避坑与排查调车时最容易翻车的五个地方4.1 现象上电后电机不转但驱动板指示灯亮原因TB6612的STBY引脚没接高电平或者L298N的使能跳线帽没拔。很多人以为接了PWM就能调速实际上使能脚没使能芯片处于待机状态。解决用万用表测STBY电压如果是0伏直接接3.3V或5V。L298N则检查ENA、ENB跳线帽是否拔掉PWM线是否插在使能脚上。4.2 现象小车直线行驶时左右轮速度不一致越走越偏原因两个电机的死区占空比不同或者轮子摩擦力不同。代码里给左右轮相同的PWM值实际转速不一样。解决在代码里加左右轮补偿系数。比如左轮Pulse speed * 0.95右轮Pulse speed * 1.05具体值通过实测调整。更彻底的办法是加编码器做闭环但入门项目用补偿系数就够了。4.3 现象循迹时车在黑线边缘来回摆动像喝醉原因比例系数Kp太大或者传感器响应有延迟。Kp太大时偏差一出现就猛打方向车冲过黑线又反向猛打。解决先把Kp降到10观察车是否能缓慢回正再逐步加大到刚好不摆动为止。另外传感器离地高度要一致一般1到2厘米太高会误判太低会刮地。4.4 现象避障触发后车后退撞到后面东西原因后退时间太长或后退速度太快。AVOID状态里HAL_Delay(200)后退200毫秒如果速度400车可能后退十几厘米。解决缩短后退时间到100毫秒或者降低后退速度到300。同时如果车尾也装避障传感器后退时也要检测后方障碍。4.5 现象烧录后车不动串口无输出原因STM32的BOOT引脚状态不对或者程序卡在HAL_Delay里。还有一种可能是电机驱动抢了下载引脚比如PA9、PA10被复用。解决检查BOOT0是否接地串口TX、RX是否接对。如果用了PA9、PA10做电机控制换到其他引脚。另外在main函数开头加一句串口打印确认程序跑起来了。5. 进阶技巧用定时器中断做非阻塞调度前面状态机里用了HAL_Delay实际调车时你会发现避障后退的200毫秒里循迹传感器完全没在读如果障碍物突然消失车还在傻退。更好的做法是用定时器中断做时间片调度每1毫秒进一次中断在中断里更新状态机的计时器主循环只负责读传感器和更新PWM。具体做法配置一个TIM2预分频到1kHz开启更新中断。中断里维护track_tick、avoid_tick、recover_tick三个计数器主循环根据计数器判断状态切换不再用HAL_Delay。// tim.c 1ms定时器中断 void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(htim2, TIM_FLAG_UPDATE); if (avoid_tick 0xFFFFFFFF) avoid_tick; if (recover_tick 0xFFFFFFFF) recover_tick; } }这样主循环里判断if (avoid_tick 50)即可不会阻塞。参数说明TIM2的预分频值设为71周期设为999和PWM定时器一样但注意不要和PWM共用同一个定时器否则中断频率和PWM频率会互相干扰。另一个技巧是给电机加软启动。直接给满占空比车会猛冲轮子打滑循迹瞬间丢线。我一般在Motor_Set里做斜坡目标速度分10步每步延时5毫秒逐步加到目标值。这样车起步平稳循迹成功率明显提高。最后说一个验证方法把车放在黑线赛道上用手挡住避障传感器观察车是否先避障再回循迹然后松开手看车能否在1秒内回到循迹状态。如果超过1秒说明RECOVER时间太长或循迹Kp太小。反复调这三者直到车在赛道和障碍物之间切换自如。我调这类小车的习惯是先让车能直线走再让车能循迹走最后加避障。顺序反了三个问题搅在一起排查起来就是黑匣子。希望帮到你。本文还有配套的精品资源点击获取