简介:US-016超声波测距模块产品手册,专注于STM32等微控制器平台的中短距离非接触测距应用,为开发者提供从选型评估、引脚接线、量程配置到程序编写的全套参考。文档基于官方V1.11版,不仅列出5V直流供电、3.8mA工作电流、0~70摄氏度工作温度、小于15度感应角度、0.3cm+1%精度和1mm分辨率等关键参数,还解释了模拟电压输出与测量距离成正比的内在原理,便于理解不同电压对应不同距离的换算逻辑。Range引脚内置上拉电阻,悬空或高电平对应3m量程,低电平对应1m量程;手册据此分别给出1m量程下L=1024Vout/Vcc与3m量程下L=3096Vout/Vcc的计算公式,并提示10位ADC参考电压等于Vcc时,采样值可直接作为毫米距离,简化编程思路。压缩包内文件数量为1个,类型为PDF技术手册,整体大小353KB,包含4Pin接口定义、45mm×20mm×1.2mm外形尺寸图、Arduino量程示例代码等,方便STM32及Arduino用户直接参考。已有570人学习浏览,适用于机器人导航、环境监测、自动化测距等嵌入式场景,按照手册配置即可快速完成ADC采样与距离解算,提升项目开发效率。
1. US-016 超声波测距模块:模拟电压输出怎么接进 STM32 的 ADC,量程怎么设
US-016 这款模拟电压输出的超声波测距模块,在 STM32 项目里常被当成一支"会变电压的电位器"来用:5V 供电,Out 引脚接 ADC,读到的电压线性对应距离。它的探测范围是 2cm~3m,工作电流只有 3.8mA,精度标称 0.3cm+1%,分辨率 1mm。相比常见的 I2C/UART 测距模块,模拟输出没有协议时序问题,一个 ADC 通道就能读数,几乎不占定时器资源。真正让新手翻车的不是接线,而是 Range 引脚的上电锁存逻辑和两种量程的换算系数。这篇从引脚定义、公式推导到 STM32 实际采样一次讲清楚,适合做避障、液位、接近检测的开发者照着复现。
2. 四线接口与量程机制:Range 引脚的上电电平决定你能测多远
2.1 引脚定义与供电细节
先看接口。模块只有一个 4 Pin 连接器,2.54mm 间距弯排针,从左到右编号 1~4,定义如下:
| Pin | 名称 | 功能说明 |
|---|---|---|
| 1 | VCC | 直流 5V 电源正极 |
| 2 | Range | 量程设置:上电为高电平切 3m 量程,低电平切 1m 量程,内部上拉 |
| 3 | Out | 模拟电压输出,范围 0~Vcc,与距离成正比 |
| 4 | GND | 电源地 |
供电电压标称 DC 5V,工作电流 3.8mA。这个电流是模块自身功耗,不包含 Out 外接负载,接 ADC 时因为 ADC 输入阻抗很高,额外电流可以忽略,所以直接用单片机板上的 5V 就能带得动,不需要单独一路电源。模块工作温度 0~70°C,室内正常使用没问题,放在机箱里长期贴近发热元件时建议先实测环境温度再定安装位置。整板尺寸 45mm × 20mm × 1.2mm,板上有两个 1mm 机械孔,固定时用 M1 螺丝或扎带都可以,装支架时注意别把排针侧的铜箔压到。
第一件要记住的事:Out 的输出范围是 0~Vcc。VCC 接 5V 时,Out 最高接近 5V。很多人第一次拿 3.3V 单片机的 ADC 直接量它,近距离读数一开始是线性的,到某个距离后读数就封顶不动了,那是输出电压超过 ADC 参考电压后的饱和现象。怎么处理放到第 4 章展开,这里先记住这条边界。
2.2 Range 引脚的"上电锁存"逻辑
Range 引脚内部带上拉电阻,悬空时等效于高电平,所以模块默认量程是 3m。要切到 1m,必须在模块上电的时候把 Range 拉低。
关键理解在于"上电时"三个字。手册描述是:上电时 Range 为高电平,量程 3m;上电时为低电平,量程 1m。也就是说量程是上电瞬间采样并锁存的,运行中修改 Range 电平并不生效。你开着机把 Range 从高拉低,测距公式不会跟着变,读数还是按 3m 量程算,必须断电重新上电才切换。这个设计在第一次上手时很容易被忽略,我先在洞洞板上踩过这个坑,后来凡是改量程都老老实实断电重上。
我一般用两种方案管理这个引脚。调试阶段用跳线帽:要 1m 就把 Range 短接到 GND,要 3m 就悬空,改起来直观。量产场景用 MCU 引脚控制:STM32 的某个 GPIO 接 Range,同时用 MOS 管或负载开关控制模块 VCC,上电时序是"先设置 GPIO 电平,再打开模块电源"。要注意的是一个细节:STM32 GPIO 高电平是 3.3V,而 Range 是 5V 逻辑输入,3.3V 高电平并不保证能被可靠识别成高电平,因为很多 5V 器件的输入高阈值在 0.7×Vcc 即 3.5V 附近。稳妥做法是用开漏输出外加电阻上拉到 5V,或者用 NPN 三极管拉低,避免电平卡在阈值边缘的玄学问题。
2.3 量程选择别只看最大距离
1m 量程满量程对应 1024mm,3m 量程对应 3072mm,两种量程的电压系数差 3 倍。选量程不光看目标距离,还要看分辨率利用率和噪声表现。
近距离场景建议 1m 量程。桌面避障、机械臂防碰撞这类目标距离一般不超过 50cm,用 1m 量程时 10 位 ADC 一个 LSB 就是 1mm;换 3m 量程,一个 LSB 对应 3mm,读数颗粒感明显,近距离的小变化会被"吃掉"。远距离场景只能用 3m,比如小车前方 2~3m 的障碍探测、仓储货架高度测量,用 1m 量程会直接溢出。
还有一种情况我建议直接选 3m:液位和料位监测。液面晃动时读数本来就有波动,1m 量程分辨率太高,反而把水面波纹放大成噪声,滤波会变得更费劲。另外模块感应角度小于 15 度,属于锥形波束,安装时探头轴线要垂直对准目标反射面,倾斜 15 度以上回波衰减明显,读数会偏大。模块最小探测距离 2cm,2cm 以内是盲区,发射余振没有停,接收电路还在饱和,这个边界要在程序里做无效判断。
提示:Range 悬空默认 3m。如果你焊了排针但没接这根线,所有 1m 量程的配置代码都不会生效,排查时先确认硬件再接软件。
3. 测距公式与 ADC 换算:输出电压到毫米的完整链路
3.1 1m 量程:满量程 1024mm 的由来
模块把距离线性映射成模拟电压,1m 量程的关系式是:
L = 1024 × Vout / Vcc(mm)
0V 对应 0mm,Vcc 对应 1024mm。这里满量程是 1.024m 而不是 1.000m,为什么定成 1024?因为 2^10 = 1024,正好对齐 10 位 ADC 的满量程,采样值可以省一次乘法直接当距离用。这个系数不是随便拍的,是为了兼容 8 位/10 位 ADC 的整数运算场景。
手册同时给出通用 ADC 换算公式:
L = A × 1024 / 2^n × Vref / Vcc
其中 A 是 ADC 采样值,n 是 ADC 位数,Vref 是 ADC 参考电压。这个式子包含两个前提:ADC 输入范围是 0~Vref,并且 Vref 和 Vcc 的关系必须明确。只有当 Vref = Vcc 时,L = A × 1024 / 2^n 才能化简。很多移植失败都发生在这里:模块按 5V 供电,单片机 ADC 参考却是 3.3V,直接套"采样值等于毫米数"的结论,结果差一大截。接线之前先确认这两个电压是否一致,比调代码更重要。
3.2 3m 量程:3072 和 3096 的取舍
3m 量程的电压关系式是:
L = 3072 × Vout / Vcc(mm)
Vcc 满量程对应 3072mm,即 3.072m。注意手册里另一处把系数写成 3096,按 3.072m 的满量程定义反推,3072 才是自洽的,3096 基本可以判定是早期版本笔误,代码和标定一律以 3072 为准。这 0.8% 的差异在要求不高的场合察觉不到,但做完两点标定后会看到明显的系统性偏差,追根溯源时容易卡住。
10 位 ADC 下 3m 量程每个 LSB 对应 3mm,所以例程直接用 ADCValue *= 3。对比 1m 量程每 LSB 1mm,3m 量程的电压分辨率天生差 3 倍。这不算模块缺陷,是物理限制:同一个 0~Vcc 电压窗口,量程拉长 3 倍,同样的电压步进对应的距离跨度变大,ADC 量化误差占比自然升高。要求毫米级读数时,优先用 1m 量程。
3.3 Arduino 例程拆解:采样值为什么能直接当毫米
1m 量程的 Arduino 例程是这样:
unsigned int ADCValue; void setup() { Serial.begin(9600); } void loop() { ADCValue = analogRead(0); Serial.print("Present Length is: "); Serial.print(ADCValue, DEC); Serial.println("mm"); delay(1000); // delay 1S }这段代码能成立,是因为三条前提同时满足:A0 是 10 位 ADC,默认参考电压 AVCC 等于 5V,模块 VCC 也接 5V,而且两端共地。于是 ADC 采样值 = Vout / 5V × 1024 = Vout / Vcc × 1024,恰好等于毫米数。任何一个前提不满足,比如模块改成 3.3V 供电,或者 ADC 参考电压换成内部 1.1V,"采样值等于毫米"就不成立,必须回到通用公式。
3m 量程例程只多了一行:
ADCValue = analogRead(0); ADCValue *= 3;本质是 10 位采样值乘 3。到这里能看出,例程省事全靠"Vref = Vcc = 5V 且共地"这个巧合。换到 STM32 的 12 位 ADC、参考电压 3.3V 的环境,这套简洁写法完全失效,要按第 4 章的方式单独处理。
3.4 常见 ADC 位数的换算系数对照
为了方便移植,我把不同 ADC 位数在两种量程下的距离换算系数列出来(前提均为 Vref = 模块 Vcc):
| ADC 位数 | 满量程采样值 | 1m 量程距离(mm) | 3m 量程距离(mm) |
|---|---|---|---|
| 8 | 256 | L = A × 4 | L = A × 12 |
| 10 | 1024 | L = A | L = A × 3 |
| 12 | 4096 | L = A / 4 | L = A × 0.75 |
12 位 ADC 的 3m 量程系数是 0.75,换算出来可能是小数,处理时记得用浮点或定点,别直接整数除法把精度丢掉。这张表在 Vref ≠ Vcc 时整体不成立,最稳的路径永远是先把采样值折算回电压,再代入 L = 系数 × Vout / Vcc。
4. STM32 实战:HAL 库 ADC 采样、分压网络与距离换算
4.1 硬件连接:Out 的 5V 动态范围怎么进 3.3V ADC
STM32 的 ADC 输入范围是 0~VDDA,常见 VDDA 为 3.3V。US-016 在 5V 供电下 Out 最高接近 5V,直接接进去有两个问题:超过 3.3V 的部分会被截顶,读数饱和;长期超过绝对最大额定还可能损伤引脚。常见做法是电阻分压,把 0~5V 映射到 0~3V 附近,这样既有安全裕量,又不损失太多电压分辨率。
推荐用 10kΩ 和 15kΩ 两只 1% 电阻:Out → R1(10k) → ADC 引脚,再从 ADC 引脚接 R2(15k) 到 GND。分压比 k = R2 / (R1 + R2) = 0.6,5V × 0.6 = 3.0V,离 3.3V 有 0.3V 裕量,不会顶到参考电压。同时在 R2 两端并一只 0.1µF 电容,形成低通滤波,能压掉一部分超声波回波带来的毛刺。分压后 ADC 引脚看进去的等效源阻抗约是 R1 与 R2 并联,即 6kΩ,对常规 ADC 采样来说足够小,但采样间隔要大于电容的充放电时间,否则读数会偏低。
4.2 STM32F103 的单通道 ADC 初始化与读取
用 STM32F103 的 ADC1 通道 0(PA0)做单次采样,HAL 库配置如下:
ADC_HandleTypeDef hadc1; void US016_ADC_Init(void) { GPIO_InitTypeDef gpio = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); gpio.Pin = GPIO_PIN_0; gpio.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &gpio); hadc1.Instance = ADC1; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.NbrOfConversion = 1; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.Resolution = ADC_RESOLUTION_12B; HAL_ADC_Init(&hadc1); ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); } uint32_t US016_ReadADC(void) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); return HAL_ADC_GetValue(&hadc1); }逻辑说明:初始化把 PA0 配成模拟输入,ADC1 配成 12 位、单通道、软件触发,采样时间取最长的 239.5 个 ADC 时钟周期。采样时间长一点没有坏处,因为模块 Out 是缓慢变化的直流电平,不是高频信号,宁可多花几十微秒也要保证采样电容充分充电。US016_ReadADC 每次启动一次转换,等转换完成(超时 10ms)后取 12 位结果。如果 HAL_ADC_PollForConversion 一直返回超时,先查时钟树里 ADC 时钟有没有打开,这是移植 HAL 工程最常见的翻车点。
4.3 距离换算与滑动滤波
拿到 12 位采样值后,先把 ADC 值还原成模块输出电压,再算距离:
#define VREF_MV 3300.0f #define ADC_FULL_SCALE 4095.0f #define DIVIDER_K 0.6f /* 15k / (10k + 15k) */ #define MODULE_VCC_MV 5000.0f #define RANGE_1M_MM 1024.0f #define RANGE_3M_MM 3072.0f float US016_GetDistanceMM(uint8_t use_3m_range) { uint32_t raw = US016_ReadADC(); float vadc_mv = (float)raw * VREF_MV / ADC_FULL_SCALE; float vout_mv = vadc_mv / DIVIDER_K; /* 还原分压前的模块输出 */ float dist_mm = (use_3m_range ? RANGE_3M_MM : RANGE_1M_MM) * vout_mv / MODULE_VCC_MV; return dist_mm; }参数说明:VREF_MV 是 PA0 引脚实际的参考电压,如果板子上 VDDA 不是精确 3.3V,用万用表实测后填进去;DIVIDER_K 必须和实际分压电阻一致,电阻用 1% 精度时 k 的偏差约 2%,这部分可以由第 6 章的标定吸收;MODULE_VCC_MV 是模块供电电压,5V 电源带载后有跌落也按实测值填。整个链路是:12 位原始值 → 引脚电压 → 分压还原 → 距离,一步都不能少。
连续测距读数会有随机波动,常见做法是采 8 次求平均,每次间隔一点时间让分压电容稳定:
#define FILTER_N 8 float US016_GetDistanceFiltered(uint8_t use_3m_range) { uint32_t sum = 0; for (int i = 0; i < FILTER_N; i++) { sum += US016_ReadADC(); HAL_Delay(3); /* 等分压电容重新充满,RC 约 0.6ms */ } float raw_mean = (float)sum / FILTER_N; float vadc_mv = raw_mean * VREF_MV / ADC_FULL_SCALE; float vout_mv = vadc_mv / DIVIDER_K; return (use_3m_range ? RANGE_3M_MM : RANGE_1M_MM) * vout_mv / MODULE_VCC_MV; }这里的 HAL_Delay 3ms 是算出来的:ADC 引脚等效源阻抗约 6kΩ,并联 0.1µF 电容后时间常数约 0.6ms,等 5 个时间常数约 3ms,电容电压才能跟上前一次采样引起的跌落。如果省掉这个延时,连续采样时读数会低几个 LSB,表现为静止目标距离缓慢漂移,这是采样电路供电恢复问题,不是模块本身的问题,排查时先看这里。
5. 避坑指南:量程锁存、电压截顶与文档系数的五个坑
5.1 上电后再切 Range:量程根本没变
现象:程序里先让 GPIO 输出低电平控制 Range,再读距离,发现读数范围还是 3m 量程,近距离读数明显偏小。
原因:量程在上电瞬间锁存,GPIO 设置完成时模块已经完成量程采样,运行中修改 Range 不生效。
解决:让 GPIO 先输出目标电平,再给模块 VCC 上电。用 MOS 管或负载开关控制 VCC 时,初始化顺序是"GPIO 配置输出 → 拉电平 → 延时 10ms → 打开模块电源"。调试阶段没有电源控制电路,就断电重新上电切量程,别指望软件热切换。
5.2 Out 直连 3.3V ADC:读数到一半封顶
现象:近距离读数随距离线性增长,到某个距离后数值不再变化,继续靠近也没反应。
原因:模块输出电压超过 ADC 参考电压 3.3V,ADC 在满量程处饱和。按 1m 量程算,3.3V 对应约 675mm,超过这个距离读数就开始失真,等于量程被白白吃掉一半。
解决:按第 4 章的分压方案处理,分压比留出裕量,选 0.6 而不是 0.66。如果项目不允许分压引入额外误差,可以改用外部 ADC 芯片直接接受 5V 输入,但成本和电路复杂度都会上升,多数场景不值当。
5.3 手册系数 3096 与 3072 混用:标定差异的元凶
现象:按手册某一页的公式写代码,标定时发现读数整体偏大不到 1%,怎么滤波都消不掉。
原因:手册里 3m 量程同时出现 3096 和 3072 两个系数,3096 与满量程 3.072m 的定义不自洽,判定为笔误。按 3096 算,每个读数会系统性多出约 0.8%。
解决:代码和标定一律用 3072。如果拿到的资料里两个系数都出现了,写注释注明"以 3072 为准",防止后来接手的人改回去。这个坑很隐蔽,因为 0.8% 的偏差在单点读数里几乎看不出来,只有在做两点标定拟合斜率的那一步才会暴露。
5.4 目标物倾斜或太近:读数跳变、回波丢失
现象:对着墙面测距离正常,对着圆瓶、斜板测,读数忽大忽小,有时候直接跳到很大的值。
原因:模块感应角度小于 15 度,探头发出的是锥形波束,目标面倾斜后回波不能垂直返回探头,回波能量衰减,可能丢波。丢波时模块不报错,而是维持上一次输出或输出异常值。另外 2cm 以内的盲区,发射余振未停,接收电路还在饱和,读数不可信。
解决:安装支架时保证探头轴线垂直目标面;测液位时加导波管;程序里对小于 2cm 的读数直接判无效,对单次跳变用中值滤波剔除。跳变比持续偏差难查,建议把 ADC 原始值和换算后的距离一起打日志,肉眼对比才能定位是采样问题还是波束问题。
5.5 多路测距互相串扰:两个模块打架
现象:左右各装一个 US-016,单独测都正常,同时工作后读数周期性紊乱。
原因:两个模块使用相同频率的超声波,A 的发射波可能被 B 的接收端收到,形成虚假回波,距离数值出现周期性错误。
解决:让两个模块分时工作,用 GPIO 分别控制 VCC,错开 50ms 以上逐个采样;或者把两个探头朝向错开、加挡板隔离。超声波串扰是这类模块的共性问题,不是 US-016 个例,多传感器布局时一定要提前规划时序。
6. 两点标定与验证流程:让 1mm 分辨率真正可复现
手册公式跑通只是第一步。真实项目里分压电阻有误差、参考电压不精确、模块个体有差异,理论公式算出来的距离通常会有几毫米到十几毫米的系统偏差,所以我的习惯是:公式先跑通,然后立刻做两点标定。
操作不复杂:找一个平整墙面固定模块,在 300mm 和 1500mm 处各摆一块平板作为反射面,记录程序输出的原始换算值 raw1、raw2 和真实距离 true1、true2,算出斜率 k 和偏移 b:
k = (true2 - true1) / (raw2 - raw1) b = true1 - k × raw1然后在代码里加一层线性校正:
float US016_CalibratedMM(float raw_mm) { static const float k = 1.01f; /* 两点标定得到的斜率 */ static const float b = -8.0f; /* 偏移,单位 mm */ return k * raw_mm + b; }标定点选在量程的 20% 和 80% 附近比较稳,两个点都挤在量程一端时,拟合斜率容易被单点测量误差带偏。标定完之后在全量程取 4~5 个点验证,偏差落在手册精度范围内才算合格。
滤波方面,我用中值加滑动的组合:先取 5 次原始值排序取中位数,剔除突发跳变,再对中位数序列做 4 次滑动平均,压掉周期性纹波。中值窗口别开太大,否则近距离快速接近时读数滞后明显,避障场景会直接撞上。
验证时记得把 ADC 原始值、分压电压、换算距离三个量同时打印出来。这个习惯帮我抓过不少问题,比如分压电阻虚焊、5V 电源纹波过大导致读数抖动、Range 引脚被误接地导致量程错乱。从那以后,我每次拿到新的超声波模块,都会先花十分钟把上电时序和输出特性读透,再决定硬件怎么接,这套流程帮我少走了很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取