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

资讯详情

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

STM32F103 HC-SR04超声波测距OLED显示实战详解

STM32F103 HC-SR04超声波测距OLED显示实战详解 简介一套基于STM32F103的超声波测距与CRT显示完整工程面向嵌入式学习者、电子竞赛备赛者以及需要快速上手HC-SR04测距开发的工程师。工程演示了从GPIO控制、定时器捕获超声波回波、计算距离再到通过SPI/I2C连接驱动电路控制CRT显示的全流程代码结构清晰配置齐全。压缩包共141个文件约3.5MB包含C源码与H头文件如stm32f10x_tim.c、stm32f10x_usart.c等、Keil工程配置uvprojx/uvoptx、编译产物axf/hex/map/lst以及keilkilll.bat辅助脚本可直接打开、编译并烧录到开发板验证。已有209人学习下载。通过学习该工程可掌握STM32定时器测量时间间隔、超声波传感器触发与回波检测、外设初始化、CRT显示电压转换与扫描控制等关键知识点整体方案适合课程设计、毕业设计或低成本距离感知产品原型参考复用。 拿到“STM32F103超声波测距并显示.zip”这个压缩包大概率是两种情况一种是课程设计、毕业设计或者电赛的小任务需要快速把超声波测距这套东西跑起来另一种是看到网盘资源先存了等真要做的时候才打开。我之前帮人调试过不少同名的工程包里面的代码思路大同小异但真正能让初学者少走弯路的反而不是代码本身而是背后那套“从传感器时序到单片机计算再到屏幕显示”的完整逻辑链。所以这篇东西不打算把某个压缩包里的代码逐行念一遍而是按这类工程最常见的结构把HC-SR04怎么测距、STM32F103怎么把它换算成厘米、OLED怎么把结果显示出来以及你下载的工程上电后为什么不工作这四件事讲清楚。适合刚接触STM32的学生也适合想快速把超声波模块用到项目里的开发者。1. 工程包里通常有什么先搞清楚再动手1.1 从文件名能读出的隐藏信息压缩包名字“STM32F103超声波测距并显示”已经划定了边界主控是STM32F103外设是超声波模块输出结果是某种显示终端。打开包之后一般会看到这些内容一个Keil工程文件夹里面是.uvprojx工程文件双击就能打开Hardware或BSP目录放着hc_sr04.c、oled.c、delay.c这类底层驱动App或User目录放着main.c、stm32f10x_it.c中断文件一个可能写着“README.txt”但实际上只有两行字的说明文件有些工程还附带Project目录下的.hex文件方便你直接用串口或烧录器烧进去这些文件和STM32的三种开发方式对应标准外设库、HAL库、寄存器操作。如果代码里看到GPIO_SetBits、TIM_Cmd那是标准库看到HAL_GPIO_WritePin那是HAL库看到直接操作GPIOA-ODR | 11那是寄存器。三种写法运行效果没区别但调试思路不同。比较老的课程设计代码用标准库居多因为很多教学板带的资料就是ST在2011年左右发布的V3.5标准库。1.2 方案选型为什么固定是这三样STM32F103系列是一颗ARM Cortex-M3内核的MCU主频72MHzFlash从64KB到512KB不等价格便宜、资料多、开发板遍地都是。做主控的最大优势是“不用为资源发愁”哪怕是最小的C8T6也有两个定时器、三个USART、两个I2C、两个SPI跑超声波测距加OLED显示绰绰有余。超声波模块选HC-SR04则几乎是这个领域的事实标准。它自带发射和接收电路单片机只需要给一个触发脉冲然后测量回响电平的持续时间即可不需要自己处理40kHz的脉冲调制。显示终端常见两个选择OLED或LCD1602。工程里的显示代码大概率是0.96寸白色OLEDI2C控制的SSD1306四根线VCC、GND、SCL、SDA就能连比8根数据线的LCD1602省事得多所以大多数新写的课程设计都用OLED。2. 超声波测距原理那句公式背后的完整时序2.1 飞行时间法到底怎么“飞”的HC-SR04测距的核心叫“飞行时间法”。你给Trig引脚一个大于10微秒的高电平模块内部的超声波发射头就会发出8个周期的40kHz方波脉冲同时Echo引脚被拉高。当声波碰到前方的障碍物反射回来被接收头捕捉后Echo引脚被拉低。也就是说Echo高电平持续的时间等于声波从发射到返回所经过的完整时间。单片机要做的就一件事测量Echo高电平持续了多少微秒。原理看着简单但很多人栽在时序细节上。注意两个时间点Trig的触发脉冲宽度必须大于10微秒太短模块不响应Echo高电平最大持续约38毫秒如果前方没有障碍物Echo会一直保持高电平直到超时拉低。所以程序里要加超时判断否则一旦前方没东西或者距离超过4米程序就卡死在等待Echo变低的循环里按空气中的声速约340米/秒算38毫秒对应约6.5米的往返飞行距离但HC-SR04的有效量程标称是2厘米到400厘米超出4米测出来的数据就不可靠了。2.2 从微秒到厘米的换算少踩一个精度坑假设测到Echo高电平持续时间为t微秒声速为v毫米/微秒声波走了往返两趟所以单程距离是距离 v × t ÷ 2常温20度时声速约为343米/秒换算成毫米/微秒就是0.343。这个数值乘以t再除以2就得到毫米单位。但很多工程代码里会用更偷懒的近似公式距离厘米 t ÷ 58这个58是怎么来的因为1 ÷ (0.343 ÷ 2 × 10)约等于58.3。也就是声波往返1厘米距离需要约58微秒所以直接用微秒数除以58就能得到厘米数。实测下来在室温环境下t÷58和完整公式算出来的结果只差几毫米完全够用。但要注意声速并不是恒定不变的温度每升高1度声速大约增加0.6米/秒。如果做高精度测量官方公式是v 331.4 0.607 × T比如冬天的室温10度声速337.5米/秒夏天室温30度声速349.6米/秒前后差了12米/秒对应测量误差大约3.5%。如果要精测就得加个DS18B20温度传感器做补偿如果只是课程设计展示用固定声速也够应付了。3. STM32F103端的具体实现思路3.1 触发脉冲和回响检测的GPIO操作接线方案很固定HC-SR04的Trig接一个STM32的普通输出引脚Echo接一个普通输入引脚。如果用到标准库初始化代码长这样GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // Trig: PA1 推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // Echo: PA2 浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure);Cortex-M3内核复位后GPIO默认是浮空输入状态所以初始化顺序没有硬性要求但最好先把Trig设为输出防止上电瞬间误触发射波。Echo引脚用浮空输入是常规做法因为HC-SR04的Echo是推挽输出不需要拉电阻协助。3.2 测量Echo高电平宽度别用傻等的方式测量高电平宽度有四种常见做法从简单粗暴到优雅高效分别是延时翻转法用SysTick做微秒延时不断翻转电平精度差且占CPU查询计数法Echo变高时开启定时器变低时读出定时器值输入捕获法用定时器通道的捕获功能上升沿自动记录起始值下降沿自动记录结束值外部中断定时器法Echo接外部中断引脚配合定时器课程设计代码里最常用的是第二种逻辑直观也最容易讲解。用TIM2做基本计时器内部72MHz时钟分频到1MHz计数一次正好1微秒。void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InitStructure.TIM_Period 0xFFFF; // 最大计数65535us够用 TIM_InitStructure.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); }测距函数的核心逻辑float HCSR04_GetDistance(void) { uint16_t time_us 0; float distance_cm 0; // 1. 触发至少10us高电平 GPIO_SetBits(GPIOA, GPIO_Pin_1); Delay_us(15); GPIO_ResetBits(GPIOA, GPIO_Pin_1); // 2. 等待Echo变高带超时保护 uint32_t timeout 0; while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) 0) { if (timeout 10000) return -1; // 大约10ms } // 3. 开启定时器 TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); // 4. 等待Echo变低同样带超时保护 timeout 0; while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) 1) { if (timeout 50000) // 大约50ms { TIM_Cmd(TIM2, DISABLE); return -2; // 超时前方可能无障碍物 } } // 5. 停止定时器并读取计数值 TIM_Cmd(TIM2, DISABLE); time_us TIM_GetCounter(TIM2); // 6. 换算成厘米 distance_cm time_us / 58.0; return distance_cm; }这里有两个细节值得说。第一每轮测距间隔至少要留60毫秒以上因为声波最长往返时间约38毫秒要等上一次回波完全消失再触发否则容易把上一次的余波当成这一次的回波导致距离跳变。第二返回-1或-2这类负值表示异常调用方要做判断不要直接把负数送去显示。3.3 OLED显示驱动的接入要点OLED屏幕大多数是SSD1306控制器用I2C接口。STM32的硬件I2C模块一直被诟病不稳定所以很多工程直接软件模拟I2C时序。不管哪种方式初始化SSD1306才是最关键的。我见过不少人卡在“OLED白屏”其实不是驱动代码问题而是初始化顺序不对。SSD1306上电后要先发0xAE关闭显示然后逐条发送初始化命令序列对比度、显示偏移、扫描方向、电荷泵等最后发0xAF开启显示。显示整型数据时要先把数字转成字符串用C库函数sprintf最省事char buf[20]; sprintf(buf, Dist: %.1f cm, distance); OLED_ShowString(0, 0, buf);注意STM32标准库工程默认微库MicroLIB是关的如果用sprintf的浮点格式编译后Flash占用会飙升。开了微库之后浮点格式化问题就解决了代价是printf的缓冲能力变小但对于显示距离这种场景完全没影响。4. 实操流程从编译烧录到正常显示4.1 接线速查表无论你用哪个开发板这套项目的接线都出不了这个范围。我结合最常见的C8T6最小系统板和0.96寸OLED整理一个速查表HC-SR04引脚接STM32引脚说明VCC5VHC-SR04供电支持5V板载稳压后工作正常GNDGND和STM32共地这一步千万不能省TrigPA1触发信号输入普通GPIO输出EchoPA2回响信号输出普通GPIO输入OLED引脚接STM32引脚说明VCC3.3VSSD1306逻辑电压3.3V接5V有烧毁风险GNDGND共地SCLPB6I2C时钟STM32F103的I2C1默认脚SDAPB7I2C数据有一个安全提醒值得单独讲HC-SR04的Echo引脚标称输出是5V电平如果直接接到STM32的PA2对于3.3V供电的STM32来说这个5V高电平虽然通常不会立即烧坏引脚因为很多STM32引脚是5V容忍的但从长期可靠性角度还是建议用两个电阻分压把Echo信号降到3.3V左右再进单片机。分压电阻可以选1K和2K简单粗暴还能用。4.2 核心代码讲解怎样让测距和显示协同工作主循环的逻辑不复杂但我见过的问题往往出在“延时”和“显示刷新”的位置上。建议的结构是每100毫秒测一次距离测完立即刷新OLED然后进入下一次循环。这样既保证了OLED刷新不闪烁也给超声波留足了发射间隔。int main(void) { float dist 0; SystemInit(); Delay_init(72); GPIO_Config(); TIM2_Config(); OLED_Init(); OLED_Clear(); while (1) { dist HCSR04_GetDistance(); if (dist 0 dist 400) { char buf[20]; sprintf(buf, Dist: %.1f cm, dist); OLED_ShowString(0, 0, buf); } else { OLED_ShowString(0, 0, No object!); } Delay_ms(100); } }为什么要判断dist 0因为前面的函数在超时或无回波时会返回负值。如果直接显示OLED上就会蹦出一个莫名其妙的负距离看起来像是程序崩了实际上只是没测到目标。这种防御性判断在嵌入式代码里很常见属于“写代码时多花两秒调试时省两小时”的典型例子。4.3 烧录验证为什么你烧进去之后没有反应编译下载这一步最容易翻车的是芯片选型。同样的“STM32F103”工程C8T6和ZET6的Flash容量不同CubeMX生成的工程如果芯片型号选错在Keil里编译没问题但烧录后程序跑不起来或者拷不进。检查路径是魔术棒Options for Target→ Device选项卡→ 确认芯片型号和你的板子一致。另外Boot0引脚如果被拉高芯片会进入ISP下载模式而不是运行Flash里的程序导致你明明烧录成功了板子却“没反应”。这时候把Boot0接GND再复位一次就好。5. 常见问题与排查技巧实录5.1 测距结果乱跳、可能的原因先问三个问题模块前方是否正对着被测物体物体表面是否平整且接近垂直周围有没有强风力或风扇吹动超声波测距最怕的情况是斜着照射到光滑表面这种情况下反射波会弹到你根本收不到的方向Echo等待超时后返回负值程序就会显示“No object”而不是给出一个稳定距离。这在实际操作里很常见。如果距离值在静止状态下跳变超过正负2厘米优先检查供电。HC-SR04工作峰值电流约15毫安看似不大但如果供电线过长压降累积起来会导致模块工作不稳定。我实测过用USB供电时的测量结果明显比电池供电时稳定就是这个原因。数值乱跳的第二个常见原因是信号滤波没做。超声波回波本身受环境干扰较大最简单的处理方法是连续测5次去掉最大值和最小值剩下3次取平均。这段代码也就十行左右但效果立竿见影。工程包里的原始代码一般没有这个滤波逻辑需要自己补上。5.2 OLED白屏和显示错乱的排查顺序第一步查接线用万用表量一下OLED的VCC引脚对GND电压是不是3.3VSCL和SDA信号线有没有接反。第二步查I2C地址SSD1306常见的7位地址是0x3C或0x3D对应写地址0x78或0x7A。如果驱动代码里写死了一个地址而你的模块实际是另一个地址初始化函数会一直失败。改进办法是写个扫描函数把I2C总线上所有能响应的地址打印出来这一步能直接锁定问题。第三步查软件I2C的引脚定义确认scl和sda宏定义和实际接线一致。头文件改动后要重新编译别只改接线不重新烧录。5.3 编译报错和标准库版本问题这个项目网上流传的源码多数基于标准库V3.5而不少同学电脑上装的是HAL库版本的例程。如果你拿到的是标准库工程编译器却提示找不到stm32f10x.h多半是头文件包含路径没配置好。在Keil的魔术棒里找到C/C选项卡点击Include Paths把工程内的Libraries/CMSIS、Libraries/StdPeriph_Driver/inc这几个目录加进去。添加完路径后还需要定义USE_STDPERIPH_DRIVER和STM32F10X_MD这两个宏前者用于启用标准库驱动后者告诉编译器芯片用的是中等容量型号。如果是ZET6直接用STM32F10X_HD。这里选错最典型的现象就是代码编译到一半报出一堆关于GPIOA未定义的错误。还有一种情况是启动文件选错。如果工程里没有startup_stm32f10x_md.s这个文件编译会直接提示Undefined symbol SystemInit之类的问题。到Keil安装目录下的ARM/Startup里把对应型号的启动文件复制进工程添加到工程树中即可。6. 一点个人建议和扩展思路这类“STM32F103超声波测距并显示”的工程跑通只是起点。我最后额外分享两个实际测试中的体会第一关于HC-SR04的量程问题声称400厘米但实际测到250厘米以上时回波已经比较微弱容易受到干扰如果项目对远距离有要求不妨换成工业级的US-100或激光测距模块第二如果显示器容易反光OLED的对比度在强光下确实不如LCD1602但LCD1602的接线复杂度和功耗又高一些选型时要有取舍。另一个值得动手的方向是加入温度补偿。把DS18B20接在STM32的PA1或PB1上每秒钟读一次温度把声速代入公式计算。代码改动不大但精度提升明显答辩时老师问起也能讲出“考虑了环境温度对声速的影响”这个加分点。如果想做到更稳的显示效果可以尝试把OLED刷新频率降到20Hz配合滤波算法动态测量移动物体时会明显感觉数字更平滑。无论如何先把手上这个压缩包跑起来把接线、时序、换算这几个环节的坑都踩一遍比看十篇教程都有用。后面遇到问题可以顺着这篇的思路先把现象、输入输出和异常代码打印出来再逐个环节排查。超声波测距本身不难难的是把每个细节都考虑到等你在实际项目里把这些细节串起来就会觉得这个老套的“单片机入门三件套”其实帮了你一个大忙。本文还有配套的精品资源点击获取
返回列表