☰
STM32超声波测距与DS18B20温度补偿实战指南
2026/11/3 18:12:05 网站建设 项目流程

1. 项目缘起与核心需求拆解

1.1 为什么选择超声波测距这个方向

做单片机项目的人,绕不开超声波测距这个经典命题。它看起来简单——发一个脉冲,等回波,算时间,乘声速,除以二,完事。但真正把它做稳、做准,尤其是做到能拿得出手当毕业设计或者产品原型,里面的门道比想象中多得多。

我前后做过四五个版本的超声波测距模块,从最开始的“能出数就行”,到后来被温度漂移折磨得死去活来,再到最后老老实实加上温度补偿,才算真正理解了这个东西的脾气。S007C这个项目标题里特意标注了“温度补偿”,说明作者已经踩过坑了——单纯用340m/s这个常数去算距离,在冬天和夏天、在空调房和室外,误差能大到让你怀疑人生。

这个项目适合谁?如果你是电子、自动化、物联网相关专业的学生,正在找毕业设计题目;或者你是刚接触STM32的开发者,想找一个外设组合丰富、有实际工程意义的练手项目;再或者你已经在做测距类产品,想看看别人是怎么处理温度补偿这个环节的——那这篇内容应该能给你不少可以直接抄作业的东西。

核心关键词先摆出来:STM32、超声波测距、温度补偿、HC-SR04、DS18B20。这五个词基本框定了整个项目的技术栈。STM32是主控,HC-SR04负责发波收波,DS18B20采集环境温度,温度补偿算法把声速修正到当前温度下的真实值,最终输出一个靠谱的距离数据。

1.2 温度补偿到底在补什么

先把这个事情说透。超声波在空气中的传播速度不是固定的,它随温度变化。初中物理就讲过,声速在空气中约等于331.4 + 0.6T,T是摄氏温度。0度的时候声速是331.4m/s,25度的时候变成346.4m/s,40度的时候直接飙到355.4m/s。

你如果用340m/s这个常数去算,在25度环境下误差约1.9%,在0度环境下误差约2.6%,在40度环境下误差约4.5%。听起来不大?但测距量程如果是2米,4.5%就是9厘米的误差。对于避障小车、液位检测、停车辅助这些场景,9厘米足够让车撞墙或者让液位报警误触发。

所以温度补偿不是锦上添花,是刚需。DS18B20在这个项目里的角色就是实时告诉单片机“现在多少度”,然后单片机用这个温度去修正声速,再用修正后的声速去算距离。逻辑链条非常清晰。

1.3 整体方案选型的考量

为什么用HC-04而不是其他超声波模块?便宜、好买、资料多、5V供电和STM32的3.3V IO通过简单的分压或者电平转换就能对接。它的测距范围2cm到400cm,精度能到3mm(理想条件下),对于大多数非精密场景完全够用。

为什么用DS18B20而不是LM35或者热敏电阻?DS18B20是数字输出,单总线协议,抗干扰能力强,不需要额外的ADC,直接一个IO口就能读温度,精度±0.5度,对于声速补偿来说绰绰有余。而且它支持多点组网,以后想扩展多个温度点也很方便。

主控选STM32F103C8T6,也就是常说的“蓝板”或者“最小系统板”。72MHz主频,定时器资源丰富,输入捕获、PWM输出、微秒级延时都能轻松实现。价格便宜,资料铺天盖地,Keil和VSCode都能开发,对新手极其友好。

显示部分可以用OLED或者LCD1602,串口输出也可以。我建议至少留一个串口,方便调试的时候看原始数据。如果要做完整产品,加个OLED屏做本地显示,再加个按键做校准,基本就是一个可演示的完整系统了。

2. 硬件设计与关键细节解析

2.1 核心器件选型与参数对比

先把几个关键器件的参数摆出来,方便你做选型决策。

器件型号关键参数选型理由
主控STM32F103C8T672MHz, 64KB Flash, 20KB RAM资源充足,定时器多,价格低
超声波HC-SR042-400cm, 精度3mm, 5V供电便宜好买,时序简单,资料多
温度DS18B20-55~125度, ±0.5度, 单总线数字输出,抗干扰,一线多点
显示OLED 0.96寸I2C接口, 128x64显示清晰,接线少,代码简单
电源AMS1117-3.35V转3.3V给STM32和OLED供电

HC-SR04的工作电压是5V,它的Echo引脚输出也是5V电平。STM32的IO口耐受5V的型号(比如F103的很多引脚是FT耐5V的)可以直接接,但为了保险起见,建议在Echo线上串一个1k电阻再并一个3.3V稳压管做钳位,或者用简单的电阻分压(1k+2k)把5V降到3.3V。我实测过直接接FT引脚也能跑,但长期稳定性不如加保护电路。

DS18B20的供电方式有两种:寄生电源模式和外部电源模式。寄生电源模式下,DS18B20从数据线取电,省一根线,但在温度转换期间总线必须保持高电平,时序要求更严格。我建议用外部电源模式,VDD接3.3V,GND接地,DQ接STM32的一个IO口,同时DQ上拉一个4.7k电阻到3.3V。这样时序最稳定,不容易出幺蛾子。

2.2 超声波模块的时序与测距原理

HC-SR04的时序说简单也简单,说讲究也讲究。Trig引脚给一个至少10微秒的高电平脉冲,模块内部会自动发出8个40kHz的方波,然后Echo引脚会拉高,高电平的持续时间就是超声波从发射到遇到障碍物反射回来的总时间。

关键点在于:Echo高电平的时间对应的是“往返时间”,所以距离 = 声速 × 时间 / 2。这个除以2千万别忘了,我见过不止一个新手在这里翻车,算出来的距离直接翻倍。

还有一个细节:HC-SR04的Echo引脚在没测到回波的时候会一直保持高电平,直到超时(约200ms)才拉低。如果你用轮询方式等Echo变低,程序会卡死200ms。所以要么用输入捕获+超时中断,要么用外部中断+定时器计数,要么在轮询里加超时判断。我推荐用定时器的输入捕获模式,既能测高电平时间,又能自动处理超时。

具体来说,用TIM2的通道1或者通道2做输入捕获,配置成上升沿和下降沿都触发。上升沿到来时记录计数器值,下降沿到来时再记录一次,两次差值就是高电平时间。同时开启定时器溢出中断,如果溢出次数超过阈值(比如对应200ms),就判定为超时,强制结束本次测量。

2.3 DS18B20的单总线时序要点

DS18B20的时序是很多人的噩梦,尤其是用STM32这种跑得快的主控。它的单总线协议对时间要求很严格,微秒级的偏差就可能导致读写失败。

几个关键时间参数:复位脉冲至少480微秒的低电平,然后释放总线,等待15-60微秒后检测存在脉冲。写0的时候拉低至少60微秒,写1的时候拉低1-15微秒然后释放。读的时候主机拉低至少1微秒,然后在15微秒内采样总线电平。

用STM32实现的时候,延时函数必须准。如果你用SysTick做微秒延时,注意SysTick的时钟源和重装载值。72MHz主频下,1微秒就是72个时钟周期。我一般用DWT(数据观察点)做微秒延时,比SysTick更准,而且不占用中断。

还有一个坑:DS18B20的温度转换需要时间。12位精度下需要750毫秒。如果你发完转换命令立刻去读,读到的永远是85度(默认值)。正确的流程是:复位→跳过ROM→发转换命令→等待750ms→复位→跳过ROM→发读暂存器命令→读9个字节→计算温度。等待那750ms可以用延时,也可以用定时器中断,但千万别省。

2.4 电源与抗干扰设计

超声波模块工作时瞬间电流能到15mA以上,DS18B20转换时也有1.5mA的峰值。如果整个系统用USB供电,一般没问题。但如果用电池供电,建议在电源入口加一个100微法和一个0.1微法的电容并联,滤掉低频和高频噪声。

HC-SR04对电源噪声比较敏感,如果发现测距数据跳动大,先检查电源。我遇到过用劣质USB线导致测距值乱跳的情况,换根好线立刻稳定。另外,超声波模块和STM32的晶振尽量离远一点,避免40kHz的谐波干扰晶振。

PCB布局上,DS18B20的上拉电阻尽量靠近单片机引脚,走线短而粗。超声波模块的Trig和Echo线不要和电机驱动线捆在一起走,如果项目里有电机,这一点尤其重要。

3. 软件架构与核心代码实现

3.1 整体软件流程设计

软件部分我习惯分成三层:底层驱动层、业务逻辑层、应用层。底层驱动包括GPIO初始化、定时器配置、微秒延时、DS18B20读写、HC-SR04触发和捕获。业务逻辑层负责温度读取、声速计算、距离换算、滤波处理。应用层就是主循环、显示刷新、串口输出、按键处理。

主循环的节奏大概是这样的:每500毫秒读一次DS18B20温度,每100毫秒触发一次超声波测距,每次测距结果先做温度补偿,然后做滑动平均滤波,最后更新显示和串口输出。温度和距离的更新频率不同,因为温度变化慢,没必要频繁读,而距离需要实时性。

状态机是个好东西。我用一个简单的状态机来管理超声波测距:空闲态→触发态→等待回波态→计算态→空闲态。每个状态用定时器中断驱动,避免阻塞主循环。这样主循环可以腾出手来处理显示和按键。

3.2 微秒延时函数的实现

微秒延时是DS18B20和HC-SR04的基础。我试过三种方案,最后选了DWT。

第一种是SysTick轮询,配置SysTick为1微秒中断一次,然后在延时函数里等计数。缺点是占用中断资源,而且如果中断优先级没配好,延时不准。

第二种是空循环,用__NOP()指令凑时间。缺点是不同优化等级下循环次数不一样,换编译器就废了。

第三种是DWT,也就是Data Watchpoint and Trace单元。Cortex-M3内核自带这个功能,用DWT->CYCCNT寄存器记录时钟周期数,72MHz下1微秒就是72个周期。代码大概是这样:

void DWT_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }

这个方案不占用中断,精度高,而且不受编译器优化影响。唯一需要注意的是DWT在调试器连接时才能用,如果脱离调试器运行,需要先使能TRCENA位。不过实测下来,只要初始化正确,独立运行也没问题。

3.3 DS18B20驱动代码详解

DS18B20的驱动我封装了四个函数:复位、写字节、读字节、读温度。复位函数拉低总线至少480微秒,然后释放,等待存在脉冲。写字节按位操作,先写低位。读字节也是按位读,主机先拉低1微秒,然后释放,在15微秒内采样。

读温度的函数流程前面说过了,这里重点说温度计算。DS18B20返回的是16位数据,低4位是小数部分,高12位是整数部分。如果温度是正数,直接把原始值乘以0.0625就是摄氏度。如果是负数,需要先取反再加一,然后乘以0.0625。

float DS18B20_GetTemp(void) { uint8_t temp_l, temp_h; int16_t temp_raw; float temp; DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x44); // 启动转换 delay_ms(750); DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器 temp_l = DS18B20_ReadByte(); temp_h = DS18B20_ReadByte(); temp_raw = (temp_h << 8) | temp_l; if (temp_raw < 0) { temp_raw = ~temp_raw + 1; temp = temp_raw * 0.0625; temp = -temp; } else { temp = temp_raw * 0.0625; } return temp; }

注意那个750毫秒的延时。如果你用12位精度,必须等这么久。想快一点可以用9位精度,转换时间只要93.75毫秒,但分辨率只有0.5度。对于声速补偿来说,0.5度的分辨率其实也够用,因为声速变化0.6m/s每度,0.5度对应0.3m/s的声速误差,在2米量程上对应不到2毫米的距离误差。所以如果你追求响应速度,用9位精度完全没问题。

3.4 超声波测距与温度补偿算法

超声波测距我用TIM2的输入捕获。配置TIM2为向上计数模式,预分频器设为71,这样计数器频率是1MHz,每个计数代表1微秒。通道1配置为输入捕获,上升沿和下降沿都触发。

中断服务函数里,上升沿到来时记录捕获值到变量capture_start,同时把捕获极性改为下降沿。下降沿到来时记录capture_end,计算差值得到高电平时间。如果定时器溢出,溢出中断里给一个溢出计数,超时后强制复位状态机。

温度补偿的公式很简单:

float get_sound_speed(float temp) { return 331.4 + 0.6 * temp; } float get_distance(float time_us, float temp) { float speed = get_sound_speed(temp); return (speed * time_us) / 20000.0; // 除以2,再换算成厘米 }

time_us是微秒,speed是米每秒,乘出来是米乘微秒每微秒,也就是微米。除以10000得到厘米,再除以2得到单程距离。所以是除以20000。

我实际测试过,在25度环境下,用340m/s常数和用温度补偿,测1米距离的差值大概是1.5厘米。在0度环境下,差值能到2.5厘米。对于需要精确测距的场景,这个差距是肉眼可见的。

3.5 数据滤波与稳定性处理

原始测距数据肯定有跳动,尤其是测远距离或者障碍物表面不规整的时候。我一般用滑动平均滤波,窗口大小取5到10。窗口太大响应慢,太小滤波效果差。5个点的滑动平均在响应速度和稳定性之间平衡得比较好。

如果障碍物是斜面或者软质材料,回波信号弱,可能会出现偶尔的跳变。这时候可以加一个中值滤波,先取5个点排序,去掉最大最小,剩下的取平均。这样能有效剔除野值。

还有一个技巧:如果连续几次测距值的方差超过阈值,说明信号不稳定,可以暂时保持上一次的有效值,同时把LED闪烁或者串口输出警告。这样显示出来的数据不会乱跳,用户体验好很多。

#define FILTER_SIZE 5 float distance_buf[FILTER_SIZE]; uint8_t buf_index = 0; float filter_distance(float new_dist) { distance_buf[buf_index] = new_dist; buf_index = (buf_index + 1) % FILTER_SIZE; float sum = 0; for (int i = 0; i < FILTER_SIZE; i++) { sum += distance_buf[i]; } return sum / FILTER_SIZE; }

这个滑动平均实现简单,但有个小问题:刚开始的时候缓冲区里是0,前几个数据会被拉低。解决办法是初始化时用第一个有效值填满整个缓冲区,或者用一个计数器判断是否已经填满。

4. 调试过程与常见问题实录

4.1 DS18B20读不到温度怎么办

这是最高频的问题。现象是读出来的温度永远是0或者85,或者干脆是-127。

第一步,检查硬件。用万用表量DQ线在空闲时是不是3.3V。如果不是,检查上拉电阻是不是接了,阻值是不是4.7k。我见过有人用10k上拉,也能用,但抗干扰差一些。还有人忘了接上拉,DQ线一直是低电平,当然读不到。

第二步,检查时序。用示波器或者逻辑分析仪抓一下复位脉冲和存在脉冲。复位脉冲低电平至少480微秒,存在脉冲是DS18B20拉低的60-240微秒。如果存在脉冲没有出现,说明DS18B20没响应,可能是芯片坏了或者接线错了。

第三步,检查延时函数。如果你用SysTick做微秒延时,确认SysTick的时钟配置正确。72MHz下,SysTick重装载值应该是71(72-1),这样每72个周期中断一次,正好1微秒。如果重装载值设错了,延时就不准,时序就崩了。

第四步,检查转换等待时间。如果你没等够750毫秒就去读,读到的就是85度。这个85度是DS18B20上电默认值,不是真实温度。

4.2 超声波测距值跳动大怎么排查

先看电源。用示波器看5V电源上的纹波,如果峰峰值超过100mV,加电容滤波。HC-SR04对电源很敏感,尤其是和电机共用电源的时候。

再看触发间隔。HC-SR04两次测距之间至少间隔60毫秒,否则上一次的回波还没结束,下一次触发就来了,数据肯定乱。我一般设100毫秒,留足余量。

然后看障碍物。如果障碍物是斜面、海绵、布料,回波很弱,测距值会跳。换一个硬质平面测试,如果稳定了,说明是障碍物的问题,不是电路的问题。

最后看中断优先级。如果超声波捕获中断和DS18B20的延时冲突,比如DS18B20正在延时的时候超声波中断来了,延时被打断,时序就乱了。解决办法是在DS18B20的读写过程中关中断,或者把超声波中断优先级设低一点。

4.3 温度补偿后距离反而更不准了

这种情况一般是温度读错了。先确认DS18B20读出来的温度是不是环境真实温度。用手捏住传感器,温度应该慢慢上升。如果没变化,说明读的是假数据。

还有一种可能是声速公式用错了。331.4 + 0.6T这个公式里,T是摄氏度,不是华氏度。如果你从DS18B20读出来的是华氏度(虽然默认是摄氏度),那算出来的声速就偏了。

另外检查单位换算。time_us是微秒,speed是米每秒,乘出来是微米。除以20000得到厘米。如果你除以10000,得到的是毫米,显示出来数值会大10倍。

4.4 常见问题速查表

现象可能原因排查方法解决方案
DS18B20读数恒为85转换未完成检查延时是否够750ms增加延时或降低精度
DS18B20读数恒为0上拉电阻未接量DQ线空闲电平接4.7k上拉电阻
超声波无回波Trig脉冲太短示波器看Trig波形确保至少10us高电平
测距值翻倍忘记除以2检查距离计算公式距离=声速×时间/2
测距值跳动大电源纹波大示波器看5V电源加100uF+0.1uF电容
温度补偿无效温度读取错误对比DS18B20和温度计修复DS18B20驱动
串口无输出波特率不对检查串口配置设为115200或9600
OLED不亮I2C地址错误扫描I2C地址通常为0x78或0x3C

4.5 几个容易被忽略的实操心得

第一个心得:DS18B20的线不要太长。我试过用2米长的杜邦线接DS18B20,读出来的温度偶尔会跳变。换成30厘米的短线就稳定了。如果确实需要长线,用屏蔽线,屏蔽层接地。

第二个心得:HC-SR04的两个探头不要被遮挡。有些人为了美观把超声波模块装在壳子里,只留两个孔,结果测距范围大幅缩水。探头周围至少留5毫米的净空,不要有遮挡物。

第三个心得:温度补偿的时机。不要每次测距都去读DS18B20,温度变化没那么快。我一般是每5秒读一次温度,然后缓存起来,测距的时候直接用缓存值。这样既保证了补偿精度,又不会因为频繁读温度而影响测距实时性。

第四个心得:如果测距范围要求不远(比如只有几十厘米),可以用更简单的方案。但既然标题里强调了温度补偿,说明作者追求的是全温域、全量程的准确性,那就老老实实把DS18B20和补偿算法做好。

5. 系统优化与扩展思路

5.1 从能用到好用的几个优化点

第一个优化点是自动校准。可以在系统启动时,让用户把障碍物放在已知距离(比如20厘米)处,按一下按键,系统自动计算当前的声速偏移量,然后把这个偏移量存到Flash里。这样即使温度传感器有误差,也能通过实测校准回来。

第二个优化点是多传感器融合。如果项目允许,加一个红外测距模块或者激光测距模块,和超声波数据做互补滤波。超声波在远距离和软质障碍物上表现好,红外在近距离和硬质障碍物上响应快。两者融合,取长补短。

第三个优化点是低功耗设计。如果做电池供电的便携设备,可以在两次测距之间让STM32进入Stop模式,用RTC或者外部中断唤醒。DS18B20的转换时间也可以利用起来,转换期间让MCU休眠,转换完成后再唤醒读取。

第四个优化点是数据记录。加一个SD卡模块或者Flash芯片,把每次测距的时间、温度、距离都记录下来。后期可以用Excel或者Python做数据分析,看看温度补偿的实际效果,也方便写论文的时候出图表。

5.2 显示与交互的改进

OLED显示我建议分两行:第一行显示当前温度,第二行显示距离。温度保留一位小数,距离保留一位小数。如果距离超出量程,显示“---”或者“Out of Range”。

按键可以设两个:一个用于切换单位(厘米/英寸),一个用于进入校准模式。长按校准键3秒进入校准,此时把障碍物放在20厘米处,再按一下确认,系统自动计算偏移量。

蜂鸣器可以加一个,当距离小于设定阈值时报警。阈值可以通过按键调节,比如10厘米、20厘米、50厘米三档。这个功能在避障小车和倒车雷达上很实用。

5.3 从毕业设计到产品原型的距离

毕业设计只要演示效果好就行,但产品原型要考虑更多。比如温度范围要覆盖-20到60度,DS18B20本身支持这个范围,但要注意封装。TO-92封装的DS18B20适合板载,不锈钢封装的适合防水场景。

超声波模块的防护也要考虑。HC-SR04的探头是裸露的,在潮湿或者多尘环境下容易失效。可以加一层防水透声膜,或者换成防水型超声波传感器。

外壳设计上,超声波探头要朝外,DS18B20可以放在侧面或者通过延长线引出来。OLED窗口要留透明亚克力或者直接开孔。按键用轻触开关,手感要好。

电源部分,如果做便携设备,用锂电池供电,加一个TP4056充电模块和AMS1117-3.3稳压。锂电池电压从4.2V降到3.0V的过程中,AMS1117的压差要够,否则3.3V输出会不稳。可以换成低压差的LDO,比如XC6206。

5.4 代码组织与版本管理建议

代码不要全部堆在main.c里。我习惯这样分文件:ds18b20.c/h放温度传感器驱动,hcsr04.c/h放超声波驱动,filter.c/h放滤波算法,oled.c/h放显示驱动,main.c只放业务逻辑。这样以后换传感器或者换显示,只需要改对应的驱动文件。

版本管理用Git,每次功能稳定了就提交一次。分支可以按功能分,比如feature/temp-compensation、feature/oled-display。合并到主分支之前先测试通过。如果做毕业设计,Git提交记录也是很好的过程材料。

Keil工程里注意把优化等级设成Level 1或者Level 2,不要用Level 3。Level 3优化有时候会把延时循环优化掉,导致时序崩溃。如果发现某个延时函数在Level 3下失效,给那个函数加__attribute__((optimize("O0")))或者用volatile变量。

5.5 测试用例与验收标准

做完之后怎么判断系统好不好用?我一般跑这几个测试:

常温测试:25度环境下,测10cm、50cm、100cm、200cm四个点,每个点测10次,看平均值和标准差。标准差应该在1cm以内。

温度测试:把DS18B20和超声波模块一起放进冰箱(注意防水),测0度左右的数据。再拿到室外阳光下测40度左右的数据。对比温度补偿前后的误差。

障碍物测试:用纸板、玻璃、布料、金属四种材料做障碍物,看测距值是否一致。纸板和金属应该差不多,玻璃和布料可能会有偏差,记录偏差范围。

长时间测试:连续运行2小时,每10秒记录一次数据,看有没有漂移或者死机。如果2小时稳定,基本就可以交付了。

这些测试数据整理成表格,放在报告或者论文里,比单纯说“系统运行稳定”有说服力得多。

6. 写在最后的实操体会

这个项目我从头到尾做了三版。第一版没用温度补偿,冬天在阳台测距,2米外的目标测出来只有1.92米,差了8厘米。第二版加了DS18B20,但延时函数用SysTick写的,DS18B20偶尔读失败,后来换成DWT才稳定。第三版把滤波和校准都加上,才算真正能用。

最大的体会是:超声波测距的精度瓶颈不在超声波模块本身,而在声速的准确性。HC-SR04的计时精度其实很高,1微秒对应0.17毫米的距离变化。但如果你用340m/s这个常数,温度一变,误差就直接体现在结果里。DS18B20虽然只有±0.5度的精度,但对应到声速上只有±0.3m/s的误差,在2米量程上不到2毫米。这个投入产出比非常高。

另一个体会是:调试的时候一定要有串口输出。OLED显示只能看最终结果,但串口可以打印原始时间、温度、补偿前距离、补偿后距离。数据一摆出来,问题在哪一目了然。我习惯在串口里用逗号分隔,方便直接复制到Excel里画图。

最后分享一个小技巧:如果你觉得DS18B20的750毫秒转换时间太长,可以在发完转换命令后不等待,直接去处理其他任务(比如刷新OLED、扫描按键),等750毫秒后再回来读。用一个定时器标志位来判断时间到没到。这样主循环不会阻塞,整体响应更流畅。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询