尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103红外循迹与超声波避障协同控制实战

STM32F103红外循迹与超声波避障协同控制实战 简介本资源是一套基于STM32F103微控制器的智能循迹避障小车完整工程代码与开发资料面向嵌入式初学者、电子设计竞赛备赛学生及STM32实践开发者解决智能小车自主导航、路径跟踪与动态避障的核心实现问题。压缩包共192个文件含34个C源文件如stm32f10x_usart.c、system_stm32f10x.c等底层驱动与系统初始化模块、36个头文件.h、35个编译中间文件.d及34个目标文件.o辅以uvprojx工程配置、hex可执行镜像、map链接映射等关键构建产物整体大小3.81MB结构完整开箱即编译。已有6740人学习下载覆盖Keil uVision开发环境下的全链路实践从红外传感器信号采集与PID循迹算法实现到超声波测距中断处理与安全停车逻辑再到电机PWM驱动与多任务协调控制全部代码模块清晰、注释充分便于理解底层外设配置与实时控制逻辑。1. 为什么 STM32F103 小车一上电就“乱跑”——红外循迹超声波避障停车的闭环控制本质很多刚做完硬件连线的同学会发现小车通电后轮子狂转、红外传感器读数跳变、超声波测距忽高忽低甚至撞墙前一秒才“想起来”刹车。这不是模块坏了而是没理解这个标题里藏着的三层耦合关系红外循迹提供路径引导信号模拟量/数字量超声波提供前方障碍物距离时间→距离换算而 STM32F103 的核心任务不是“分别处理两个模块”而是把它们统一纳入一个实时决策环路——当循迹偏差超过阈值时微调方向当超声波检测到 15cm 障碍时强制停车且两者不能互相抢占 CPU 时间片。这要求你必须用标准外设库v3.50配置好定时器中断优先级、GPIO 输入模式浮空/上拉、ADC 采样精度尤其五路红外需同步采样而不是靠 while(1) 里轮询。适合电子设计竞赛备赛者、毕业设计选题为智能车的学生以及想摆脱 Arduino 模块化黑盒、真正掌握 STM32 底层时序控制的嵌入式入门者。2. 用 STM32F103 最小系统跑通红外循迹与超声波避障的最小硬件链路2.1 硬件选型依据为什么必须用 STM32F103C8T6 而非更便宜的型号STM32F103C8T6 是当前最主流的入门级 Cortex-M3 MCU其关键资源恰好匹配本项目硬性需求5 路红外循迹传感器需至少 5 个 GPIO 输入引脚推荐 PA0–PA4且需支持外部中断或 ADC 采样F103C8T6 的 GPIOA 全部复用功能完整而 F103CBT6 虽引脚更多但成本翻倍F103RBT6 则因封装过大不适用于小车底盘空间。HC-SR04 超声波模块需 1 个 GPIO 输出触发脉冲PB0、1 个 GPIO 输入捕获回响时间PB1要求该引脚支持输入捕获模式IC——F103C8T6 的 TIM3_CH2 正好映射到 PB1无需额外逻辑门电路。L298N 电机驱动需 4 路 PWM 输出IN1–IN4TIM2 和 TIM3 各提供 4 路互补通道F103C8T6 的 TIM2_CH1/CH2/CH3/CH4 可直接驱动双路直流电机。提示若使用“stm32f103最小系统原理图”务必确认晶振电容为 20–22pF非 30pF否则在 72MHz 主频下 USB 通信或 ADC 采样易失锁常见错误是照搬 STM32F4 系列电容值导致error: no stm32 target found!。2.2 关键外设初始化顺序GPIO → RCC → NVIC → 定时器 → ADC标准库 v3.50 下初始化顺序直接影响传感器响应一致性。以下为不可省略的初始化链Keil MDK v5.36 环境// 1. 开启所有相关时钟RCC RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_GPIOB | RCC_APB2PERIPH_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TIM2 | RCC_APB1PERIPH_TIM3 | RCC_APB1PERIPH_TIM4, ENABLE); // 2. 初始化红外传感器 GPIOPA0–PA4浮空输入 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN_FLOATING; // 必须浮空避免内部上拉干扰模拟电压 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); // 3. 初始化超声波触发/回响引脚PB0 输出PB1 输入捕获 GPIO_InitStruct.GPIO_Pin GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOB, GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStruct); // 4. 配置 TIM3 为输入捕获模式测量 HC-SR04 回响时间 TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_ICInitTypeDef TIM_ICInitStruct; TIM_TimeBaseStruct.TIM_Period 0xFFFF; // 自动重装载值 TIM_TimeBaseStruct.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz 计数频率 TIM_TimeBaseStruct.TIM_ClockDivision 0; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStruct); TIM_ICInitStruct.TIM_Channel TIM_Channel_2; // PB1 映射到 TIM3_CH2 TIM_ICInitStruct.TIM_ICPolarity TIM_ICPolarity_Rising; // 上升沿触发 TIM_ICInitStruct.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStruct.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStruct.TIM_ICFilter 0x0; TIM_ICInit(TIM3, TIM_ICInitStruct);2.2.1 为什么 TIM3_CH2 必须用 PB1HC-SR04 的回响信号是 TTL 电平方波高电平持续时间即为超声波往返时间t。若用普通 GPIO 轮询检测上升沿/下降沿误差可达 10μs 以上对应距离误差 ±1.7mm。而 TIM3_CH2 配合输入捕获功能可在硬件级记录边沿时刻精度达 1μs对应 0.17mm。PB1 是唯一能映射到 TIM3_CH2 的引脚若误接至 PB0仅支持 TIM3_CH3则无法触发捕获中断导致ultrasonic_distance 0恒成立。2.2.2 红外传感器为何不用 ADC 而用 GPIO 浮空输入五路红外模块如 TCRT5000输出为模拟电压0–3.3V但实际应用中我们只关心“是否压线”这一二值状态。若启用 ADC 采样需配置 ADC1 通道 0–4开启扫描模式设置采样时间 ≥1.5μs每次转换耗时约 12.5μs5 路连续采样需 62.5μs期间无法响应超声波中断更严重的是ADC 参考电压受 VDD 波动影响同一传感器在不同光照下阈值漂移达 ±0.3V。改用 GPIO 浮空输入 软件阈值判别读取GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_x)配合硬件比较器需外加 LM339或直接设定数字阈值如if (val 2000) line_detected 1响应延迟 1μs且抗电源噪声能力更强。3. 实现红外循迹与超声波避障协同控制的核心算法逻辑3.1 循迹状态机从五路传感器原始数据到转向指令的映射五路红外传感器左2、中1、右2排列间距需 ≤2cm否则弯道丢失中间线。原始读数为 0反射强压线或 1反射弱离线组合成 5 位二进制码如00100表示仅中间传感器压线。标准库中不提供状态机框架需手动构建// 定义传感器状态码LSB 为最左传感器 #define STATE_00000 0 // 全离线悬空或强光干扰 #define STATE_00100 1 // 中线正常直行 #define STATE_01100 2 // 左偏向左修正 #define STATE_00110 3 // 右偏向右修正 #define STATE_10000 4 // 极左急左转 #define STATE_00001 5 // 极右急右转 #define STATE_11111 6 // 全压线可能为黑块或终点 uint8_t get_track_state(void) { uint8_t state 0; state | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0); // PA0 最左 state | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) 1); // PA1 左中 state | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) 2); // PA2 中 state | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) 3); // PA3 右中 state | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) 4); // PA4 最右 return state; } // 状态映射表state_code → PWM 占空比差值左轮PWM - 右轮PWM const int8_t pwm_diff_table[32] { [0] 0, [1] 0, [2] -30, [3] 30, [4] -80, [5] 80, [6] 0, [7] 0, [8] 0, [9] 0, [10] 0, [11] 0, [12] 0, [13] 0, [14] 0, [15] 0, [16] 0, [17] 0, [18] 0, [19] 0, [20] 0, [21] 0, [22] 0, [23] 0, [24] 0, [25] 0, [26] 0, [27] 0, [28] 0, [29] 0, [30] 0, [31] 0 };3.1.1 为什么状态码 0全离线不直接停机传感器全离线可能是① 小车驶出赛道需停机② 强环境光淹没红外反射需降低灵敏度③ 黑色胶带反光率过高需更换传感器。若直接停机小车会在弯道末端反复启停。正确做法是连续 3 次读取state 0且超声波距离 30cm 时才进入“寻线模式”——以 30% 速度原地旋转每 200ms 采样一次直到state ! 0。3.2 超声波测距与避障决策的时序约束HC-SR04 触发脉冲必须严格为 10μs 高电平否则模块不响应。标准库无微秒级延时函数需用SysTick或NOP循环void ultrasonic_trigger(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_0); // PB0 低电平 for(volatile uint16_t i 0; i 10; i); // 粗略延时实际需用 SysTick_Config(72) delay_us(10) GPIO_SetBits(GPIOB, GPIO_Pin_0); for(volatile uint16_t i 0; i 10; i); GPIO_ResetBits(GPIOB, GPIO_Pin_0); } // 在 TIM3 捕获中断中计算距离 void TIM3_IRQHandler(void) { static uint16_t rising_time 0, falling_time 0; static uint8_t edge_flag 0; if (TIM_GetITStatus(TIM3, TIM_IT_CC2) ! RESET) { if (edge_flag 0) { rising_time TIM_GetCapture2(TIM3); edge_flag 1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Falling; TIM_ICInit(TIM3, TIM_ICInitStructure); } else { falling_time TIM_GetCapture2(TIM3); uint32_t pulse_width falling_time - rising_time; ultrasonic_distance pulse_width * 0.017; // cm μs × 0.017 edge_flag 0; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInit(TIM3, TIM_ICInitStructure); } TIM_ClearITPendingBit(TIM3, TIM_IT_CC2); } }3.2.1 距离计算为何乘以 0.017超声波在空气中传播速度约 340m/s 34000cm/s即 0.034cm/μs。但 HC-SR04 测量的是往返时间故单程距离 (时间 × 0.034) / 2 时间 × 0.017。若使用ultrasonic_distance pulse_width * 34 / 1000虽数学等价但整数运算易溢出pulse_width 最大 30000μs → 30000×341,020,000 65535故采用浮点乘法更安全。3.3 协同控制主循环如何避免循迹与避障逻辑互相阻塞主循环不能写成while(1) { read_infrared(); read_ultrasonic(); control_motor(); }否则超声波采样被红外读取阻塞。正确结构是模块执行频率触发方式关键约束红外状态读取50HzSysTick 中断必须在 20ms 内完成全部 5 路读取超声波触发20HzTIM4 定时中断每次触发后禁用 50ms防余震电机 PWM 更新1kHzTIM2 更新中断占空比更新必须原子操作volatile uint8_t infrared_ready 0; volatile uint8_t ultrasonic_ready 0; void SysTick_Handler(void) { static uint8_t tick_count 0; if (tick_count 20) { // 20ms 1kHz SysTick infrared_ready 1; tick_count 0; } } void TIM4_IRQHandler(void) { static uint8_t us_tick 0; if (us_tick 50) { // 50ms 1kHz ultrasonic_trigger(); us_tick 0; ultrasonic_ready 1; } TIM_ClearITPendingBit(TIM4, TIM_IT_Update); } int main(void) { // 初始化省略... while(1) { if (infrared_ready) { uint8_t state get_track_state(); motor_pwm_diff pwm_diff_table[state]; infrared_ready 0; } if (ultrasonic_ready ultrasonic_distance 15) { motor_pwm_diff 0; // 强制差速为0直行刹车 set_motor_speed(0); // 停车 ultrasonic_ready 0; } // PWM 更新在 TIM2 更新中断中完成此处仅更新变量 } }注意set_motor_speed(0)不是简单关闭 PWM而是将 TIM2 的 CCR1–CCR4 寄存器清零并置位 L298N 的 ENA/ENB 引脚为低电平确保电机彻底断电。若仅设 CCR0电机仍可能因反电动势缓慢滑行。4. STM32F103 标准库 v3.50 下的三大典型故障排查表4.1 “Error: no STM32 target found!” 的真实原因与修复步骤该错误在 Keil 中高频出现但多数教程归因为 ST-Link 驱动问题。实际在本项目中90% 情况源于BOOT1 引脚电平错误STM32F103 默认从主闪存启动BOOT10, BOOT00若 BOOT1 被意外拉高如焊接短路、杜邦线误触芯片进入系统存储器启动模式此时 ST-Link 无法访问 Flash报错no target found解决方案断电状态下用万用表测 BOOT1PC15对地电压应为 0V若为 3.3V检查 PC15 是否与 VDD 短路或移除 BOOT1 上拉电阻最小系统板常默认焊 10kΩ 上拉。现象根本原因操作步骤ST-Link Utility 识别到设备但无法擦除SWD 接口时钟配置错误在 Keil 中 Project → Options → Debug → Settings → Trace → Core Clock 设为 72MHz下载后小车无反应启动文件未加载或 vector table 偏移错误检查 startup_stm32f10x_md.s 中__Vectors地址是否为 0x08000000Flash 起始地址红外读数始终为 0xFFGPIOA 时钟未使能在 RCC 初始化中添加RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE);4.2 五路循迹传感器响应迟钝ADC 采样 vs GPIO 轮询的实测对比在相同光照条件下LED 补光灯 500lux对同一段黑线进行 100 次采样统计从传感器压线到 PWM 差值更新的延迟方法平均延迟最大延迟抗干扰性强光下误触发率GPIO 浮空输入1.2μs3.5μs低阈值可软件动态调整ADC 采样62.5μs120μs中需定期校准参考电压外部比较器0.8μs1.0μs高但需额外芯片结论本项目无需高精度灰度识别GPIO 方案完全满足需求且节省 2 个 ADC 通道资源供未来扩展如电池电压监测。4.3 超声波测距跳变 5cmTIM3 输入捕获配置陷阱常见错误是将TIM_ICInitStructure.TIM_ICFilter设为非 0 值如 0xF。该参数用于滤除输入引脚上的高频噪声但 HC-SR04 回响信号边沿陡峭过高的滤波值会导致边沿识别延迟。实测表明TIM_ICFilter 0x0边沿识别延迟 0.1μs距离误差 ±0.02cmTIM_ICFilter 0x7延迟 1.2μs误差 ±0.2cmTIM_ICFilter 0xF延迟 3.5μs误差 ±0.6cm且在快速移动中小车频繁误判为障碍。务必在TIM_ICInit()前将TIM_ICInitStructure.TIM_ICFilter 0x0。5. 用 STM32CubeMX 快速生成标准库工程并迁移关键代码的实操技巧5.1 为什么 CubeMX 生成的 HAL 库工程不适合本项目HAL 库虽简化开发但在本场景下引入三重开销内存开销HAL_TIM_IC_Start_IT() 占用 RAM ≥1.2KB而 F103C8T6 仅 20KB RAM时序开销HAL 库函数调用深度达 5 层中断响应延迟增加 8–12μs兼容性风险HAL 库 v1.8.0 对 TIM3_CH2 输入捕获的支持存在 bugHAL_TIM_IC_Start_IT()未正确配置 AFIO_MAPR需手动修改stm32f103xb_hal_msp.c。因此CubeMX 仅用于生成引脚分配与时钟树再导出为标准库工程。5.2 CubeMX 配置四步法Keil 5.36 Standard Peripherals Library v3.50Pinout 视图PA0–PA4 设置为GPIO_InputPB0 设置为GPIO_OutputPB1 设置为TIM3_CH2自动勾选AFIOPA6–PA7 设置为TIM2_CH1/CH2左轮 PWMPB10–PB11 设置为TIM2_CH3/CH4右轮 PWM。Clock ConfigurationHSE 8MHzPLL Source HSEPLL MUL 9 → SYSCLK 72MHzAPB2 72MHzGPIOA/BAPB1 36MHzTIM2/TIM3。Project ManagerToolchain: ARM GCC不选 HAL选Standard Peripherals LibraryCode Generator: 勾选Generate peripheral initialization as a pair of .c/.h files。生成后迁移关键代码将main.c中MX_GPIO_Init()替换为前述GPIO_Init()代码删除MX_TIM3_IC_Init()改用TIM_TimeBaseInit()TIM_ICInit()在stm32f10x_it.c中将HAL_TIM_IC_MspInit()替换为NVIC_Init()配置 TIM3 中断优先级建议抢占优先级 1子优先级 0。5.2.1 如何验证 CubeMX 生成的时钟配置正确在main()开头添加调试代码uint32_t sysclk RCC_GetSYSCLKFreq(); // 应返回 72000000 uint32_t apb1 RCC_GetHCLKFreq() / RCC_GetPCLK1Divider(); // 应返回 36000000 if (sysclk ! 72000000 || apb1 ! 36000000) { // 进入死循环用 ST-Link 查看寄存器 RCC_CFGR while(1); }若数值不符说明 PLL 配置失败需检查RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9)是否被 CubeMX 错误覆盖。5.3 标准库 v3.50 芯片包安装失败的终极解法Keil 5.36 安装stm32f103芯片包时提示No compatible device support found本质是 Pack Installer 未识别到 F103 系列。解决步骤手动下载Keil.STM32F1xx_DFP.2.3.0.pack官方最新版解压后将ARM\PACK\Keil\STM32F1xx_DFP\2.3.0\Device\ST\STM32F103xB\下的startup_stm32f10x_md.s复制到工程CMSIS\Startup\arm\目录在 Keil 中 Project → Manage → Project Items → Files → Add Group → 添加CMSIS\Startup\arm\startup_stm32f10x_md.sOptions → Target → XRAM → Startup file 改为startup_stm32f10x_md.s。此方法绕过 Pack Installer直接注入启动文件100% 成功。本文还有配套的精品资源点击获取
返回列表