资讯详情

STM32F103双路超声波测距与瞬时速度解算

📅 2026/9/13 17:41:03 | 华诺云谱 👁 阅读
STM32F103双路超声波测距与瞬时速度解算
简介本资源是一套基于STM32F103ZET6核心板正点原子精英版的双路超声波测距与测速嵌入式项目面向嵌入式初学者及课程设计、毕业设计实践者解决多目标距离实时测量、运动速度估算及直线碰撞预警等典型传感器应用问题。压缩包共141个文件含36个.h头文件与34个.c源码文件涵盖TIM定时器、LCD显示、HC-SR04驱动、ADC采集及蜂鸣器报警等关键模块辅以.o目标文件、.axf可执行镜像、.hex烧录文件及Keil工程配置文件.uvprojx/.uvoptx整体大小为2.56MB。已有1213人学习下载项目代码结构完整、模块划分清晰包含串口调试输出、LCD实时数据显示、两路超声波同步触发与时间差计算逻辑并实现了基于相对运动趋势的碰撞可能性判断与声光告警功能可直接编译运行是理解STM32外设协同与传感器融合应用的优质实践范例。1. 两路HC-SR04STM32F103实现直线碰撞预警不是简单测距而是用时间差解算瞬时速度并触发物理碰撞判断你手头有一块STM32F103最小系统板、两颗HC-SR04超声波模块、一块带40pin接口的LCD屏常见为1602或12864目标不是“让LCD显示一个数字”而是构建一个能实时响应直线运动物体的距离-速度双参监测系统当小车沿导轨匀速驶来系统不仅每100ms刷新一次当前距离如“23.7cm”更要基于连续两次测距的时间戳与位移差精确计算出瞬时速度如“-18.2cm/s”并在速度突变为负且距离跌破阈值如5cm时判定为“即将碰撞”同步在LCD高亮提示、串口输出带时间戳的结构化日志。这要求你绕过HC-SR04单次测距的固有延迟约60ms用定时器捕获两路ECHO脉宽高精度时间戳再通过滑动窗口滤波抑制噪声——很多教程只教“接线→显示→报警”但真实工业场景中没有速度维度的测距系统无法区分缓慢靠近与高速撞击也就谈不上有效碰撞检测。本文面向已掌握GPIO/USART基础、正调试STM32F103裸机或标准外设库v3.5.0的工程师全程不依赖HAL库所有代码可直接在Keil MDK-ARM v5.37下编译运行。2. 用TIM2TIM3双定时器捕获两路HC-SR04回波为什么必须用输入捕获而非延时测距HC-SR04的测距原理是Trig引脚接收10μs高电平后模块发出8个40kHz方波ECHO引脚随即输出与距离成正比的高电平脉宽1cm ≈ 58.8μs。若用软件延时如delay_us(10)配合GPIO_ReadInputDataBit()轮询ECHO会因中断响应抖动、循环开销导致脉宽测量误差达±100μs以上换算成距离误差超1.7cm——这对碰撞检测是致命的。标准外设库v3.5.0中TIM2和TIM3均支持4通道输入捕获且其时基可配置为1MHz即1μs分辨率这才是工业级测距的起点。2.1 硬件连接与引脚复用规划HC-SR04模块需独立供电VCC接5VGND共地避免与MCU共用LDO导致ECHO信号毛刺。两路模块的ECHO引脚必须接入具备输入捕获功能的GPIO第一路HC-SR04 ECHO → PA0TIM2_CH1第二路HC-SR04 ECHO → PA1TIM2_CH2提示PA0/PA1在STM32F103C8T6等主流型号上默认复用为TIM2通道无需额外跳线若使用其他核心板如PA6/PA7需查《STM32F103xx参考手册》第9章确认AFIO重映射配置。Trig引脚由GPIO推挽输出控制推荐使用PB0/PB1非复用引脚避免干扰定时器第一路HC-SR04 Trig → PB0第二路HC-SR04 Trig → PB1LCD屏采用40pin并行接口典型为ST7920驱动的12864数据线接GPIOA低8位PA0~PA7RS/RW/EN控制线接GPIOB高位PB8~PB10。此布局确保测距定时器与LCD总线无引脚冲突。2.2 TIM2输入捕获初始化双通道同步启动与边沿切换关键在于捕获ECHO脉宽的起始上升沿与结束下降沿需在单次触发后完成两次捕获。以下为TIM2初始化核心代码基于标准外设库v3.5.0void TIM2_Capture_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 使能GPIOA和AFIO时钟 // PA0/PA1配置为浮空输入ECHO信号由模块驱动 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // TIM2时基1MHz计数频率1μs分辨率自动重装载值6553516位 TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 71; // APB172MHz → 72MHz/(711)1MHz TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 通道1PA0捕获上升沿用于记录ECHO开始时间 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x03; // 滤波器采样4次抑制高频噪声 TIM_ICInit(TIM2, TIM_ICInitStructure); // 通道2PA1捕获下降沿用于记录ECHO结束时间 TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Falling; TIM_ICInit(TIM2, TIM_ICInitStructure); // 使能捕获中断 TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); // 配置NVIC NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); // 启动TIM2 }参数说明与踩坑点TIM_ICFilter 0x03设置输入滤波器为4个采样周期可滤除250kHz干扰HC-SR04工作频段40kHz完全兼容若设为0则易受电源噪声误触发。TIM_ICPolarity分设为Rising/Falling这是实现单脉宽测量的核心——先捕获上升沿存入CCR1再捕获下降沿存入CCR2两者相减即得脉宽计数值。TIM_IT_CC1 | TIM_IT_CC2必须同时使能两个通道中断否则第二次下降沿捕获无法触发中断服务函数。2.3 输入捕获中断服务函数用静态变量保存时间戳并计算距离在stm32f10x_it.c中编写中断处理逻辑关键在于用静态变量暂存前一次捕获值避免全局变量被多任务篡改// 全局变量声明在.c文件顶部 volatile uint16_t g_ulEchoStart1 0, g_ulEchoEnd1 0; // 第一路时间戳 volatile uint16_t g_ulEchoStart2 0, g_ulEchoEnd2 0; // 第二路时间戳 volatile uint8_t g_ucCapFinish1 0, g_ucCapFinish2 0; // 捕获完成标志 // TIM2中断服务函数 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { // 通道1上升沿触发记录起始时间 g_ulEchoStart1 TIM_GetCapture1(TIM2); TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } if (TIM_GetITStatus(TIM2, TIM_IT_CC2) ! RESET) { // 通道2下降沿触发记录结束时间 g_ulEchoEnd1 TIM_GetCapture2(TIM2); TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); g_ucCapFinish1 1; // 标记第一路捕获完成 } // 注意此处未处理第二路PA1的捕获因本例仅用PA0/PA1作为单路双沿捕获 // 若需两路独立测距应使用TIM2TIM3双定时器TIM3捕获PA1/PA2 }注意上述代码仅实现单路测距PA0。要支持两路独立HC-SR04必须启用TIM3捕获第二路ECHO如PA2→TIM3_CH1此时需复制TIM2初始化逻辑并修改时钟使能RCC_APB1Periph_TIM3、中断向量TIM3_IRQn及GPIO配置。两路定时器需严格同步启动建议在主循环中用TIM_Cmd(TIM2,ENABLE); TIM_Cmd(TIM3,ENABLE);并行开启。3. 基于滑动窗口的速度解算与碰撞逻辑从原始脉宽到物理量的三步转换单纯获得两路距离值D1, D2并不足以判断碰撞——若物体匀速运动D1与D2的差值恒定只有当D2 D1且差值在Δt内急剧增大即速度负向突增才表明物体正在加速逼近。速度计算必须绑定时间戳而非简单用distance_diff / fixed_interval因为HC-SR04单次测量耗时约60ms两次触发间隔若固定为100ms则实际时间差可能因环境温度、电压波动而偏移±5ms导致速度误差超10%。3.1 距离-速度转换的数学模型设第n次测量距离为Dn对应绝对时间戳为Tn单位ms则瞬时速度Vn计算公式为Vn (Dn - Dn-1) / (Tn - Tn-1) [单位cm/ms → 需×1000转为cm/s]其中Dn由脉宽Δt单位μs换算Dn Δt / 58.8HC-SR04声速340m/s往返时间对应单程距离。为抑制随机噪声对最近4次Dn值做滑动平均Dn_smooth (Dn Dn-1 Dn-2 Dn-3) / 43.2 主循环中的速度解算与碰撞判定逻辑在main.c主循环中每200ms执行一次测距-计算-显示流程避开HC-SR04最小触发间隔60ms#define MEASURE_INTERVAL 200 // 测量周期200ms uint32_t ulLastMeasureTime 0; float afDistance[4] {0}; // 滑动窗口距离数组 float afSpeed[4] {0}; // 滑动窗口速度数组 uint8_t ucWinIndex 0; int main(void) { SystemInit(); USART1_Init(); // 初始化串口1115200bps LCD_Init(); // 初始化12864 LCD TIM2_Capture_Init(); // 初始化第一路捕获 TIM3_Capture_Init(); // 初始化第二路捕获需自行实现类似TIM2 while(1) { if (GetTick() - ulLastMeasureTime MEASURE_INTERVAL) { ulLastMeasureTime GetTick(); // 步骤1触发两路HC-SR04 GPIO_ResetBits(GPIOB, GPIO_Pin_0); // PB0拉低 Delay_us(2); GPIO_SetBits(GPIOB, GPIO_Pin_0); // PB0拉高10us Delay_us(10); GPIO_ResetBits(GPIOB, GPIO_Pin_0); GPIO_ResetBits(GPIOB, GPIO_Pin_1); // PB1同理触发第二路 Delay_us(2); GPIO_SetBits(GPIOB, GPIO_Pin_1); Delay_us(10); GPIO_ResetBits(GPIOB, GPIO_Pin_1); // 步骤2等待捕获完成超时保护 uint16_t usTimeout 0; while((!g_ucCapFinish1 || !g_ucCapFinish2) usTimeout 30000) { Delay_us(10); // 10us轮询 } // 步骤3读取脉宽并计算距离以第一路为例 uint16_t usPulse1 g_ulEchoEnd1 - g_ulEchoStart1; float fDist1 (float)usPulse1 / 58.8f; // 单位cm afDistance[ucWinIndex] fDist1; // 步骤4滑动窗口平均仅当有足够历史数据 float fDistAvg 0; for(uint8_t i0; i4; i) { fDistAvg afDistance[(ucWinIndexi)%4]; } fDistAvg / 4.0f; // 步骤5计算速度需至少2次有效距离 static float fPrevDist 0; static uint32_t ulPrevTime 0; if(ulPrevTime ! 0) { float fDeltaDist fDistAvg - fPrevDist; uint32_t ulDeltaTime GetTick() - ulPrevTime; if(ulDeltaTime 0) { float fSpeed (fDeltaDist / ulDeltaTime) * 1000.0f; // cm/s afSpeed[ucWinIndex] fSpeed; } } fPrevDist fDistAvg; ulPrevTime GetTick(); // 步骤6碰撞判定距离5cm且速度-15cm/s if(fDistAvg 5.0f afSpeed[ucWinIndex] -15.0f) { LCD_DisplayString(COLLISION!); // LCD高亮显示 printf(ALERT: Distance%.1fcm, Speed%.1fcm/s, Time%dms\r\n, fDistAvg, afSpeed[ucWinIndex], ulPrevTime); } else { char acBuf[32]; sprintf(acBuf, D1:%.1fcm V:%.1f, fDistAvg, afSpeed[ucWinIndex]); LCD_DisplayString(acBuf); printf(D1%.1fcm, V%.1fcm/s\r\n, fDistAvg, afSpeed[ucWinIndex]); } ucWinIndex (ucWinIndex 1) % 4; // 移动窗口索引 g_ucCapFinish1 g_ucCapFinish2 0; // 清除捕获标志 } } }关键参数说明MEASURE_INTERVAL 200远大于HC-SR04单次测量最大耗时约60ms确保两路触发不重叠。usPulse1 / 58.8f58.8是HC-SR04标称换算系数340m/s ÷ 2 ÷ 100cm/m × 10^6μs/s实测中可在25℃室温下微调至58.5~59.0提升精度。fSpeed (fDeltaDist / ulDeltaTime) * 1000.0f将cm/ms转换为cm/s避免浮点运算溢出。碰撞阈值5.0f和-15.0f需根据实际机械结构校准——若导轨末端有缓冲胶垫可放宽至8cm若检测高速小球则需收紧至3cm并提高速度阈值至-30cm/s。3.3 串口日志格式设计带时间戳的结构化输出便于后期分析为满足工业现场调试需求串口输出必须包含可解析的时间戳和物理量。printf函数需重定向至USART1见syscalls.c中_write实现日志格式如下ALERT: Distance3.2cm, Speed-28.7cm/s, Time12450ms D123.7cm, V-18.2cm/s提示Time12450ms是自系统启动以来的毫秒计数由SysTick提供非绝对时间但足以定位碰撞发生时刻在测试序列中的位置。若需UTC时间需外接RTC模块。4. LCD屏显示优化在12864上实现距离/速度双行动态刷新与中文告警40pin接口的12864 LCDST7920驱动支持8×4汉字显示但标准外设库无现成中文字符集。不推荐移植完整GB2312字库占用16KB Flash而是针对本项目需求仅定义“碰撞”“距离”“速度”4个汉字的16×16点阵每个汉字32字节总内存占用128字节。4.1 中文字符点阵定义与写入CGROM在lcd_font.c中定义汉字编码按GB2312区位码// “碰”字区位码 37-27 → 0x251B需加0xA0A0转为机内码 const unsigned char g_ucFont_Peng[] { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; // 实际点阵需用取模软件生成此处省略具体数据在LCD_Init()中调用LCD_WriteCmd(0x40)进入CGRAM地址逐字节写入点阵数据。4.2 双行动态刷新策略避免闪烁与残影12864的显示刷新需全屏重绘若每次更新都清屏再写入会导致视觉闪烁。最优策略是仅刷新变化区域第一行地址0x80固定显示“距离XX.Xcm 速度XX.Xcm/s”第二行地址0xC0根据状态切换显示“正常”或“碰撞”中文void LCD_UpdateDisplay(float fDistance, float fSpeed, uint8_t ucAlarm) { // 第一行距离与速度固定位置仅更新数字部分 LCD_SetCursor(0,0); // 第0行第0列 LCD_PrintStr(距离); LCD_PrintFloat(fDistance, 1); // 自定义浮点打印函数保留1位小数 LCD_PrintStr(cm 速度); LCD_PrintFloat(fSpeed, 1); LCD_PrintStr(cm/s); // 第二行状态提示 LCD_SetCursor(1,0); // 第1行第0列 if(ucAlarm) { LCD_WriteData(0x00); // 写入CGRAM中“碰”字首字节 LCD_WriteData(0x01); // “撞”字 LCD_WriteData(0x02); // “”符号ASCII 0x21 } else { LCD_PrintStr(正常); } }LCD引脚关键配置表40pin接口LCD引脚STM32F103引脚功能说明DB0~DB7PA0~PA7数据总线8位模式RSPB8寄存器选择0指令1数据RWPB9读写选择本项目固定写入接GNDENPB10使能信号高脉冲触发PSBGND并行模式接GNDRSTVCC复位常高提示若LCD显示乱码优先检查RW引脚是否误接为PB9输出而非GND若第二行不显示确认LCD_SetCursor(1,0)发送的地址命令为0xC0非0x80。5. 直线碰撞检测的工程化验证用游标卡尺校准与示波器抓波形理论模型必须经实测验证。不要依赖HC-SR04标称精度±3mm而要用游标卡尺示波器建立可信度闭环。5.1 距离校准用游标卡尺测量实际距离与LCD显示值的偏差将HC-SR04固定于桌面前方放置可移动挡板如亚克力板用游标卡尺测量挡板前沿到HC-SR04发射头的距离D_real单位mm记录LCD显示距离D_display单位cm计算绝对误差|D_display×10 - D_real|在10cm、30cm、50cm、100cm四点测量绘制误差曲线若误差5mm调整换算系数新系数 58.8 × D_real / (D_display×10)。5.2 速度验证用DSO-X 2002A示波器抓取ECHO脉宽与Trig时序将示波器通道1接Trig引脚PB0通道2接ECHO引脚PA0触发方式设为通道1上升沿测量Trig高电平宽度确认为10±1μs测量ECHO高电平宽度与LCD显示距离对比例如显示20.0cm时脉宽应≈1176μs20×58.8若脉宽稳定但距离跳变检查TIM_ICFilter是否过小0x02导致噪声捕获。5.3 碰撞检测鲁棒性测试模拟不同逼近场景场景操作预期结果排查要点缓慢靠近手持挡板以1cm/s匀速靠近LCD距离递减速度显示≈-1cm/s不触发告警检查速度阈值是否设为-15cm/s过高突然加速挡板静止→快速推向传感器距离骤降速度瞬时-25cm/s立即显示“碰撞”检查滑动窗口是否未及时更新导致fPrevDist滞后多路径反射在传感器侧方放置斜面反射板LCD距离跳变10cm启用TIM_ICFilter 0x07增强滤波或加装遮光罩提示若串口日志中出现ALERT但LCD无反应检查LCD_SetCursor(1,0)后是否遗漏LCD_WriteData()调用若所有字符显示为方块确认CGRAM写入地址是否为0x40而非0x00。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。