简介:本资源是一套基于STM32F103C8T6主控的智能小车循迹灭火实验完整嵌入式源码工程,面向电子类专业学生、单片机初学者及智能硬件实践者,解决红外循迹与火焰识别联动控制的核心开发问题,适用于课程设计、电子竞赛备赛及创客项目快速验证。压缩包共44个文件,含8个头文件(.h)定义外设接口与模块参数,3个C源文件(.c)实现主控逻辑、循迹算法与灭火触发,另有编译输出文件(.axf/.hex/.map)、工程配置(.uvproj/.uvopt)、电路参考图(.jpg)及关键说明文档(.txt),整体体积仅356KB,轻量易部署。已有1259人学习下载,代码经作者实机测试验证,配套L293D电机驱动与TT减速电机,支持红外传感器阵列采集路径信息,并集成灭火模块响应逻辑,结构清晰、注释完整,便于理解底层驱动时序、状态机设计与多任务协同思路。
1. STM32F103C8T6智能小车不是“拼凑玩具”,而是嵌入式工程能力的实体验证场
你手头那个标着“STM32F103C8T6智能小车循迹灭火实验程序源代码.rar”的压缩包,绝不是一段可直接烧录就能跑通的“万能代码”。它本质是一套面向教学与竞赛场景的嵌入式系统最小闭环:以Cortex-M3内核的STM32F103C8T6为控制中枢,通过红外传感器阵列实现路径识别(循迹),结合火焰传感器触发执行机构(灭火),全程依赖裸机或轻量级HAL库驱动,不依赖RTOS调度。这类项目常见于高校电子设计竞赛、工创赛智能物流小车初阶训练、以及单片机课程设计——它要求你同时理解硬件信号链(传感器→ADC/IO→MCU→PWM→电机)、实时响应约束(循迹需5–20ms级采样周期)、以及多任务协同逻辑(巡线优先级高于灭火,但火焰检测必须零延迟中断响应)。新手常卡在“代码编译通过却小车乱转”,老手则更关注“五路循迹传感器数据如何抗抖动滤波”“火焰阈值如何随环境光动态校准”。本文不讲原理图识读或PCB布线,只聚焦从解压源码到稳定运行的可验证、可调试、可复现路径。
2. 解析源码结构与硬件映射关系:先读懂引脚定义再碰代码
拿到.rar文件后,第一步不是烧录,而是建立代码与物理板卡的精确映射。STM32F103C8T6最小系统板虽有标准布局,但不同厂商对LED、按键、传感器接口的GPIO分配存在差异,而源码中硬编码的引脚号若与你的板子不一致,会导致功能完全失效。
2.1 源码目录典型结构与关键文件定位
解压后常见目录结构如下(以主流Keil MDK工程为例):
├── Core/ // HAL库或标准外设库核心 │ ├── inc/ // 头文件:stm32f10x.h, stm32f10x_conf.h │ └── src/ // 库源码:stm32f10x_gpio.c, stm32f10x_tim.c等 ├── Drivers/ // 自定义驱动 │ ├── sensor/ // 红外循迹模块(如TCRT5000)、火焰传感器(如GY-906) │ └── motor/ // L298N或TB6612电机驱动控制逻辑 ├── Application/ // 主业务逻辑 │ ├── main.c // 系统初始化、主循环框架 │ ├── track.c/.h // 循迹算法(差速PID或状态机) │ └── fire.c/.h // 火焰检测与喷水/风扇触发 └── Startup/ // 启动文件:startup_stm32f10x_md.s提示:重点检查
Application/track.c中的Track_Init()函数和Drivers/sensor/下的传感器初始化函数。这些位置必然包含GPIO端口(如GPIOA,GPIOB)和具体引脚号(如GPIO_Pin_0,GPIO_Pin_1)的宏定义,它们直接对应硬件电路。
2.2 引脚功能对照表:用数据手册反向验证代码
STM32F103C8T6有37个通用IO引脚,但常用作传感器输入/电机输出的仅10–15个。必须将源码中出现的引脚编号,与你的开发板原理图逐一对齐。例如:
| 源码中定义 | 常见物理意义 | 数据手册确认要点 |
|---|---|---|
GPIO_Pin_0onGPIOA | 左侧红外传感器D0输出 | 查STM32F103C8T6数据手册第42页:PA0支持模拟输入(ADC1_IN0),若传感器为模拟输出则需启用ADC;若为数字开关量,则配置为浮空输入 |
GPIO_Pin_8onGPIOB | 右侧电机使能ENB | PB8无重映射功能,直接配置为推挽输出,注意L298N ENB引脚需高电平有效 |
GPIO_Pin_12onGPIOA | 火焰传感器中断引脚 | PA12支持外部中断线12(EXTI12),需在NVIC_Init()中使能对应通道,并设置触发边沿(建议下降沿,因火焰信号常为低电平有效) |
2.2.1 验证方法:用万用表+逻辑分析仪交叉比对
- 步骤1:断电状态下,用万用表二极管档测量开发板上红外传感器VOUT引脚与MCU对应GPIO焊盘是否导通;
- 步骤2:上电后,用逻辑分析仪抓取该GPIO引脚波形,手动遮挡/释放红外传感器,观察电平跳变是否与代码中
GPIO_ReadInputDataBit()返回值一致; - 步骤3:若不一致,立即修改源码中对应引脚定义(如将
GPIO_Pin_0改为GPIO_Pin_1),重新编译烧录。
2.3 时钟配置陷阱:HSE/HSI选择错误导致定时器失准
循迹依赖精准的PWM占空比调节电机速度,而PWM由TIM2/TIM3产生,其时钟源来自APB1总线。若源码中RCC_Configuration()函数错误启用了外部晶振(HSE)但你的板子未焊接8MHz晶振,系统将卡死在while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET)循环中。此时需强制切换至内部高速时钟(HSI):
// 修改前(依赖外部晶振) RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); // 修改后(强制使用内部8MHz RC振荡器) RCC_HSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);注意:切换时钟源后,所有基于SysTick或TIM的延时函数(如
Delay_ms(10))需重新校准。例如,若原代码假设SysTick频率为72MHz,而HSI实际为8MHz,则10ms延时需将计数值从72000改为8000。
3. 循迹算法落地:从五路红外传感器原始数据到稳定转向
五路循迹传感器(左2、中1、右2布局)输出的是5位二进制状态码,但直接用0b10101这样的原始值做判断极易受光照干扰。真实工程中必须经过硬件滤波+软件状态机两级处理。
3.1 硬件层:红外接收管供电与上拉电阻匹配
五路模块通常共用VCC/GND,但每路OUT引脚需独立接上拉电阻(4.7kΩ)至3.3V。若使用5V上拉,STM32F103C8T6的IO口可能因过压损坏;若上拉电阻过大(如10kΩ),信号上升沿变缓,高速巡线时MCU读取到错误电平。实测推荐参数:
| 传感器类型 | 上拉电阻 | 供电电压 | 典型输出高电平 |
|---|---|---|---|
| 数字开关量(TCRT5000) | 4.7kΩ | 3.3V | ≥2.4V(满足STM32输入高电平阈值) |
| 模拟输出(QRE1113) | 不接上拉 | 3.3V | 0–3.3V连续变化,需接ADC通道 |
3.2 软件层:状态机驱动的循迹决策逻辑
源码中track.c的主循环通常采用状态机而非简单if-else,因其能处理传感器瞬态抖动。典型状态定义如下:
typedef enum { TRACK_STATE_STRAIGHT, // 0b00100:中路黑,左右白 → 直行 TRACK_STATE_LEFT_TURN, // 0b01100 或 0b01000:左路开始变黑 → 左转 TRACK_STATE_RIGHT_TURN, // 0b00010 或 0b00011:右路开始变黑 → 右转 TRACK_STATE_LOST, // 0b00000:全白 → 丢失轨迹,执行搜索策略 TRACK_STATE_ERROR // 0b11111:全黑(异常)→ 停车报警 } TrackState;3.2.1 关键防抖逻辑:连续3次采样一致才更新状态
// 在main()循环中调用 static uint8_t track_sample_buffer[3] = {0}; // 存储最近3次采样值 static uint8_t sample_index = 0; uint8_t current_state = Read_Track_Sensors(); // 返回5位状态码(0–31) track_sample_buffer[sample_index] = current_state; sample_index = (sample_index + 1) % 3; // 仅当3次采样值完全相同时才更新全局状态 if (track_sample_buffer[0] == track_sample_buffer[1] && track_sample_buffer[1] == track_sample_buffer[2]) { current_track_state = Get_State_From_Code(track_sample_buffer[0]); }逻辑说明:
Read_Track_Sensors()函数需依次读取5个GPIO引脚电平并组合成一字节。Get_State_From_Code()根据预设查表(如state_table[32])返回枚举值。此设计避免单次误触发导致小车突然转向。
3.3 电机控制:PWM占空比与转向角的非线性映射
L298N驱动双轮时,左右轮PWM占空比差值决定转向半径。但线性差值(如左80%、右40%)在低速时转向过猛,高速时又转向不足。工程实践采用分段映射:
| 当前速度档位 | 直行基础占空比 | 左转时左轮减幅 | 左转时右轮增幅 |
|---|---|---|---|
| 低速(<30%) | 30% | -10% | +20% |
| 中速(30–70%) | 50% | -15% | +25% |
| 高速(>70%) | 80% | -5% | +10% |
此参数需通过实际赛道测试调整,记录在motor.c的Motor_SetSpeed()函数中。 |
4. 火焰检测与灭火执行:中断优先级与执行机构时序控制
火焰传感器(如MLX90614红外测温或KY-026数字火焰模块)的响应速度远高于循迹,必须用中断而非轮询。但若中断服务程序(ISR)中执行喷水动作,会阻塞主循环导致循迹失控。
4.1 外部中断配置:抢占优先级必须高于循迹定时器
STM32F103C8T6的NVIC支持16级抢占优先级(0最高)。火焰检测中断需设为最高优先级(0),而TIM2的PWM更新中断设为次高(1),确保火焰信号到来时能立即打断循迹逻辑:
NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 假设火焰接PA12,对应EXTI12 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 最高抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);注意:若使用多个传感器(如同时接火焰和避障),需为不同EXTI线分配不同抢占优先级,避免高优先级中断被同级中断阻塞。
4.2 中断服务程序:只置标志位,不执行耗时操作
volatile uint8_t fire_detected_flag = 0; void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line12) != RESET) { fire_detected_flag = 1; // 仅设置标志,清中断标志 EXTI_ClearITPendingBit(EXTI_Line12); } } // 在main()循环中处理 if (fire_detected_flag) { fire_detected_flag = 0; Fire_Extinction_Sequence(); // 执行灭火流程:停转电机→启动风扇/水泵→延时2s→恢复循迹 }4.2.1 灭火执行序列的时序约束
Fire_Extinction_Sequence()必须严格遵循以下时序(单位:毫秒):
| 步骤 | 动作 | 时长 | 依据 |
|---|---|---|---|
| 1 | 立即停止左右轮PWM输出 | ≤1ms | 防止惯性冲过火源 |
| 2 | 触发继电器驱动水泵/风扇 | 10ms | 继电器吸合时间典型值 |
| 3 | 保持灭火执行状态 | 2000ms | 确保火焰彻底熄灭,避免复燃 |
| 4 | 恢复循迹模式 | — | 重启电机PWM,重新进入TRACK_STATE_STRAIGHT |
此序列需用SysTick或TIM4实现精确延时,禁用for()循环延时。 |
5. 调试与性能优化:用串口日志定位传感器数据漂移
当小车在特定路段反复脱轨,或火焰传感器误触发,问题往往不在代码逻辑,而在传感器原始数据的稳定性。此时需开启串口调试,将关键变量实时输出。
5.1 串口初始化:复用USART1避免额外引脚占用
STM32F103C8T6的USART1默认映射到PA9(TX)和PA10(RX),这两引脚在多数最小系统板上已引出为USB转串口接口,无需额外接线:
void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1 | RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } // 发送字符串函数(用于调试) void USART1_SendString(char *str) { while(*str) { while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待发送完成 USART_SendData(USART1, *str++); } }5.2 关键数据日志化:每100ms输出一次传感器快照
在main()循环中插入日志输出:
static uint32_t log_timer = 0; if (SysTick_GetCounter() - log_timer >= 100) { // 每100ms触发 log_timer = SysTick_GetCounter(); // 输出五路传感器原始值(0/1) USART1_SendString("TRK:"); for(uint8_t i=0; i<5; i++) { char buf[2]; sprintf(buf, "%d", track_raw_data[i]); USART1_SendString(buf); } USART1_SendString(" FIRE:"); USART1_SendString(track_fire_status ? "ON" : "OFF"); USART1_SendString("\r\n"); }技巧:将串口输出连接至电脑,用Tera Term或XCOM工具捕获日志。当发现某一路传感器在直道上持续输出0(应为1),即可锁定该路红外发射管老化或接收管积灰,针对性清洁即可解决。
5.3 环境光自适应:火焰阈值动态校准算法
固定阈值(如ADC值>800判定为火焰)在强光/弱光环境下失效。工程方案是每5秒采集一次环境光基准值,动态调整火焰判据:
#define FIRE_BASELINE_UPDATE_INTERVAL 5000 // ms static uint16_t fire_baseline = 0; static uint32_t last_baseline_update = 0; if (SysTick_GetCounter() - last_baseline_update >= FIRE_BASELINE_UPDATE_INTERVAL) { last_baseline_update = SysTick_GetCounter(); fire_baseline = ADC_GetConversionValue(ADC1); // 读取无火焰时的ADC值 } // 实时火焰判断 uint16_t fire_adc = ADC_GetConversionValue(ADC1); if (fire_adc > fire_baseline + 200) { // 动态偏移量200,可调 Set_Fire_Flag(); }此算法使小车在教室灯光、窗外阳光直射等不同场景下均能稳定识别火焰,无需人工反复调试阈值。
本文还有配套的精品资源,点击获取