资讯动态

STM32循迹避障小车从零实战:硬件选型、电机驱动与调试避坑

发布时间:2026/10/15 22:20:31 来源:尧图企业网站定制
简介这是一份面向嵌入式初学者与电子竞赛选手的STM32循迹避障小车完整工程基于STM32F103C8T6最小系统板搭建采用L298N驱动两路直流电机并以12V供电通过Timer3输出两路PWM实现调速同时集成四路红外循迹、超声波测距配合舵机自动转向的避障方案并支持手机蓝牙调试助手远程控制小车运动方向。资源包共227个文件约7.38MB以c与h源码、o与d编译中间文件、crf与axf等工程产物为主另含uvprojx、uvoptx等Keil工程配置及hex烧录文件目录结构完整便于直接编译与二次开发。工程代码注释详尽涵盖定时器PWM、外部中断、串口通信与传感器驱动等模块适合作为课程设计、毕业设计或智能车竞赛的参考模板。目前已有1677人学习下载可帮助读者快速理解循迹与避障的软硬件协同逻辑掌握电机调速、舵机控制与蓝牙遥控的整合思路。1. 从一块 8 块钱的芯片说起循迹避障小车为什么成了嵌入式入门的分水岭很多人第一次接触嵌入式都是从一块 STM32C8T6 最小系统板开始的。这块芯片便宜、资料多、引脚够用但真正把它跑起来做一辆能循迹、能避障的小车你会发现事情远没有想象中简单。循迹避障小车这个题目表面上看是「传感器读值、电机转动」的简单逻辑实际上它把 GPIO 输入输出、定时器 PWM、外部中断、ADC 采样、传感器时序、电机驱动、电源管理全部串在了一起。任何一个环节出问题小车要么原地打转要么冲出赛道要么见墙就撞。我带过几个做课设的同学最常见的翻车场景是代码烧进去电机不转查了半天发现是 TB6612 的 STBY 引脚没拉高或者循迹时小车疯狂抖动最后定位到是红外对管离地太高阈值判断一直在临界点跳变。这些坑不踩一遍光看教程是学不会的。这篇文章面向的是手里已经有 STM32C8T6 最小系统板、想从零搭一辆循迹避障小车的朋友。我会按「硬件选型 → 电机驱动 → 循迹实现 → 避障实现 → 调试避坑 → 进阶调参」的顺序把每一步的参数、代码和排查方法讲清楚。你照着做大概率能跑通跑不通的地方我也把排查思路留给你。2. 硬件选型与接线别让电源和驱动拖后腿2.1 主控、传感器、驱动、电源的选型逻辑STM32C8T6 是 LQFP48 封装48 个引脚72MHz 主频64KB Flash20KB RAM。对于循迹避障小车来说这个资源绰绰有余。真正需要花心思的是外设选型。循迹传感器常见两种红外对管和灰度传感器。红外对管便宜但受环境光影响大黑色赛道和白色底板的反射率差异要足够明显才能稳定判断。灰度传感器精度更高但价格贵一些。我一般建议新手先用红外对管把逻辑跑通再换灰度传感器提升稳定性。避障传感器主流是 HC-SR04 超声波模块和红外避障模块。HC-SR04 测距范围 2cm 到 400cm精度够用但要注意它的触发信号需要至少 10us 的高电平回响信号是脉宽与距离成正比。红外避障模块便宜但检测距离固定一般只有 2cm 到 30cm 可调容易受阳光干扰。电机驱动选 TB6612 还是 L298NTB6612 效率高、发热小、支持 1.2A 持续电流适合 3V 到 5V 的小型直流电机。L298N 便宜但压降大、发热严重电池电压会被吃掉 1.5V 左右。如果小车用 4 节 1.5V 电池供电L298N 会让电机实际电压只有 4.5V 左右转速明显下降。我一般推荐 TB6612接线简单代码也清爽。电源部分是最容易被忽视的。STM32C8T6 需要 3.3V电机需要 6V 到 12V传感器需要 5V。如果直接用电池给所有模块供电电机的电流波动会通过电源线耦合到单片机导致复位或死机。常见做法是电池先接一个降压模块给单片机供电电机驱动单独接电池两者共地但不共电源。这样电机启动时的电流冲击不会影响单片机。2.2 接线表与最小系统搭建下面这张表是我常用的接线方案基于 STM32C8T6 最小系统板、TB6612 驱动、红外对管循迹模块、HC-SR04 超声波模块。模块引脚STM32C8T6 引脚说明TB6612PWMAPA0TIM2_CH1左电机 PWMTB6612AIN1PB0左电机方向TB6612AIN2PB1左电机方向TB6612PWMBPA1TIM2_CH2右电机 PWMTB6612BIN1PB2右电机方向TB6612BIN2PB3右电机方向TB6612STBY3.3V必须拉高否则电机不转红外对管左PA4GPIO 输入上拉红外对管中PA5GPIO 输入上拉红外对管右PA6GPIO 输入上拉HC-SR04TrigPA7GPIO 输出HC-SR04EchoPA8GPIO 输入或 TIM 输入捕获接线时注意几点TB6612 的 VM 接电池正极VCC 接 5V 或 3.3VGND 共地。红外对管的输出一般是数字量黑色赛道输出高电平白色底板输出低电平具体要看模块说明书。HC-SR04 的 Echo 引脚输出是 5V 电平如果 STM32 引脚不是 5V 容忍的需要加电阻分压。提示TB6612 的 STBY 引脚悬空时芯片处于待机状态电机完全不转。这个坑我见过至少五次每次都是查了半天代码才发现是硬件问题。3. 电机驱动与 PWM 调速让小车动起来的第一步3.1 TB6612 的控制逻辑与 PWM 频率选择TB6612 的控制逻辑很简单AIN1 和 AIN2 决定方向PWMA 决定速度。当 AIN11、AIN20 时正转AIN10、AIN21 时反转两者相同则刹车。PWMA 输入 PWM 波占空比越高转速越快。PWM 频率选多少常见做法是 10kHz 到 20kHz。频率太低电机会有啸叫声频率太高驱动芯片开关损耗增加。我一般用 10kHz对应 TIM2 的预分频器和自动重装载值可以这样算72MHz / (72 * 100) 10kHz即预分频器设为 71自动重装载值设为 99。下面是 TIM2 的 PWM 初始化代码用 HAL 库实现。// TIM2 PWM 初始化10kHz占空比可调 void PWM_Init(void) { TIM_HandleTypeDef htim2; TIM_OC_InitTypeDef sConfigOC {0}; htim2.Instance TIM2; htim2.Init.Prescaler 71; // 72MHz / 72 1MHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 99; // 1MHz / 100 10kHz htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim2); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 0; // 初始占空比 0 sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); }这段代码把 PA0 和 PA1 配置为 PWM 输出。Prescaler 设为 71意味着计数器时钟是 1MHz。Period 设为 99意味着计数器从 0 数到 99 后溢出周期是 100 个计数对应 10kHz。Pulse 是比较值设为 0 时占空比为 0%设为 99 时占空比为 99%。实际使用中一般不超过 80%留一点余量给电机启动。3.2 电机方向控制与速度设置函数有了 PWM 初始化还需要一个函数来设置左右电机的方向和速度。下面这个函数把方向控制和 PWM 占空比设置封装在一起。// 设置左电机速度和方向speed 范围 -100 到 100 void Motor_Left(int speed) { if (speed 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, speed); } else if (speed 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, -speed); } else { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 0); } }speed 为正时正转为负时反转为 0 时刹车。__HAL_TIM_SET_COMPARE 是 HAL 库的宏直接修改比较寄存器比重新调用 HAL_TIM_PWM_ConfigChannel 快得多。右电机的函数同理只需要改引脚和通道。注意电机启动瞬间电流很大如果电池供电不足单片机会复位。建议在电机驱动电源端并联一个 100uF 到 470uF 的电解电容能明显改善这个问题。4. 循迹与避障的代码实现从传感器读值到动作决策4.1 红外循迹的阈值判断与状态机红外对管的输出是数字量但实际使用中不能简单用 0 和 1 判断。黑色赛道和白色底板的反射率差异、环境光变化、传感器离地高度都会影响输出。我一般会在初始化时做一次校准把小车放在白色底板上读一次值放在黑色赛道上读一次值取中间值作为阈值。更稳妥的做法是用状态机处理循迹逻辑。三个红外对管分别对应左、中、右状态组合有 8 种。常见处理逻辑是左中右动作010直行100左转001右转110左转011右转111停止或直行000停止或直行下面是状态机的代码实现。// 循迹状态机返回动作指令 typedef enum { GO, LEFT, RIGHT, STOP } Action; Action Track_Decision(void) { uint8_t L HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4); uint8_t M HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); uint8_t R HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6); if (L 0 M 1 R 0) return GO; if (L 1 M 0 R 0) return LEFT; if (L 0 M 0 R 1) return RIGHT; if (L 1 M 1 R 0) return LEFT; if (L 0 M 1 R 1) return RIGHT; if (L 1 M 1 R 1) return STOP; return GO; }这段代码里L、M、R 分别读取三个红外对管的输出。注意这里的 0 和 1 对应的是白色和黑色具体取决于模块输出极性。如果你的模块输出相反把判断条件反过来即可。状态机的好处是逻辑清晰每个状态对应一个明确动作调试时也容易定位问题。4.2 HC-SR04 超声波测距与避障逻辑HC-SR04 的测距原理是Trig 引脚给至少 10us 高电平模块发出 8 个 40kHz 脉冲Echo 引脚变高持续时间就是声波往返时间。距离 时间 * 声速 / 2。声速取 340m/s即 0.034cm/us。下面是用定时器输入捕获实现测距的代码框架。// HC-SR04 测距返回距离单位 cm float Ultrasonic_Measure(void) { uint32_t t_start 0, t_end 0; float distance 0; // 发送 10us 触发脉冲 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); // 等待 Echo 变高 while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) GPIO_PIN_RESET); t_start __HAL_TIM_GET_COUNTER(htim3); // 等待 Echo 变低 while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) GPIO_PIN_SET); t_end __HAL_TIM_GET_COUNTER(htim3); // 计算距离假设定时器 1MHz即 1us 一个计数 distance (t_end - t_start) * 0.034 / 2; return distance; }这段代码用轮询方式读取 Echo 引脚简单但会阻塞 CPU。实际项目中更推荐用输入捕获中断这样测距不占用主循环时间。避障逻辑就是如果距离小于 20cm就停车或转向如果距离在 20cm 到 50cm 之间减速大于 50cm 正常行驶。提示HC-SR04 的 Echo 引脚输出是 5V 电平STM32C8T6 的 PA8 不是 5V 容忍引脚直接接可能损坏芯片。常见做法是用两个电阻分压比如 1k 和 2k 串联把 5V 降到 3.3V。5. 调试避坑那些让你怀疑人生的常见问题5.1 电机不转或只往一个方向转现象代码烧进去电机完全不转或者只有一个电机转。原因最常见的是 TB6612 的 STBY 引脚没拉高。STBY 是使能引脚低电平时芯片待机所有输出关闭。其次是方向引脚接错或者 PWM 通道配置错误。解决先用万用表测 STBY 引脚电压正常应该是 3.3V 或 5V。如果电压不对检查接线。如果 STBY 正常用示波器看 PWM 引脚有没有波形输出。没有波形就检查定时器初始化代码有波形但电机不转就检查电机驱动电源。5.2 循迹时小车疯狂抖动或冲出赛道现象小车在直道上左右摇摆或者到了弯道直接冲出去。原因红外对管的阈值判断在临界点跳变或者循迹逻辑的转向力度太大。另外如果传感器离地太高反射信号弱输出会不稳定。解决先调整传感器离地高度一般 1cm 到 2cm 比较合适。然后在代码里加滤波比如连续读三次两次以上相同才认为有效。转向力度也要调不要一检测到偏差就猛打方向可以用比例控制偏差越大转向越大。5.3 超声波测距值跳变或一直为 0现象串口打印的距离值忽大忽小或者一直是 0。原因Echo 引脚没有正确捕获或者定时器频率设置不对。另外HC-SR04 需要至少 60ms 的测量周期如果连续触发太快模块会来不及响应。解决先确认定时器频率1MHz 对应 1us 一个计数计算距离时系数是 0.034。如果定时器频率不对距离值会差很多。另外在两次测距之间加 60ms 以上的延时。如果还是跳变检查电源是否稳定超声波模块对电源噪声比较敏感。5.4 小车运行一段时间后复位或死机现象小车跑了几秒或几十秒后突然停止然后重新启动。原因电机启动电流大电池电压被拉低导致单片机复位。或者电源线太细压降太大。解决在电机驱动电源端并联大电容比如 470uF。电池换成容量更大的或者用两块电池分别给单片机和电机供电。电源线尽量短粗减少压降。5.5 PWM 调速时电机有啸叫声现象电机转动时发出刺耳的高频声音。原因PWM 频率落在人耳可听范围内一般是 1kHz 到 20kHz。解决把 PWM 频率提高到 20kHz 以上超出人耳听觉范围。但频率太高驱动芯片发热会增加需要权衡。我一般用 10kHz啸叫声不明显驱动也不热。6. 进阶调参让小车跑得更稳的几个技巧6.1 用比例控制替代开关控制开关控制就是「偏了就猛转」比例控制是「偏得越多转得越多」。实现起来很简单把循迹传感器的偏差量化成数值比如左偏为 -1直行为 0右偏为 1然后乘以一个比例系数 Kp作为转向速度。// 比例控制循迹 int error 0; if (L 1 M 0) error -1; else if (L 0 R 1) error 1; else if (L 1 M 1) error -2; else if (M 1 R 1) error 2; int turn error * Kp; // Kp 一般取 20 到 40 Motor_Left(base_speed turn); Motor_Right(base_speed - turn);Kp 的取值需要实际调试。太小了转向不够太大了会震荡。我一般从 20 开始试每次加 5直到小车能稳定过弯又不抖动。6.2 超声波避障的滤波与决策优化超声波测距容易受干扰单次测量不可靠。常见做法是连续测三次去掉最大值和最小值取中间值。或者用滑动平均滤波把最近五次测量值求平均。避障决策也可以优化。不要一检测到障碍就急停可以先减速再判断距离如果持续小于阈值再转向。这样小车运行更平滑不会一顿一顿的。6.3 用串口打印调试信息调试时最有用的工具是串口。把传感器读数、电机速度、状态机状态打印出来一眼就能看出问题在哪。我一般用 115200 波特率每 100ms 打印一次不影响小车运行。printf(L:%d M:%d R:%d Dist:%.1f Action:%d\r\n, L, M, R, distance, action);注意 printf 需要重定向 fputc或者用 HAL_UART_Transmit 直接发送。重定向的代码网上很多这里不展开。6.4 电池电压监测小车跑久了电池电压会下降电机转速变慢循迹效果变差。可以在代码里加电池电压监测用 ADC 读取分压后的电池电压低于阈值时提醒更换电池。这个功能对比赛或长时间运行很有用。我自己的习惯是每次调试新功能前先用串口把关键数据打印出来确认传感器读值正常、电机响应正常再上车跑。这样能省下大量「拆了装、装了拆」的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑