简介这份资源面向学习STM32嵌入式开发的学生与电子爱好者提供一套完整的智能小车综合实践方案涵盖遥控、循迹、避障与灭火四类功能。项目以STM32为核心处理器串联电机驱动、红外循迹、超声波测距、无线遥控与电源管理等模块并涉及PID控制、避障决策与安全触发机制适合作为课程设计、竞赛训练或毕业设计的参考工程。压缩包共197个文件约6.9MB以c与h源码、crf与o编译中间文件、s启动文件为主另含uvprojx与uvoptx工程配置、map映射文件及axf可执行文件构成可直接用Keil打开调试的完整工程。已有449人学习下载读者可从中获得模块化硬件连接思路、传感器数据处理与中断服务写法、遥控指令解析及系统联调排错经验便于对照复现并在此基础上扩展功能。1. 从一块开发板和三个传感器说起这套小车方案到底解决什么问题很多同学拿到“智能小车”这个题目第一反应是先把电机转起来再说结果转到一半发现循迹抖、避障误判、灭火模块一启动单片机就复位。这套基于 STM32 单片机的智能遥控循迹避障灭火小车要解决的不是“让轮子动起来”而是把四种工作模式——遥控、循迹、避障、灭火——在一颗主控上稳定地调度起来并且让每种模式都有明确的进入条件、退出条件和优先级。它适合正在做单片机课程设计、自动化或电子信息类毕设的读者也适合需要一份可交付小车方案的从业者参考。整套方案的核心矛盾很集中传感器多、执行机构多、实时性要求不低而资源和引脚都有限怎么分配、怎么仲裁决定了这辆车是“能跑”还是“跑得稳”。2. 需求与总体方案四种模式、三层结构和主控资源分配先把需求钉死再谈选型。这辆车的功能边界通常划成四块遥控模式下由外部遥控信号给出方向和速度循迹模式下靠地面黑线自主行驶避障模式下靠测距传感器检测前方障碍并绕开灭火模式下检测到火源后靠近并启动灭火执行机构。四种模式不是并列关系而是有优先级的——避障优先级高于循迹灭火优先级高于避障遥控则作为人工接管随时可以抢占。这个优先级如果一开始不定清楚后面代码里就会出现“循迹把车开进墙里避障想接管却被循迹的定时器打断”这类血泪经验。2.1 功能需求拆解与模式优先级把需求拆成可验收的条目是这份方案能不能交差的第一步。我一般会按“输入—决策—执行”三段来拆输入侧包括遥控接收、循迹传感器阵列、避障测距、火焰检测决策侧是模式仲裁和运动控制执行侧是左右电机驱动、灭火装置、状态指示。每一段都要有可观测的输出否则答辩时老师问“你怎么证明避障真的生效了”你只能干瞪眼。模式优先级建议按下面的顺序固化到代码里而不是靠 if 堆叠优先级模式进入条件退出条件1最高灭火火焰传感器持续检测到火源火源消失或人工复位2避障前方距离小于设定阈值障碍解除且延时确认3循迹循迹阵列检测到黑线丢失黑线超时4最低遥控收到有效遥控指令指令停止或超时这张表的价值在于它把“谁听谁的”变成了可编码的规则。实际实现时用一个模式状态机每个控制周期先采集所有传感器再按优先级从高到低判断命中即锁定当前模式低优先级模式只能在高优先级模式退出后接管。这样避障不会和循迹打架灭火也不会被遥控误触发。2.2 总体架构感知层、控制层、执行层的职责划分整车按三层划分比较清晰。感知层负责把物理量变成数字量循迹用红外对管阵列输出高低电平避障用超声波或红外测距输出距离值火焰检测用火焰传感器输出开关量遥控用无线接收模块输出通道脉宽。控制层是 STM32 主控负责定时采集、模式仲裁、PID 或比例控制、PWM 输出。执行层是电机驱动、舵机如果转向用舵机、灭火泵或风扇、蜂鸣器和 LED 指示。三层之间用明确的接口隔开感知层只提供“原始值滤波后值”控制层不直接读引脚执行层只接受“目标速度/目标角度”这类语义化指令。这样做的好处是调试时可以单独替换某一层——比如循迹抖得厉害先换感知层的滤波参数不用动控制逻辑。很多翻车案例都是因为引脚操作散落在主循环各处改一个传感器要把整个工程翻一遍。2.3 主控资源与引脚分配STM32 型号选择上这类小车通常用 F103 系列就够需要至少两路定时器输出 PWM 驱动左右电机一路定时器做控制周期基准若干 GPIO 接循迹阵列和火焰传感器一路 USART 或外部中断接遥控接收ADC 视测距方案而定。引脚分配要提前画表避免“调到最后发现两个功能抢同一个定时器通道”。资源用途说明TIM2 CH1/CH2左电机 PWM频率 10–20 kHz避免啸叫TIM2 CH3/CH4右电机 PWM与左侧同频便于同步TIM3控制周期基准10 ms 中断采集与仲裁都在这里GPIOA循迹阵列输入上拉输入注意去抖GPIOB电机方向、灭火执行推挽输出USART1遥控接收/调试输出波特率与接收模块匹配ADC1测距模拟量如用红外多通道扫描做滑动平均引脚表不是形式主义它是后面写初始化代码的依据。我习惯在工程里单独放一个board_config.h把所有引脚和定时器通道集中定义换板子只改这一个文件。3. 感知与执行模块实现循迹、避障、灭火和电机驱动怎么落地这一章是整套方案里最容易被“看起来能跑”掩盖问题的部分。传感器装上去有输出不等于数据可信电机能转不等于速度可控。下面按模块给出关键实现和参数说明代码只保留能说明逻辑的骨架具体寄存器配置按所用库补全。3.1 循迹阵列的采集与去抖循迹通常用 3 到 5 路红外对管黑线吸收红外输出电平翻转。原始电平不能直接进决策因为地面反光、传感器高度不一致都会造成抖动。做法是定时采样后做多数表决或连续 N 次一致才确认。/* 每 10 ms 调用一次返回循迹偏差负偏左正偏右0 居中 */ int8_t track_scan(void) { static uint8_t last_state 0; uint8_t raw 0; /* 读取 5 路传感器黑线为 1 */ raw | (HAL_GPIO_ReadPin(TRACK_L2_PORT, TRACK_L2_PIN) 4); raw | (HAL_GPIO_ReadPin(TRACK_L1_PORT, TRACK_L1_PIN) 3); raw | (HAL_GPIO_ReadPin(TRACK_M_PORT, TRACK_M_PIN) 2); raw | (HAL_GPIO_ReadPin(TRACK_R1_PORT, TRACK_R1_PIN) 1); raw | (HAL_GPIO_ReadPin(TRACK_R2_PORT, TRACK_R2_PIN) 0); /* 连续两次一致才更新抑制单次抖动 */ if (raw ! last_state) { last_state raw; return 0; /* 本次不更新偏差保持上次输出 */ } switch (raw) { case 0b00100: return 0; /* 正中 */ case 0b01100: case 0b01000: return -1; /* 略偏左 */ case 0b11100: case 0b11000: return -2; /* 偏左较多 */ case 0b00110: case 0b00010: return 1; /* 略偏右 */ case 0b00111: case 0b00011: return 2; /* 偏右较多 */ default: return 0; /* 丢线或全黑交给上层处理 */ } }这段代码的关键不在 switch而在“连续两次一致才更新”这个去抖策略。参数上采样周期跟控制周期一致取 10 ms连续次数取 2 是折中取 3 以上响应会变慢取 1 等于没滤波。返回值用 -2 到 2 的离散偏差是为了后面做比例控制时直接乘系数不需要再归一化。丢线时返回 0 并不是正确行为实际工程里应该返回一个特殊值让上层进入“找线”状态这里保留 0 是为了说明结构落地时要补上。3.2 避障测距的滤波与阈值判定避障用超声波时单次测距容易受反射面角度影响出现野值用红外测距时输出随距离非线性变化。无论哪种都要做滤波再判阈值。常见做法是滑动平均加中值或者连续多次小于阈值才确认障碍。#define DIST_BUF_LEN 5 #define OBSTACLE_CM 25 static uint16_t dist_buf[DIST_BUF_LEN]; static uint8_t dist_idx 0; /* 返回 1 表示确认有障碍0 表示无障碍 */ uint8_t obstacle_check(uint16_t new_dist) { uint8_t i, j; uint16_t tmp[DIST_BUF_LEN], sum 0; dist_buf[dist_idx] new_dist; dist_idx (dist_idx 1) % DIST_BUF_LEN; /* 拷贝后做冒泡排序取中值长度固定为 5开销可接受 */ for (i 0; i DIST_BUF_LEN; i) tmp[i] dist_buf[i]; for (i 0; i DIST_BUF_LEN - 1; i) for (j 0; j DIST_BUF_LEN - 1 - i; j) if (tmp[j] tmp[j 1]) { uint16_t t tmp[j]; tmp[j] tmp[j 1]; tmp[j 1] t; } /* 用中值而不是均值避免单次野值拉偏 */ return (tmp[DIST_BUF_LEN / 2] OBSTACLE_CM) ? 1 : 0; }这里用中值而不是均值是因为超声波偶尔会返回一个很小的野值均值会被拉低导致误判障碍。缓冲区长度取 5对应 50 ms 的窗口按 10 ms 一次测距既能滤掉单次野值又不会让刹车反应太迟。阈值 25 cm 是这类小车的常用值实际要按车体长度和刹车距离调车越长、速度越快阈值越要大。注意这个函数只回答“有没有障碍”不回答“障碍在左还是右”多方向避障需要左右各一组测距或者用舵机带动单传感器扫描。3.3 灭火检测与执行时序灭火模块的坑主要在时序。火焰传感器检测到火源后不能立刻全速冲过去要先减速靠近进入有效距离再启动灭火执行机构并且执行时间要有限制否则泵或风扇长时间空转容易过热。typedef enum { FIRE_IDLE, FIRE_APPROACH, FIRE_ACTING, FIRE_DONE } fire_state_t; void fire_task(uint8_t flame_detected, uint16_t front_dist) { static fire_state_t st FIRE_IDLE; static uint16_t act_ms 0; switch (st) { case FIRE_IDLE: if (flame_detected) st FIRE_APPROACH; break; case FIRE_APPROACH: if (front_dist 15) { /* 进入有效灭火距离 */ motor_set_speed(0, 0); /* 先停稳 */ fire_pump_on(); act_ms 0; st FIRE_ACTING; } else { motor_set_speed(30, 30); /* 低速靠近别冲过头 */ } break; case FIRE_ACTING: act_ms 10; /* 与控制周期一致 */ if (act_ms 3000 || !flame_detected) { fire_pump_off(); st FIRE_DONE; } break; case FIRE_DONE: if (!flame_detected) st FIRE_IDLE; break; } }状态机的意义在于把“靠近”和“灭火”分开避免一边高速前进一边喷既灭不准也不安全。执行时间上限 3 秒是保护值按实际泵的规格调整靠近速度给 30占空比百分比是低速档具体数值要实测太快会冲过火源。火焰检测本身建议加连续确认单次触发不进入 APPROACH防止灯光干扰造成误启动。3.4 电机驱动与速度闭环电机驱动常用 L298N 或 TB6612 这类双 H 桥模块STM32 出 PWM 加两路方向。开环给占空比在平地上勉强能用但电池电压下降后同样占空比速度会变慢循迹和避障的阈值就会失准。条件允许时加编码器做速度闭环至少做电压补偿。/* 左右轮差速控制base 为基准速度diff 为转向修正 */ void motor_set_diff(int16_t base, int16_t diff) { int16_t left base diff; int16_t right base - diff; /* 限幅防止溢出和过流 */ if (left 100) left 100; if (left -100) left -100; if (right 100) right 100; if (right -100) right -100; motor_set_speed(left, right); }差速控制是循迹的核心循迹偏差乘一个比例系数得到 diff再叠加到基准速度上。系数太大会画龙太小会转不过弯这个参数只能实车调。限幅到 ±100 对应占空比满量程负值表示反转。如果用了编码器闭环motor_set_speed内部应该变成“设定目标速度—读编码器—PID 算占空比”的流程接口不变这也是前面强调分层的好处。4. 模式仲裁与主循环调度让四种模式不打架模块都能单独跑之后真正的问题才出现四个任务抢一个主循环谁先谁后、谁打断谁。这一章讲调度结构以及调试时怎么把“偶发故障”变成“可复现问题”。4.1 基于状态机的模式仲裁模式仲裁不要写成嵌套 if用状态机加优先级表更稳。每个控制周期先更新所有传感器缓存再按优先级判断命中高优先级就切换状态并执行对应动作未命中则维持当前状态或降级。typedef enum { MODE_REMOTE, MODE_TRACK, MODE_AVOID, MODE_FIRE } car_mode_t; static car_mode_t current_mode MODE_REMOTE; void mode_arbitrate(void) { uint8_t flame flame_confirmed(); uint8_t obst obstacle_check(latest_dist); int8_t track track_scan(); /* 高优先级先判命中即切换 */ if (flame) { current_mode MODE_FIRE; } else if (obst) { current_mode MODE_AVOID; } else if (track ! TRACK_LOST) { current_mode MODE_TRACK; } else if (remote_has_cmd()) { current_mode MODE_REMOTE; } /* 都不满足则保持当前模式由各模式内部做超时退出 */ }这段代码里有个容易忽略的点track ! TRACK_LOST才进循迹丢线时不应该继续按循迹跑而应该保持上一模式或进入找线。另外flame和obst都用了“确认后”的值不是原始值这就是前面滤波模块存在的意义。仲裁函数本身不执行动作只决定模式动作由各模式的处理函数完成职责清晰调试时也容易打点。4.2 定时器节拍与任务分频主循环裸跑也能工作但周期不稳定滤波窗口和 PID 参数都会失准。用定时器中断做节拍10 ms 一次做采集和仲裁100 ms 一次做状态指示和慢速任务灭火执行这类毫秒级计时放在 10 ms 任务里累加。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t slow_cnt 0; if (htim-Instance TIM3) { sensor_update(); /* 采集并滤波10 ms */ mode_arbitrate(); /* 模式仲裁10 ms */ mode_execute(); /* 执行当前模式动作10 ms */ if (slow_cnt 10) { /* 100 ms 慢任务 */ slow_cnt 0; status_led_update(); debug_report(); } } }分频的意义是把“必须准时”和“可以慢一点”的任务分开。传感器采集和仲裁必须准时LED 和调试输出晚几十毫秒无所谓。注意中断里不要做耗时操作串口打印尤其要控制长度否则会拖慢整个节拍表现为“电机偶尔一顿”这种玄学问题查起来很费时间。4.3 调试接口与运行日志小车类项目最怕“现场复现不了”。留一个串口调试通道按固定格式输出模式、距离、循迹偏差、PWM 值出问题时看日志比猜快得多。日志格式建议一行一条字段固定方便用串口助手过滤。void debug_report(void) { printf(M%d,D%u,T%d,L%d,R%d\r\n, current_mode, latest_dist, latest_track, motor_get_left(), motor_get_right()); }字段含义M 是当前模式D 是滤波后距离T 是循迹偏差L/R 是左右 PWM。这五个量基本能覆盖大部分故障定位——模式不对看 M避障误判看 D循迹画龙看 T 和 L/R 的关系。打印频率控制在 10 Hz 以内太高会占满串口带宽。5. 避坑与排查这类小车最容易翻车的五个点下面五条是按“现象—原因—解决”整理的都是这类项目里反复出现的提前避开能省很多返工。5.1 电机一启动单片机就复位现象是静止时一切正常电机一转就重启或程序跑飞。原因通常是电机供电和主控供电共用一路启动瞬间电流拉低电压或者电机引线没有续流处理干扰通过电源串进主控。解决方法是电机电源和主控电源分开走共地但不共供电节点电机端加续流二极管或使用带保护的驱动模块主控电源入口加足够容量的电容。布线时电机线远离信号线这一点在洞洞板上尤其要注意。5.2 循迹在直道上左右画龙现象是车能跟线但走不直左右反复修正。原因多半是比例系数过大或者循迹传感器安装高度不一致导致各路灵敏度不同。解决方法是先把偏差乘系数调小观察响应再逐步加大到刚好不画龙同时用白纸和黑线逐路测传感器输出高度调到各路一致。如果用了离散偏差值系数和偏差档位的乘积要实际算一遍别凭感觉。5.3 避障频繁误判或该停不停现象是空旷处也报障碍或者真遇到障碍却冲过去。原因分两类误判多是测距野值没滤波或者阈值设得比车体实际刹车距离还小该停不停多是仲裁优先级写反避障模式被循迹覆盖。解决方法是先看调试日志里的距离值是否稳定不稳定就加中值滤波再核对仲裁顺序确保避障判断在循迹之前并且障碍确认用了连续多次判定。5.4 灭火模式误触发或执行不到位现象是灯光一照就启动灭火或者靠近了却不喷。原因是火焰传感器没有做连续确认环境光干扰被当成火源执行不到位则是靠近距离阈值和实际喷射距离不匹配。解决方法是火焰检测加连续 N 次确认并配合距离条件执行前先停稳执行时间设上限。传感器安装角度也要调正对前方而不是朝天。5.5 遥控与自主模式切换时动作突变现象是遥控接管瞬间车猛冲或猛停。原因是模式切换时没有做速度斜坡目标速度从当前值直接跳到新值。解决方法是在motor_set_speed里加斜率限制每个控制周期最多变化固定步长切换模式时先把速度降到安全值再切。这个改动很小但对现场演示的观感影响很大。6. 验收与答辩怎么证明这辆车真的达标验收不是把车放地上跑一圈就完事要有可重复的测试项和判定标准。我一般按功能逐项过每项记录通过与否答辩时直接拿这张表说话比口头描述有说服力。验收项测试方法判定标准遥控分别给前进、后退、左转、右转指令四个方向动作正确停止指令能立即停循迹直道、弯道、断线各跑 3 次直道不画龙弯道不脱线断线能停或找线避障正面、侧面放置障碍物阈值内能停或绕开无误触发灭火安全火源如打火机注意安全能靠近并启动执行机构超时能停模式切换运行中插入遥控指令和障碍按优先级切换无突变、无死机连续运行连续运行 10 分钟不复位、不失控、电机不过热测试时有个技巧先分项测再组合测。分项测能定位单个模块的问题组合测才能暴露仲裁和时序的问题。组合测里最值得做的是“循迹途中突然放障碍”和“避障途中给遥控指令”这两个场景最能检验优先级有没有写对。答辩时老师常问的无非是“为什么选这个方案”“某个参数怎么定的”“出问题怎么排查”。前两个问题用前面的选型理由和参数说明回答第三个问题直接讲调试日志和那张避障排查表比背概念管用。还有一点演示前一定把电池充满电压不足会让所有阈值失准这是最冤的翻车方式。从那以后我每次做这类带多种模式的小车都强制先把优先级表和验收表写出来再动手写代码跑通一项勾一项不再靠“感觉差不多了”收尾。希望帮到你。本文还有配套的精品资源点击获取