
简介基于STM32F103C8T6的智能小车寻迹与避障项目提供完整工程代码与配套原理图面向嵌入式学习、电子设计竞赛及课程设计实践适合想要从零搭建智能小车的开发者。项目利用超声波传感器实现避障通过路径检测完成寻迹并使用PWM调速控制电机代码中涉及GPIO、定时器、中断、串口等外设配置同时包含OLED显示与PID控制等关键模块。压缩包共181个文件约2.05MB主体为C源文件与头文件另附PDF原理图、Keil工程文件及hex固件可直接打开工程编译和烧录验证也方便按模块阅读源码。已有2991人学习下载目录组织清晰能够快速定位寻迹、避障、显示等功能代码既可作为比赛方案参考也便于在此基础上扩展蓝牙遥控、摄像头追踪等进阶功能。源工程内包含启动文件与配置文件可降低环境搭建门槛帮助开发者尽快投入实际调试。1. 智能小车课题为什么从STM32入手一个能跑通全流程的嵌入式入门载体做课程设计或毕业设计时选「基于STM32的智能小车」这个题目的同学大多不是冲着车本身去的而是想通过一个看得见、摸得着的实物把单片机外设、传感器采集、电机控制和中断优先级这些散落的知识点串起来。寻迹和避障这两件事表面上只是“跟着线走”和“别撞墙”实际上覆盖了GPIO输入输出、ADC采样、定时器PWM、串口调试、状态机设计这些嵌入式开发的核心环节。这套方案的价值在于它不需要你一开始就懂RTOS或者复杂算法只需要一块STM32最小系统板、一个电机驱动模块、两到三个传感器就能搭出一个可以演示、可以答辩、可以继续扩展的完整项目。适合的人群很明确——正在做课设或毕设的学生以及想快速上手STM32开发、需要一个完整参照系的入门开发者。下面按照从需求分析到验收答辩的完整流程把这份代码和原理图方案拆开讲清楚。2. 需求分析与技术选型寻迹、避障两套系统的功能边界与器件搭配2.1 功能需求拆解小车要完成的动作与判定逻辑智能小车这个题目看起来简单但需求边界不划清楚后面实现起来容易越做越乱。我一般会先把动作拆成三类基本运动控制前进、后退、左转、右转、停止这五个动作是底盘层面的原语所有上层逻辑最终都落到这五个动作上。寻迹模式小车沿着黑色或白色引导线行驶需要实时检测车身与引导线的相对位置并做出转向修正。避障模式小车在未知环境中前进遇到障碍物时停下、测距、判断方向、绕行然后继续前进。这里有一个容易忽略的点寻迹和避障通常是两种互斥的运行模式通过按键或拨码开关切换而不是同时工作。很多同学一开始想做成“既寻迹又避障”结果状态判断互相干扰调试起来非常痛苦。合理的做法是设定一个模式标志位主循环里先判断当前处于哪种模式再执行对应的传感器读取和控制逻辑。在判定逻辑上寻迹模式的核心是“偏差计算”小车底盘上横向排列多个灰度传感器每个传感器输出黑或白的数字信号把这些信号编码成一个偏差值偏差值决定转向的力度和方向。避障模式的核心是“距离阈值判断”超声波模块持续测量前方障碍物距离小于安全距离就进入避障状态大于安全距离就恢复直行。这两种逻辑都不涉及复杂的数学运算用STM32的标准库或HAL库都能轻松实现。2.2 器件选型与引脚分配从电机驱动到传感器的关键决策器件选型直接决定了代码怎么写、引脚怎么分配。这套方案里最核心的几个器件及其选型理由如下主控芯片STM32F103C8T6也就是常说的“蓝板”最小系统板。选它的原因是资源够用、资料最多、成本低而且 Keil 环境下工程配置非常简单适合课设和毕设场景。电机驱动模块TB6612 或 L298N。TB6612 体积小、压降小逻辑电平可以直接由 STM32 的 GPIO 驱动L298N 虽然便宜但发热大、压降明显如果用 4 节干电池供电转速损失会比较明显。我一般推荐 TB6612。寻迹传感器TCRT5000 灰度传感器模块3 路或 5 路均可。3 路勉强够用5 路对弯道检测更友好。每个模块输出数字信号TTL 电平不需要 ADC直接接 GPIO 读电平就行。避障传感器HC-SR04 超声波测距模块Trig 引脚发送 10 微秒以上的高电平触发信号Echo 引脚返回高电平持续时间时间乘以声速再除以 2 就是距离。这个模块有 5V 供电和 3.3V 逻辑电平兼容的问题后面会在排查章节细说。电机和底盘常见的 4 轮或 3 轮小车底盘配 TT 电机带减速箱。TT 电机的额定电压通常是 3V 到 6V用 18650 电池组或 4 节 AA 电池供电都行。引脚分配上我习惯把功能模块的区域分开PB0~PB2 给寻迹传感器PA0 和 PA1 接超声波 Trig 和 EchoPA8~PA11 接 TB6612 的 IN1~IN4 和 PWM 通道PB10 和 PB11 接按键和指示灯。这样分配的考虑是定时器 TIM1 的通道 1 和通道 2 正好在 PA8 和 PA9 上可以直接输出 PWM不用做引脚重映射省去不少配置麻烦。3. 总体架构设计电源、控制、驱动、传感四层结构与数据流3.1 硬件总体框图与模块职责划分整车的硬件结构可以按四层来理解每一层只和相邻层打交道这样排查问题时思路会清晰很多。电源层负责给所有模块供电。常见方案是 7.4V 锂电池组或 4 节 AA 电池经过 AMS1117-3.3 降压给 STM32 供电电池电压直接给电机驱动模块供电。注意一点电机和单片机不要共用同一路稳压输出否则电机启动瞬间的电流冲击会把单片机电压拉低导致复位。控制层STM32 最小系统板负责读取传感器状态、运行控制算法、输出 PWM 和方向信号。这一层是整个系统的“大脑”但不需要承担功率输出所有驱动能力都交给下一层。驱动层TB6612 电机驱动模块接收控制层的方向信号和 PWM输出驱动电流给电机。它在这里起的是“功率放大”的作用。传感层TCRT5000 寻迹传感器和 HC-SR04 超声波模块负责感知环境信息把物理量转换成电信号送给控制层。数据流方向是单向的传感层 → 控制层 → 驱动层 → 电机。控制层还会通过按键接收用户输入切换模式通过指示灯或串口输出调试信息。把这个数据流画在纸上再做 PCB 或面包板接线就不容易漏线或接反。3.2 软件架构主循环、定时器中断与优先级设计软件架构上这套方案不建议上 RTOS用前后台系统main 循环 中断就足够了。代码的整体结构维持三个角色主循环轮询读取模式标志位根据当前模式调用寻迹或避障的处理函数更新时间间隔大约 5ms 到 10ms。定时器中断TIM2 配置成 1ms 定时中断作为系统心跳。用于按键消抖计时、超声波超时计时、指示灯闪烁计时。PWM 输出TIM1 通道 1 和通道 2 输出两路 PWM频率设成 10kHz 左右。这个频率下电机噪声小、调速线性度好低于 1kHz 时电机会发出明显的啸叫。优先级设计上要记住一个原则PWM 输出不需要中断维护只要初始化好硬件会自动持续输出超声波测距的 Echo 采集用输入捕获或外部中断优先级要高于普通 GPIO 读取按键检测放在主循环里轮询加消抖不要用中断否则按键抖动会导致模式反复切换。代码文件的组织方式一般如下// main.c 主循环入口 int main(void) { HAL_Init(); SystemClock_Config(); // 配置系统时钟到72MHz MX_GPIO_Init(); // 初始化GPIO引脚 MX_TIM1_Init(); // 初始化PWM定时器 MX_TIM2_Init(); // 初始化系统心跳定时器 MX_USART1_Init(); // 初始化串口调试输出 while (1) { if (mode_flag MODE_TRACK) { track_handle(); // 寻迹模式处理 } else if (mode_flag MODE_AVOID) { avoid_handle(); // 避障模式处理 } HAL_Delay(5); } }这里有一个很多初学者会犯的错误在track_handle()或avoid_handle()里使用HAL_Delay()做长时间的延时等待比如等待传感器状态稳定等。这个问题会直接导致小车响应不及时后面排查章会专门讲。4. 核心模块实现寻迹算法、避障策略与PWM调速的代码落地4.1 寻迹模块灰度传感器的阈值判定与转向逻辑寻迹模块的关键是把三路或五路传感器的数字信号变成转向指令。以三路传感器为例传感器从左到右编号 S1、S2、S3输出为 0 表示检测到白底或黑线输出为 1 表示检测到黑线或白色引导线。具体电平逻辑取决于模块的跳线设置这里以“有信号输出低电平、模块上电默认高电平”为基准来说明。传感器状态与小车位置的关系可以归纳为五种情况只有 S2 检测到线中间传感器车身在线上直行。S1 和 S2 检测到线车身偏左需要右转修正。S2 和 S3 检测到线车身偏右需要左转修正。只有 S1 检测到线车身严重偏左需要大幅度右转。只有 S3 检测到线车身严重偏右需要大幅度左转。三个都没检测到可能是小车冲出线外按上一次的转向方向继续修正或者原地停下等待人工干预。代码如下// track_control.c 寻迹控制核心逻辑 // 返回转向值: 0-直行, 1-左转, 2-右转, 3-大左转, 4-大右转 int track_get_direction(void) { uint8_t s1 HAL_GPIO_ReadPin(TRACK_S1_GPIO_Port, TRACK_S1_Pin); uint8_t s2 HAL_GPIO_ReadPin(TRACK_S2_GPIO_Port, TRACK_S2_Pin); uint8_t s3 HAL_GPIO_ReadPin(TRACK_S3_GPIO_Port, TRACK_S3_Pin); if (s1 0 s2 1 s3 0) return 0; // 中间检测到线直行 if (s1 0 s2 0 s3 1) return 1; // 左偏左转修正 if (s1 1 s2 0 s3 0) return 2; // 右偏右转修正 if (s1 0 s2 0 s3 0) return 3; // 全黑按上轮方向修正或停止 if (s1 1 s2 1 s3 0) return 2; // 右侧脱离右转 if (s1 0 s2 1 s3 1) return 1; // 左侧脱离左转 if (s1 1 s2 1 s3 1) return 0; // 全白或全黑直行兜底 return 0; }注意这里我把电平逻辑先在注释里写清楚防止接线方式和预期相反导致调试时一头雾水。如果模块的输出电平逻辑相反把代码里的0和1对调即可不用改接线。转向的实现方式有两种一种是“差速转向”让内侧轮减速、外侧轮加速另一种是“原地转向”一个轮正转一个轮反转。对于寻迹场景差速转向更稳定因为小车仍然在前进只是修正方向不会因为原地转向而偏离引导线太远。原地转向更适合避障模式下的大角度掉头。4.2 避障模块超声波测距与状态机切换超声波避障的实现有两个重点测距准确性、状态切换的平滑性。测距部分我先用阻塞式读取来做避免一开始就引入输入捕获的复杂度。// ultrasonic.c 超声波测距函数返回单位mm float ultrasonic_get_distance(void) { // 发送10us以上的高电平触发信号 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(20); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待Echo引脚变为高电平用超时防止卡死 uint32_t timeout 1000; // 最大等待约1ms while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET) { if (timeout-- 0) return -1; // 超时返回无效距离 } // 记录高电平持续时间 uint32_t start HAL_GetTick(); timeout 1000; while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET) { if (timeout-- 0) return -1; } uint32_t end HAL_GetTick(); uint32_t duration_us (end - start) * 1000; // 单位转换HAL_GetTick返回ms float distance_mm duration_us * 0.34f / 2.0f; // 声速340m/s往返距离减半 return distance_mm; }这里说明几个容易踩的细节HAL_GetTick()的精度只有 1ms对于超声波这种需要微秒级计时的场景其实不够。要在代码里实测一下计时精度如果发现测距误差超过 2cm就要改用定时器输入捕获或延时函数来测量高电平宽度。我一般直接用delay_us()配合轮询循环来测精度在 10us 级别对应测距误差约 1.7mm完全够用。避障策略的实现我习惯用一个简单的三状态状态机直行、左转绕行、恢复直行。触发条件是超声波测距结果小于安全距离通常设为 25cm 到 30cm。状态机的切换伪代码如下// avoid.c 避障状态机 void avoid_handle(void) { float dist ultrasonic_get_distance(); if (dist 30 dist 0) { // 前方有障碍停车0.2秒然后向左转 motor_stop(); HAL_Delay(200); motor_turn_left(SPEED_MEDIUM); HAL_Delay(500); // 转完后重新直行 motor_forward(SPEED_DEFAULT); } else { // 前方无障碍保持直行 motor_forward(SPEED_DEFAULT); } }这个代码能跑但不够好。实际运行中小车会频繁进入“转弯动作”因为没有记录“上次是不是刚转过弯”。更好的做法是把状态机变量提升为函数内的静态变量或模块级变量增加一个is_avoiding标志只有从“直行状态”首次检测到障碍才执行转弯转弯完成后清标志。否则小车在障碍物前面会不断执行“停车-转弯-直行-停车-转弯-直行”的死循环看起来像在原地抽搐。4.3 电机驱动PWM调速与差速转向的具体实现电机驱动是车辆运动学的基础代码上要注意两件事PWM 频率和占空比映射、方向引脚和 PWM 引脚的配合。TB6612 的接线比较直接AIN1 和 AIN2 控制电机 A 的方向PWMA 控制电机 A 的转速B 通道同理。方向引脚的逻辑是 AIN11、AIN20 时正转反过来就反转两个引脚都为低时电机停止。// motor.c 电机控制函数 // speed取值范围 0-1000对应PWM占空比 0%-100% void motor_set_speed(int motor_id, int speed, int direction) { if (direction MOTOR_FORWARD) { // 设置方向引脚 HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET); } // 设置PWM占空比 __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, speed); } // 差速转向左侧轮减速右侧轮保持原速或适当加速 void motor_turn_left(uint16_t base_speed) { motor_set_speed(LEFT_MOTOR, base_speed * 0.3, MOTOR_FORWARD); motor_set_speed(RIGHT_MOTOR, base_speed, MOTOR_FORWARD); } void motor_turn_right(uint16_t base_speed) { motor_set_speed(LEFT_MOTOR, base_speed, MOTOR_FORWARD); motor_set_speed(RIGHT_MOTOR, base_speed * 0.3, MOTOR_FORWARD); }这里有一个关键参数差速比0.3不是固定值它取决于电机的减速比、轮胎直径和地面摩擦力。0.3 是在普通教室瓷砖地面上调出来的经验值如果地面换成地毯或跑道材料要重新实测。实测方法很简单让小车以这个差速比转弯观察它转完一个 90 度弯后是否正好回到引导线中心。如果转向不足车头还没转过来就冲出去了减小这个系数如果转向过头车头过了中心线增大系数。PWM 频率的参数同样需要注意代码初始化为 10kHz在MX_TIM1_Init()里通过Prescaler和Period计算得出。72MHz 主频、预分频 7、计数周期 899就能得到 10kHz、分辨率 900 级的 PWM。这个分辨率足够让调速看起来平滑。5. 调试与常见问题排查小车跑偏、误判、电压跌落的三类翻车现场5.1 现象一寻迹时小车在弯道冲出赛道直线段表现正常现象直线上小车走得很稳一进弯道就冲出去反复调整传感器间距和车速都没有明显改善。原因排查后我发现主要原因是“转向响应滞后”。主循环里有HAL_Delay(5)传感器读到偏差后要等 5ms 才执行转向这个延迟在高速度下被放大。另一个原因是差速转向的力度不够弯道越急需要的差速比越大如果差速比是固定的 0.3遇到锐角弯就会转向不足。解决先把主循环延时从 5ms 改到 2ms响应快了 2.5 倍弯道冲出概率明显下降。然后我把差速比和车速绑定速度越高差速比越大用几次实测确定了一条线性关系中速占空比 500时差速比 0.3高速占空比 800时差速比 0.5。整个修正过程大概花了二十分钟。如果你发现调完差速比还是冲出赛道还有一个方案是把三路传感器换成五路增加两路“预判传感器”放在前轮延长线上能提前一个车身位感知到弯道方向。5.2 现象二超声波避障距离忽远忽近小车偶尔直接撞上去现象串口打印出超声波测距结果发现在 30cm 到 60cm 之间剧烈跳变甚至偶尔输出 0 或负数小车在障碍物前没有及时停下。原因我先怀疑是供电不稳用示波器测了 5V 引脚纹波确实有点大但换独立电源后问题依旧。再仔细排查发现 HC-SR04 的 Echo 引脚输出的是 5V 高电平而 STM32 的 GPIO 容忍电压虽然标称 5V 兼容但内部钳位二极管在长时间 5V 输入下会导致波形畸变尤其当环境噪声大时Echo 信号的前沿抖动明显测距结果就飘了。解决在 Echo 引脚到 STM32 之间串一个 1kΩ 电阻再用一个 3.3V 稳压二极管做钳位把高电平限制在 3.3V 附近再测距离数据就稳定了。另一个容易被忽略的地方是 Trig 引脚HC-SR04 的 Trig 具有噪声敏感性如果 Trig 引脚悬空或走线太长干扰信号会直接触发测距导致数据异常。我通常在 Trig 引脚上并联一个 10nF 电容到地效果很好。这些硬件小改动在原理图上都要体现出来答辩时能当亮点讲。5.3 现象三电机转动瞬间单片机会复位重启现象小车静止时一切正常一加油门电机开始转单片机立刻黑屏复位。严重时反复重启完全无法工作。原因这是电源设计的问题。TT 电机启动电流可以达到正常工作电流的 3 到 5 倍如果电池电压本来就只有 6V 左右电机启动瞬间电压跌落可能超过 1.5VSTM32 的 3.3V 稳压器输入电压低于压差阈值输出就不稳定了。另外如果电机驱动模块的地和单片机的地没有共地或者共地点选择不当也会引起复位。解决两步走。第一步确认电池电压至少 7.4V不要用 4 节 1.5V 干电池凑合电压余量太小。第二步电机电源和逻辑电源分开走线在电机驱动模块的电源引脚附近加一个大电容470uF 电解电容和一个小电容0.1uF 陶瓷电容并联吸收启动瞬态电流。经过这样处理复位问题基本消失。如果还复位检查一下复位引脚的电容是否足够一般 100nF 到 1uF 都可以太小容易受干扰误复位。这几个问题的共性是它们都不会在原理图仿真或代码编译阶段暴露必须在实物上跑起来才能发现。所以调试时一定要保留串口打印把关键状态变量实时输出不要凭感觉猜。6. 验收方法与答辩技巧从功能演示到问题回答的完整准备验收这个环节很多同学以为“跑起来给老师看一眼就算完事”但实际答辩时老师更关心的是“你会不会排查问题”和“方案为什么这么设计”。我的习惯是把验收拆成三个层次来准备。第一层是功能验收。寻迹模式至少准备两种场地直线加一个直角弯、直线加一个 S 弯。能稳定跑完这两种场地说明寻迹逻辑基本成立。避障模式准备一个简单的 L 型通道让小车从一端进入在拐角处正确完成绕行后离开。这两个场景在答辩现场的演示时间一共不超过两分钟但覆盖了大部分逻辑分支。第二层是异常处理验收。老师常会问“如果小车冲出了引导线你的程序怎么办”对应到代码里答案就是全黑传感器状态的兜底逻辑——按上一轮转向方向继续修正或停止。我会在演示现场直接把小车手动推离线外让它自动找回引导线这个动作比口头解释管用得多。然后我会打开串口助手把超声波测距数据实时打出来对着障碍物移动手或书本展示距离数值跟随变化证明传感器工作正常。第三层是原理性验收。这一层不依赖代码而是基于原理图。我会提前准备一个简短的硬件说明电源怎么分层、传感器信号怎么处理、电机驱动为什么选 TB6612 而不是 L298N。回答问题时有个讨巧的框架先讲我做了什么再讲碰到了什么问题最后讲怎么解决的。比如老师问“为什么小车转弯时会顿一下”我就答“因为转向前的停车延时 200ms 是为了给车身一个稳定的姿态防止高速转弯时甩尾后来我发现可以用减速代替停车效果更好这是代码里我留的优化点。”答辩时最忌讳的是把代码从头背到尾老师时间有限他要听的只是关键决策点的理由。我会准备一张 A4 纸上面写清楚引脚分配表、PWM 频率参数、安全距离阈值这三个数字讲原理时随时指过去信息密度比口述高很多。另外提醒一点现场演示前一定要把电池充满、场地清理干净、传感器探头擦干净我见过太多翻车都是因为电池亏电或传感器沾灰导致的功能失效。从那以后我每次做演示之前都强制走一遍“满电、净场、擦探头”的检查清单希望帮到你让你的小车在答辩现场稳定跑完全程。本文还有配套的精品资源点击获取