
简介本资源是一套基于STM32F103C8T6主控的智能小车循迹灭火实验完整源码工程面向嵌入式初学者、电子类课程设计学生及智能车竞赛入门者解决红外循迹与火焰识别联动控制的核心开发问题。压缩包共44个文件含8个头文件.h定义硬件接口与功能模块、3个C源文件.c实现主控逻辑、5个编译中间文件.o/.d/.crf及Keil工程配置文件.uvproj/.uvopt另有电路图.jpg、说明文档.txt和可烧录固件.hex/.axf整体体积仅356KB轻量易部署。已有1259人学习下载适配L293D电机驱动与TT减速电机配套红外循迹与灭火模块代码经作者实机验证可稳定运行。读者可直接导入Keil4环境调试快速掌握STM32底层外设配置、PWM调速、传感器数据采集与多任务协同控制等关键技能并参考目录中SYSTEM/USER/OBJ等标准分层结构理解嵌入式项目组织规范。1. 这不是玩具小车而是一套可复现的嵌入式闭环控制实验系统你手头那块 STM32F103C8T6 最小系统板配上 L293D 驱动和五路红外传感器再接一个简易灭火风扇——它跑起来不是靠“调参玄学”而是基于明确的状态机逻辑、PWM 占空比映射关系与红外电压阈值校准。这套源代码Keil4 工程真正价值在于它把「循迹路径识别→偏差量化→PID 调速响应→火焰触发判定→灭火动作执行」整个链路压缩进不到 8KB 的 Flash 空间里且所有关键参数如 ADC 采样偏移量、电机死区补偿值、火焰检测持续时间都以宏定义形式暴露在config.h中而非硬编码进逻辑层。适合电子类课程设计、工创赛智能物流小车基础模块验证或作为 STM32F103C8T6 初学者理解外设协同控制的实操入口——尤其当你发现用 Arduino 写循迹小车总在弯道抖动时回过头看这份基于寄存器级 GPIOADCTIM 的裸机实现会立刻明白「为什么需要 ADC 校准」「为什么 TIM1 的互补通道要禁用死区」。它不依赖 HAL 库不封装底层每一行GPIO_ResetBits()和ADC_GetConversionValue()都对应真实硬件动作。2. 从硬件连接到固件架构L293D 驱动与红外传感器信号链解析2.1 L293D 电机驱动电路的关键约束与接线验证L293D 并非即插即用的“黑盒”芯片。其输入逻辑电平兼容性、输出电流能力、使能端EN控制方式直接决定小车能否稳定启停。本工程中STM32F103C8T6 的 PA0–PA3 分别连接 L293D 的 IN1–IN4PB0/PB1 作为 ENA/ENB高电平使能电机供电独立于 MCU5V 电机电源需加 100μF 电解电容滤波。必须注意L293D 输出最大持续电流为 600mA/通道TT 减速电机堵转电流常超 1A因此程序中设置了PWM 占空比上限为 70%见motor.c中MOTOR_MAX_DUTY定义避免芯片过热EN 引脚必须用推挽输出模式非开漏否则无法可靠拉高电机反电动势需通过 L293D 内置续流二极管释放PCB 布线时务必让电机电源地与 MCU 地单点共接否则 ADC 采样会受干扰。提示若小车原地打转或单侧失速先用万用表测 L293D 对应 OUT 引脚电压——正常运行时应随 PWM 变化若恒为 0V 或 5V检查 EN 引脚是否被误配置为浮空输入。2.1.1 验证 L293D 控制逻辑的最小测试代码// 在 main() 初始化后插入用于快速验证电机方向控制 void motor_test(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_GPIOB, ENABLE); // PA0-PA3: IN1-IN4; PB0: ENA; PB1: ENB GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_Init(GPIOB, GPIO_InitStructure); // 全部输出低电平使电机停止 GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3); GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1); // 测试左轮前进IN11, IN20 → ENA1 GPIO_SetBits(GPIOA, GPIO_Pin_0); // IN11 GPIO_ResetBits(GPIOA, GPIO_Pin_1); // IN20 GPIO_SetBits(GPIOB, GPIO_Pin_0); // ENA1 // 延时 2s 后停止 for(volatile int i0; i2000000; i); GPIO_ResetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1); GPIO_ResetBits(GPIOB, GPIO_Pin_0); }该代码绕过 PWM 模块直接操控 GPIO 模拟 H 桥逻辑。执行后左轮应正转右轮静止。若异常说明硬件接线或引脚定义错误常见错误GPIO_Pin_0误写为GPIO_Pin_1导致 IN1/IN2 同时为高电机锁死。2.2 五路红外循迹模块的模拟信号处理与阈值标定本实验采用 5 路红外反射式传感器TCRT5000 模块每路输出为 0–3.3V 模拟电压经 STM32F103C8T6 的 ADC1 通道 0–4 采集。关键点在于传感器安装高度需距地面 1–2cm过高则灵敏度下降过低易受灰尘干扰黑白线对比度影响 ADC 值分布白底典型值 2.8–3.3VADC≈3800黑线区域 0.3–0.8VADC≈400–1000程序未使用 DMA 传输而是轮询 ADC 转换完成标志ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)确保每次读取都是最新采样值。2.2.1 ADC 初始化与校准流程void ADC1_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1 | RCC_APB2PERIPH_GPIOA, ENABLE); // PA0-PA4 配置为模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式支持多通道 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 单次转换避免资源占用 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 5; ADC_Init(ADC1, ADC_InitStructure); // 配置通道顺序CH0→CH1→CH2→CH3→CH4 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 5, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); // 必须校准否则读数漂移 }注意ADC_SampleTime_239Cycles5是针对红外传感器缓慢变化信号的合理选择。若缩短采样时间如71Cycles5ADC 值波动增大导致循迹抖动若过长如480Cycles5采样频率不足弯道响应滞后。2.3 灭火模块的硬件接口与触发逻辑灭火模块采用直流风扇5V/0.2A 三极管驱动S8050由 STM32 的 PC13 控制。电路设计为低电平有效PC130 时风扇转动原因在于STM32F103C8T6 的 PC13 引脚内部上拉能力强可确保待机时风扇断电风扇启动电流较大直接驱动易使 MCU 复位故通过 S8050 放大电流基极串 1kΩ 电阻限流。程序中灭火动作分两级触发火焰检测红外火焰传感器中心波长 940nm输出模拟电压当 ADC 值 FIRE_THRESHOLD默认 2500且持续 30ms防误触发灭火执行拉低 PC13同时关闭电机防止小车移动中灭火失败持续 1.5s 后自动复位。2.3.1 火焰检测抗干扰策略#define FIRE_DEBOUNCE_TIME 30 // ms需对应 SysTick 中断周期 uint16_t fire_adc_val 0; uint8_t fire_debounce_cnt 0; uint8_t fire_active 0; void check_fire(void) { // 仅在循迹暂停时检测避免运动震动干扰 if (state STATE_STOP || state STATE_FIRE_READY) { fire_adc_val ADC_GetConversionValue(ADC1); // 假设火焰传感器接 ADC1_CH8PC0 if (fire_adc_val FIRE_THRESHOLD) { if (fire_debounce_cnt FIRE_DEBOUNCE_TIME / 10) { // SysTick 10ms 中断 fire_active 1; fire_debounce_cnt 0; } } else { fire_debounce_cnt 0; } } }此处FIRE_DEBOUNCE_TIME / 10表示需连续 3 次 SysTick 中断30ms满足阈值才确认火焰存在。若将FIRE_THRESHOLD设为 2000可能因环境光突变误触发设为 3000 则可能漏检远距离火焰——实际调试需用万用表测火焰传感器输出电压再换算 ADC 值。3. 循迹核心算法基于加权重心法的偏差计算与 PID 速度调节3.1 五路传感器布局与加权重心法数学模型五路传感器呈直线排列间距 1.5cm编号从左到右为 S0–S4。当小车居中行驶时S2 位于黑线上方S1/S3 部分覆盖黑线边缘S0/S4 输出高电平白底。加权重心法将各传感器 ADC 值视为“灰度权重”计算质心位置$$ \text{pos} \frac{\sum_{i0}^{4} (i-2) \times \text{adc}i}{\sum{i0}^{4} \text{adc}_i} $$其中(i-2)是传感器相对中心的坐标S0-2, S1-1, S20, S31, S42。结果pos ∈ [-2, 2]负值表示小车偏右需左转正值表示偏左需右转。该方法优于简单查表法能线性反映偏离程度为 PID 提供连续输入。3.1.1 加权重心计算的 C 实现与定点数优化int16_t calculate_position(void) { uint16_t adc_val[5]; int32_t numerator 0, denominator 0; int16_t pos 0; // 读取五路 ADC 值已预存于全局数组 adc_buf[] for(uint8_t i0; i5; i) { adc_val[i] adc_buf[i]; // adc_buf[] 由 ADC 扫描填充 // 滤波剔除明显异常值如 200 或 4000 if(adc_val[i] 200 || adc_val[i] 4000) adc_val[i] 0; } // 计算分子带权重和分母 numerator (-2)*adc_val[0] (-1)*adc_val[1] 0*adc_val[2] 1*adc_val[3] 2*adc_val[4]; denominator adc_val[0] adc_val[1] adc_val[2] adc_val[3] adc_val[4]; if(denominator 1000) { // 避免除零且要求至少有信号 pos (int16_t)(numerator * 100 / denominator); // 扩大100倍保留小数精度 } else { pos 0; // 全白或全黑保持直行 } return pos; }提示numerator * 100 / denominator是定点数除法替代浮点运算的常用技巧。若pos绝对值 5则认为偏差微小维持当前 PWM若 30则进入急转弯模式左/右轮差速加大。3.2 PID 控制器的参数整定与电机响应映射本工程采用位置式 PID但仅使用P 和 D 项无 I 项因积分项在循迹中易累积误差导致过冲。控制律为$$ \text{output} K_p \times \text{pos} K_d \times (\text{pos} - \text{pos}_{\text{prev}}) $$其中Kp15,Kd8是经验值需根据小车机械结构调整Kp过大 → 直线段左右摇摆过小 → 弯道响应迟钝Kd过大 → 电机启停有顿挫感过小 → 过弯时甩尾。3.2.1 PWM 输出与电机物理特性的非线性映射#define BASE_SPEED 600 // 基础占空比0-1000 #define KP 15 #define KD 8 int16_t pid_output 0; static int16_t pos_prev 0; void update_motor_speed(int16_t pos) { int16_t diff pos - pos_prev; pid_output KP * pos KD * diff; pos_prev pos; // 映射到左右轮 PWM左轮 BASE_SPEED - pid_output右轮 BASE_SPEED pid_output int16_t left_pwm BASE_SPEED - pid_output; int16_t right_pwm BASE_SPEED pid_output; // 限幅防止反转或超速 if(left_pwm 0) left_pwm 0; if(left_pwm 1000) left_pwm 1000; if(right_pwm 0) right_pwm 0; if(right_pwm 1000) right_pwm 1000; set_motor_pwm(left_pwm, right_pwm); // 调用 TIM3 PWM 输出函数 }此处BASE_SPEED600对应约 60% 占空比是 TT 电机在 5V 下的经济转速。若小车动力不足可提升至 700但需同步检查 L293D 温升若转弯半径过大可增大KP至 20但必须重测Kd防振荡。4. 火焰识别与状态机调度从单任务轮询到事件驱动迁移4.1 主循环状态机设计与关键状态转移条件程序采用有限状态机FSM管理整体流程状态定义在main.h中状态枚举触发条件动作STATE_INIT上电复位初始化外设LED 指示STATE_TRACKING按键 S1 按下启动循迹读取传感器STATE_FIRE_DETECTfire_active1停车启动灭火定时器STATE_FIRE_EXEC定时器超时风扇启动延时 1500msSTATE_STOP灭火完成或按键 S2电机停转等待指令状态转移非简单线性例如STATE_TRACKING中若检测到火焰立即跳转STATE_FIRE_DETECT而非等当前循迹周期结束。这种设计避免了“看到火焰还在拐弯”的逻辑缺陷。4.1.1 状态机核心调度逻辑typedef enum { STATE_INIT, STATE_TRACKING, STATE_FIRE_DETECT, STATE_FIRE_EXEC, STATE_STOP } system_state_t; system_state_t state STATE_INIT; uint32_t state_timer 0; void state_machine(void) { switch(state) { case STATE_INIT: if(key_pressed(KEY1)) { // S1 按下 state STATE_TRACKING; state_timer 0; } break; case STATE_TRACKING: read_sensors(); // 更新 adc_buf[] int16_t pos calculate_position(); update_motor_speed(pos); if(fire_active) { state STATE_FIRE_DETECT; fire_active 0; state_timer 0; } break; case STATE_FIRE_DETECT: if(state_timer 50) { // 延时 50ms 确认火焰稳定 state STATE_FIRE_EXEC; GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 启动风扇 state_timer 0; } break; case STATE_FIRE_EXEC: if(state_timer 150) { // 150 * 10ms 1500ms GPIO_SetBits(GPIOC, GPIO_Pin_13); // 关闭风扇 state STATE_STOP; } break; case STATE_STOP: stop_motors(); if(key_pressed(KEY2)) { // S2 按下 state STATE_INIT; } break; } state_timer; }注意state_timer以 10ms 为单位递增由 SysTick 中断服务函数SysTick_Handler()每 10ms 调用一次state_machine()。若将 SysTick 改为 1ms需同步修改state_timer比较阈值否则状态延时失效。4.2 火焰传感器与环境光干扰的分离策略火焰传感器如 YG-201对 940nm 红外敏感但白炽灯、阳光也含该波段。本程序通过双阈值动态校准抑制干扰启动时读取环境光基准值env_base遮盖传感器后测得实时值fire_adc与env_base的差值delta fire_adc - env_base作为有效火焰强度仅当delta 300且持续 30ms 才触发。该策略比单一固定阈值鲁棒得多。调试时可用手机红外发射器遥控器近距离照射传感器观察delta是否跃升——若无反应检查传感器供电是否达 5V部分模块需 5V 才能启动内部放大器。5. 实战调试技巧用逻辑分析仪抓取 PWM 与传感器时序定位抖动根源5.1 使用 Saleae Logic 抓取关键信号的配置要点当小车循迹出现规律性抖动如每 200ms 左右左偏一次单纯调 PID 参数无效时需用逻辑分析仪捕获以下信号PA0左轮 IN1与 PA1左轮 IN2验证 H 桥电平是否按预期切换TIM3_CH1左轮 PWM与 TIM3_CH2右轮 PWM检查占空比是否突变ADC1_EOCADC 转换完成中断引脚确认采样间隔是否稳定应为 10ms±0.5msPC13灭火控制排除误触发。5.1.1 逻辑分析仪通道分配与触发设置通道连接引脚采样率触发条件用途CH0PA01MHz上升沿检测 IN1 电平翻转时刻CH1PA11MHz上升沿检测 IN2 电平翻转时刻CH2TIM3_CH11MHz边沿观察 PWM 波形稳定性CH3ADC1_EOC1MHz下降沿确认 ADC 转换完成时间点提示若ADC1_EOC信号周期不恒定说明ADC_StartConversion()调用时机受其他任务阻塞如printf占用 UART需移除调试打印或改用 ITM。5.2 常见抖动问题与对应修复方案现象逻辑分析仪观测特征根本原因修复方法直线段周期性左偏PWM 占空比每 100ms 下降 5%随后恢复SysTick中断被长耗时函数阻塞如未优化的delay_ms()将延时改为while(SysTick-VAL target)等待或用定时器中断替代弯道急转弯后冲出赛道STATE_TRACKING状态下pos值突变为 40超出范围传感器某路 ADC 读数异常如 S4 接触不良导致读数为 0在calculate_position()中加入adc_val[i] 200判定并置零火焰检测延迟 2sPC13低电平起始时间比fire_active1晚 200ms状态机中STATE_FIRE_DETECT的延时设为 200 而非 50修改case STATE_FIRE_DETECT:中if(state_timer 200)为50最后补充一个硬核技巧若小车在深色木地板上循迹失效不要急着调FIRE_THRESHOLD先用万用表测五路传感器输出电压——深色木纹反射率接近黑线会导致所有 ADC 值跌至 800 以下此时需降低FIRE_THRESHOLD至 1800并在calculate_position()中增加“全黑”判断分支if(denominator 500) pos0;强制小车直行穿越低对比度区域。本文还有配套的精品资源点击获取