资讯详情

STC15单片机四路超声波测距与OLED显示实现

📅 2026/9/16 14:20:25 | 华诺云谱 👁 阅读
STC15单片机四路超声波测距与OLED显示实现
简介基于STC15单片机8051内核的超声波测距OLED显示设计资料包含四路超声波测距方案与完整硬件原理图面向单片机初学者、嵌入式开发人员以及测距类课程设计具有较强的参考与移植价值。压缩包共12个文件覆盖C语言源文件、头文件、Keil工程文件、原理图文件与hex可烧录文件整体大小仅70KB结构清晰便于直接导入Keil编译与验证。已有327人学习/下载。资源提供了从STC15初始化、超声波发射与回波捕获、基于声速与时间差的距离计算到OLED显示驱动的完整例程配合原理图可系统理解I/O控制、定时器计时、I2C/SPI通信、中断处理等知识点。四路设计支持同时测量多个方向适合扩展至机器人避障、区域监测等场景同时利用STC15的IAP在线编程特性可大幅简化固件调试流程对掌握嵌入式软硬件协同开发很有帮助。1. 超声波测距从原理图到固件难的不是探头而是定时器超声波测距从原理图到能跑通的固件门槛不在 HC-SR04 的那根 Trig 引脚上而在 STC15 的定时器怎么不失真地记录回波脉宽。这个项目把 STC15 单片机、四路 HC-SR04 和一个 I2C 接口的 OLED 屏组合在一起用 8051 内核里最朴素的定时器资源解决测距计时最后在屏幕上实时刷新厘米级距离。它与 STM32 加 HAL 库那类玩法完全不同没有现成的固件库可调中断和定时器全部靠寄存器配置反而更容易把测距时序抠清楚。如果你之前在 STM32 上点亮过 OLED再回头用 STC15 会明显感觉到寄存器操作的直接感。这个设计适合正在做智能小车避障、低成本多方向探测的嵌入式开发者也适合课程设计里需要把单片机、传感器和显示技术串成完整闭环的人。2. STC15与HC-SR04的时序配合触发信号与回波捕获的硬件基础2.1 HC-SR04的引脚特性与STC15的IO差异HC-SR04 一共四个引脚VCC、Trig、Echo、GND。单片机给 Trig 一个大于 10us 的高电平模块内部会发出 8 个 40kHz 的超声波脉冲然后 Echo 引脚输出一段高电平这段高电平的宽度正好等于声波从发射到反射回来的总时间。所以整个测距系统要解决的核心问题只有一个精确测量 Echo 高电平持续的时长。STC15 的 IO 默认工作在准双向口模式读外部引脚电平之前要先向端口锁存器写 1否则可能读到内部拉低后的错误电平。这一点在接 Echo 引脚时尤其容易踩坑如果只是把sbit ECHO P2^1;定义好就直接读而没有先ECHO 1;回波信号来了也可能一直读到 0程序会死死卡在等待回波的 while 循环里。原工程的原理图里 Echo 引脚直接连到 STC15 的普通 IO软件上只要在初始化时统一把 Echo 对应的引脚置高即可。2.2 原理图里容易被忽略的三个电路细节第一个是电源滤波。HC-SR04 发射瞬间电流变化很快如果模块和单片机共用一个稳压输出超声波脉冲会引起 VCC 上几十毫伏的毛刺严重时会让 STC15 误复位。原理图上最好在模块供电端并联 100uF 电解电容和 0.1uF 瓷片电容两者各司其职电解电容扛低频跌落瓷片电容吸收高频干扰。第二个是 Echo 引脚的保护。项目使用的 HC-SR04 模块 Echo 输出是 5V 逻辑电平而 STC15 系列既有 5V 型号也有 3.3V 型号。如果单片机供电是 5VEcho 可以直连如果是 3.3V 版本推荐加一级 1k 电阻串联或者用两个电阻分压到 3.3V。原理图上虽然没有标注这个细节但按我调类似模块的经验串一个 1k 电阻对 IO 的静电保护和误接反压都有实际意义。第三个是 OLED 屏 I2C 总线的上拉电阻。SSD1306 控制器的 OLED 小屏SCL 和 SDA 两根线通常需要外部上拉到 VCC阻值 4.7k 比较常见。有些现成模块已经焊好上拉电阻直接从 STC15 的 IO 接过去就能用有些模块没有这时如果总线时序不对屏幕初始化之后没有任何反应先在原理图上找一找有没有这两颗电阻。2.3 四路传感器的IO分配参考四路测距意味着四个 HC-SR04 要同时挂在 STC15 上。Trig 引脚是输出Echo 引脚是输入分配时优先保证 Echo 占用的引脚支持外部中断为后面做并行捕获留余地。下面这张表是我参考 STC15 系列常见封装整理的一种分配方式对应原理图里的 P2 口通道Trig引脚Echo引脚外部中断说明CH1P2.0P3.2INT0优先作为中断通道CH2P2.2P3.3INT1优先作为中断通道CH3P2.4P2.5无轮询读取CH4P2.6P2.7无轮询读取如果封装上没有这么多 P2 引脚也可以把全部 Trig 并联到同一个引脚用同一根线同时触发四个模块然后分别读取四路 Echo这是后面章节要讲的并发思路。原理图里没有固定这个分配实际布线时只要保证 Echo 不走太长飞线避免和 Trig 平行走线产生串扰就行。2.4 定时器与GPIO的初始化代码STC15 的定时器初始化参数直接影响后续测距的量程和分辨率。这里给出基于 Keil5 工程风格的初始化代码定时器 0 工作在 16 位模式选择 12T 分频而不是 1T 分频原因在下一章详细说明#include STC15F2K60S2.h #include intrins.h sbit TRIG1 P2^0; sbit ECHO1 P3^2; void GPIO_Init(void) { TRIG1 0; // Trig默认输出低电平 ECHO1 1; // 准双向口读引脚前先置1 } void Timer0_Init(void) { AUXR ~0x80; // 清T0x12定时器0工作在12T模式 TMOD 0xF0; // 只清T0的模式位不影响T1 TMOD | 0x01; // T0为16位定时器不自动重装 TH0 0; TL0 0; ET0 0; // 测距主流程用查询方式不用中断 TR0 0; // 先不启动等待触发后手动打开 }代码里AUXR ~0x80这一行是关键很多从 STM32 转过来的开发者习惯性认为时钟越快越好结果把 T0x12 置 1 选成 1T 模式。12T 模式下定时器计数频率是系统时钟的十二分之一在 11.0592MHz 晶振下大约是 921.6kHz每秒计 92 万个脉冲每个计数对应约 1.085us1T 模式下计数频率直接等于系统时钟11.0592MHz 时 16 位计数器在约 5.9ms 内就会溢出这个时间只够大概 1 米范围内的测距。四路 HC-SR04 的标称量程是 2cm 到 400cm4 米对应约 23ms 的往返时间1T 模式根本撑不住。3. 定时器计时与距离换算16位计数器在1T/12T模式下的量程边界3.1 Echo脉宽与定时器计数的对照关系声速在 20℃ 空气里约 343m/s即每微秒走 0.343 毫米。超声波测距测的是往返时间所以距离等于时间乘声速再除以 2。HC-SR04 的 Echo 高电平宽度与距离严格成正比下表列出了几个典型距离下的时间参数按 12T 模式、11.0592MHz 晶振、每计数约 1.085us 计算目标距离单程时间Echo高电平宽度12T模式计数值16位余量2cm58.3us116.6us1076542910cm291.5us583us53764999100cm2.915ms5.83ms537260164400cm11.66ms23.32ms2149344043从表中可以看到12T 模式下即使测到 4 米计数也只用到 21493距离 16 位定时器的 65536 上限还有充足余量。而如果误选 1T 模式11.0592MHz 下最大计时仅 5.9ms算下来极限距离刚过 1 米超出量程后定时器已经翻转算出的距离会变得毫无规律。这就是上一章代码里坚持用 12T 模式的根本原因。3.2 带超时保护的测距函数触发 HC-SR04 的流程并不复杂Trig 拉高 10us 以上再拉低然后等 Echo 变高开始计时等 Echo 变低停止计时读取定时器值换算距离。真正考验代码鲁棒性的地方在等待 Echo 下降沿这一步如果前方没有障碍物或者目标超出量程Echo 可能一直保持高电平程序就会死循环。下面的函数加入定时器溢出检测用 TF0 标志判断是否超时unsigned int GetDistance1(void) { unsigned int time_count; TRIG1 1; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); TRIG1 0; // 保持约10us高电平触发模块 TH0 0; TL0 0; TF0 0; TR0 1; // 启动定时器0 while (ECHO1 0) // 等待回波上升沿 { if (TF0) // 等待超时说明没有回波 { TR0 0; return 999; // 返回超量程标识 } } TH0 0; TL0 0; TF0 0; // 上升沿到达重新清零计时 while (ECHO1 1) // 等待回波结束 { if (TF0) { TR0 0; return 999; } } TR0 0; time_count ((unsigned int)TH0 8) | TL0; return time_count / 54; // 11.0592MHz 12T约54个计数等于1cm }这里time_count / 54是简化换算系数推导过程是每个定时器计数约 1.085us声速 34300cm/s往返旅程除 2得到每厘米需要约 54 个计数。如果板子换成 12MHz 晶振12T 模式下每计数 1us每厘米对应约 58 个计数系数就要改成 58。Keil5 工程里用(unsigned int)TH0 8而不是直接TH0 8是为了避免 8 位变量左移时被编译器截断成意外结果。3.3 等待上升沿阶段的清零时机问题上面代码里有两个清零动作第一个是在触发后的定时器清零目的是给整个回波等待过程设一个时间上限防止 Trig 拉了之后根本没有回波第二个是在捕获到上升沿之后再清零一次目的是把等待上升沿消耗的时间剔除出去。如果没有第二个清零Echo 在 2 米处才拉高此前空的等待时间会被一起算进回波脉宽最终显示的距离会比实际距离偏大。常见的一个错误写法是只在触发前清零一次然后先等上升沿再等下降沿直接读定时器。这种写法在近距离测量时误差不大因为上升沿在几百微秒内就会到来但距离拉到 3 米开外后等待上升沿的时间本身可能接近 20ms和真正的回波宽度在同一数量级测出的距离几乎翻倍。把清零动作放到上升沿捕获之后是保证测量值只反映 Echo 高电平宽度的关键。3.4 定时器溢出返回值的工程约定返回 999 表示超量程这是 HC-SR04 类程序里比较通行的约定。主流程拿到 999 后OLED 上可以显示三条横线或者 OFFLINE 之类的提示不要让 999 直接参与后续取平均值或者避障判断。需要注意 HC-SR04 的最小盲区约 2cm在这个距离内模块会把发射探头的余振误判成回波测量值经常固定在某个偏大的数字上毫无波动。发现 OLED 上近距离读数不变化时优先考虑是不是进入了盲区而不是怀疑代码逻辑。4. 四路并行测距与OLED驱动IO分配和显存刷新的协同设计4.1 串行轮询四个通道的代价四个 HC-SR04 如果按最简单的串行方式逐通道测距主流程依次调用GetDistance1、GetDistance2直到第四路每路最坏情况要等满 4 米对应的 23ms四路加起来接近 100msOLED 显示刷新率只能做到 10Hz 左右。这个刷新率对静态测距或障碍物提示没问题但如果用在移动小车上10Hz 意味着小车开 0.5m/s 时每帧之间有 5cm 的盲区避障响应明显不够跟手。串行轮询的好处是代码结构最简单四路的软件逻辑完全一致不需要处理中断嵌套和共享定时器的竞争问题。原工程的olcd.uvproj里如果没有编译并烧录过建议先把串行版本跑通OLED 能看到四路距离都已经正常刷新再考虑并发优化。并发方案是在串行版本基础上改出来的而不是另起炉灶。4.2 同时触发与中断捕获的并发思路并发方案的核心是让四路模块同一时刻开始测距声波飞行时间只消耗一次而不是四次。做法是把四个 Trig 引脚全部并联到 STC15 的同一个 IO触发后四个模块同时发出超声波脉冲然后四路 Echo 各自返回高电平宽度分别代表四个方向的障碍物距离。读取阶段用 STC15 的 INT0 到 INT3 四个外部中断分别接四路 Echo 的下降沿中断服务里读出当前定时器值存到对应全局变量。void INT0_Isr(void) interrupt 0 { TR0 0; // 停止计时保存当前计数 dist_ch1 ((unsigned int)TH0 8) | TL0; } void INT1_Isr(void) interrupt 2 { TR0 0; dist_ch2 ((unsigned int)TH0 8) | TL0; }注意这段代码的局限第一个下降沿到来后 TR0 被关闭后续几路中断读到的计数值相同。要在中断里保留各自的时间戳正确做法是保持 TR0 一直运行中断里只做保存计数值的操作等到最晚的一路回波结束后再由主循环关闭定时器。同时触发还有一个理论问题四路 Echo 上升沿到达时刻不同而我们要测的是每路高电平宽度不是绝对到达时刻所以严格做法是每个通道单独捕获上升沿和下降沿两次沿。STC15 一个外部中断只能配置成上升沿或者下降沿触发工程上通常保留轮询方式等待上升沿再开启对应中断捕获下降沿或直接用轮询读完四路并发方案在中断资源不足时并不比串行快多少。4.3 串行轮询四路的主循环结构考虑到多数使用场景测量的是静态或慢速目标工程里最稳妥的还是串行轮询配合 OLED 刷新。主循环只要做到三件事依次测四路距离、把结果写入 OLED、控制两次测量间的间隔防止屏幕上数字跳得太快void main(void) { unsigned int d1, d2, d3, d4; GPIO_Init(); Timer0_Init(); OLED_Init(); while (1) { d1 GetDistance1(); d2 GetDistance2(); d3 GetDistance3(); d4 GetDistance4(); OLED_ShowString(0, 0, CH1:); OLED_ShowNum(5, 0, d1, 3); OLED_ShowString(0, 2, CH2:); OLED_ShowNum(5, 2, d2, 3); OLED_ShowString(0, 4, CH3:); OLED_ShowNum(5, 4, d3, 3); OLED_ShowString(0, 6, CH4:); OLED_ShowNum(5, 6, d4, 3); delay_ms(50); // 限制显示刷新频率避免数字闪烁 } }OLED_ShowString的第一个参数是页地址0 到 7 对应 64 行 OLED 的每 8 行一页第二个参数是列地址按字符宽度递增。OLED_ShowNum的第三个参数 3 表示显示三位数字配合 HC-SR04 的 2cm 到 400cm 量程刚刚好超过 999 时显示逻辑要单独处理。4.4 OLED初始化与I2C写入的要点以 SSD1306 控制器的 0.96 寸 OLED 为例I2C 写地址通常是 0x78部分模组是 0x7A初始化不成功时先换这个地址试。STC15 没有硬件 I2C 外设时用 IO 口模拟时序SCL 和 SDA 两根线在空闲状态下都是高电平SCL 高电平期间 SDA 跳变是起始或停止条件。初始化序列只需要保证关闭显示、打开电荷泵和设置显示时钟这几步缺了电荷泵屏幕大概率一直黑屏void OLED_Init(void) { OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0x8D); OLED_WriteCmd(0x14); // 打开内部电荷泵 OLED_WriteCmd(0xD5); OLED_WriteCmd(0x80); // 显示时钟分频 OLED_WriteCmd(0xAF); // 打开显示 }OLED_WriteCmd内部把控制字节 0x00 放在命令前面发给从机数据写入时换成 0x40。SSD1306 的显示数据是按页缓存到芯片内部 RAM 的写显示字符串时先计算字符在字库数组里的偏移位置再把字节一列一列搬进对应页。I2C 刷整屏 128 乘 64 像素需要传 1024 字节显示数据用 100kHz 速率一次全刷大约是 10ms 级别的耗时对四路测距主循环来说这个开销可以接受但不建议把整屏刷新放在高频中断里会让测距的计时被拖乱。5. STC-ISP烧录与现场调参声速温度补偿和回波脉宽验证5.1 下载前检查型号与冷启动STC15 不同于 STM32 的 SWD 烧录方式它依靠串口配合 STC-ISP 上位机下载。打开 Keil5 工程编译生成olcd.hex后在 STC-ISP 软件里选择对应单片机型号原工程原理图标注的是 IAP15 系列实际烧录时根据原理图上丝印选择例如 STC15F2K60S2 或 IAP15W4K58S4。连接好 USB 转 TTL 的 TX 和 RX记得交叉连接然后点击下载按钮再给板子重新上电整个过程就是常说的冷启动。5.2 声速温度补偿的定点计算20℃ 时的声速约 343m/s 只代表室温。0℃ 时约 331m/s40℃ 时约 355m/s跨度接近 7%。对 100cm 的目标环境温度从 0℃ 到 40℃ 会产生约 7cm 的系统性误差。补偿公式为声速 v 331.4 0.6TT 是摄氏温度。STC15 没有硬件浮点单元尽量用整数运算unsigned int GetDistanceComp(int temperature) { unsigned int time_count; unsigned long dist_x100; unsigned int vv; time_count GetRawCount(); if (time_count 0) return 0; if (time_count 65000) return 999; vv 33140 60 * (unsigned int)temperature; // 单位cm/s dist_x100 (unsigned long)time_count * vv / 20000; return (unsigned int)((dist_x100 50) / 100); // 四舍五入到cm }time_count单位是微秒级的计数值vv是声速的厘米每秒表示。分母 20000 的来源是时间转换到秒再乘 100 到厘米的换算dist_x100保留两位小数最后一步把带小数的值四舍五入回整数厘米。温度值可以由 DS18B20 这类传感器读取后传入没有温度传感器时直接把参数固定为 25 就行室内环境误差不大。5.3 用示波器验证回波脉宽拿到一块新板子OLED 显示的数字对不上尺子量出的距离时先用示波器夹在 HC-SR04 的 Echo 引脚上看真实脉宽。比如实测脉宽 583us理论距离等于 0.000583 秒乘 343m/s 除 2约等于 10cm如果 OLED 同时显示 10 左右说明定时器分频和换算系数正确。这个方法能快速区分问题出在硬件时序还是软件换算比反复改代码盲调快得多。逻辑分析仪采样率不需要太高1MHz 以上抓 Echo 信号就够看脉宽了。5.4 免理论公式的现场标定技巧最后分享一个我调 HC-SR04 惯用的标定方法完全绕开声速公式。固定一个障碍物在已知距离处比如 10cm读取此时定时器原始计数值记为 cal_cnt之后测量时直接按time_count * 10 / cal_cnt换算距离。这样温度、晶振频率误差、模块个体差异都被一次性吸收进 cal_cnt 里室内环境下精度往往比用理论声速系数算出来的还稳。换一个 HC-SR04 模块或者改过晶振后重做一次标定即可。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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