行业资讯

Arduino HC-SR04超声波测距:从原理到避障小车的实战指南

发布时间:2026/8/19 21:59:14
Arduino HC-SR04超声波测距:从原理到避障小车的实战指南 1. 项目概述用超声波“看见”距离玩Arduino的朋友估计都绕不开一个经典项目测距。无论是做智能小车避障还是做个自动感应垃圾桶又或者搞个水位监测你总得知道前方有没有东西、距离多远。这时候HC-SR04超声波模块就成了性价比最高的“眼睛”。它便宜、易用、精度对于大多数创客项目来说也足够几乎是Arduino入门后的必修课。这个项目听起来简单——不就是让Arduino发个信号然后计算一下回波时间吗但真上手做你会发现从接线、代码调试到实际应用每一步都有值得琢磨的地方。比如为什么测距结果偶尔会飘怎么让数据更稳定除了简单的串口打印还能玩出什么花样我折腾过不少超声波相关的项目从简单的测距仪到复杂的多传感器融合定位HC-SR04这个老朋友陪我踩过不少坑也积累了一些让它更“听话”的心得。今天我就以一个老玩家的视角带你从头到尾、由浅入深地玩转Arduino和HC-SR04。我们不止于让串口监视器跳出几个数字更要理解背后的原理解决实际应用中的问题并探索一些进阶玩法。无论你是刚拿到模块的新手还是想优化现有项目的老手相信都能找到有用的东西。2. 核心原理与模块拆解声音的速度与时间的艺术2.1 超声波测距的基本原理HC-SR04的工作原理本质上是一种“回声定位”。想象一下你在山谷里大喊一声然后听到回声。如果你知道声音在空气中的速度并且能精确测量从喊出到听到回声的时间你就能算出山谷离你有多远。HC-SR04干的就是这个事只不过它用的是人耳听不到的超声波频率通常高于40kHz而且精度高得多。模块内部有一个超声波发射器和一个接收器。工作时Arduino给模块的Trig引脚一个至少10微秒的高电平脉冲这个脉冲就像“起跑枪”触发模块发射一束8个周期的40kHz超声波。这束声波在空气中传播遇到障碍物后反射回来被接收器捕捉到。模块的Echo引脚会输出一个高电平脉冲这个脉冲的宽度正好等于超声波从发射到返回所经历的总时间。接下来的计算就简单了距离 (声速 × 时间) / 2。为什么要除以2因为时间t是声波“往返”一趟的时间而我们只需要单程的距离。声速在常温20°C干燥空气中大约是343米/秒也就是每微秒0.0343厘米。所以距离厘米 ≈ (0.0343 × 时间微秒) / 2 时间微秒 × 0.01715。注意这里的声速是一个近似值。实际上声速受温度和湿度影响很大。温度每升高1°C声速约增加0.6米/秒。对于精度要求不高的室内项目用343米/秒问题不大。但如果你的项目用在温差大的环境如室外或者需要厘米级的高精度就必须考虑温度补偿。我们会在后面的进阶部分详细讨论。2.2 HC-SR04模块引脚与电气特性拿到HC-SR04模块你会看到四个引脚VCC供电正极接5V。务必注意虽然有些模块标称支持3.3V-5.5V但绝大多数HC-SR04需要5V供电才能稳定工作特别是触发和回波信号的电平。接3.3V可能导致测距不稳定或完全失效。Trig触发信号输入引脚。接收来自Arduino的脉冲信号触发模块发射超声波。Echo回波信号输出引脚。输出一个高电平脉冲其宽度对应超声波往返时间。GND接地接Arduino的GND。这里有一个关键细节Echo引脚输出的高电平是5V的。如果你用的主控板是Arduino Uno、Mega等5V逻辑的板子直接连接没问题。但如果你用的是像ESP32、ESP8266、Raspberry Pi Pico这类3.3V逻辑电平的板子直接连接5V的Echo信号可能会损坏主控板的GPIO引脚必须使用分压电路最简单的就是用两个电阻例如1kΩ和2kΩ组成分压器将5V降到大约3.3V或者使用电平转换模块。模块的测量范围官方标称是2cm到400cm但实际使用中超过2-3米后回波信号会变得非常微弱容易受干扰测量结果会不稳定。最小测距2cm也是一个理论值由于发射和接收探头有一定物理距离以及发射信号结束后内部电路需要一段“安静”时间太近的物体通常小于3-4cm可能无法准确测量或回波会与发射信号尾部重叠导致测量失败。2.3 与Arduino的交互时序理解Trig和Echo引脚之间的时序关系是写好代码和排查问题的关键。整个测距过程是一个严格的“请求-响应”流程初始化首先确保Trig引脚保持至少2毫秒的低电平让模块处于稳定状态。触发将Trig引脚设置为高电平并维持10微秒以上通常用delayMicroseconds(10)然后拉低。这个10微秒的高脉冲就是发射指令。发射与等待模块收到触发信号后会自动发射8个40kHz的超声波脉冲并开始等待回波。此时Echo引脚会由低电平变为高电平。回波接收当模块接收到反射回来的超声波时Echo引脚会从高电平拉低。因此Echo引脚高电平的持续时间就是从超声波发射到接收所经过的时间。计算距离在Arduino代码中我们使用pulseIn()函数来测量Echo引脚高电平的持续时间单位是微秒然后代入公式计算距离。一个完整的测量周期建议在60ms以上即每秒测量不要超过16-17次。给模块留出足够的“冷静”时间可以避免上一次测量的回波干扰下一次测量提高稳定性。3. 基础搭建与代码实现从零到一的测距仪3.1 硬件连接与注意事项我们以最经典的Arduino Uno为例进行连接。准备一块Arduino Uno、一个HC-SR04模块、几根杜邦线公对公。连接步骤将HC-SR04的VCC引脚连接到Arduino的5V引脚。将HC-SR04的GND引脚连接到Arduino的任意一个GND引脚。将HC-SR04的Trig引脚连接到Arduino的数字引脚 9你可以选择其他数字引脚代码中对应修改即可。将HC-SR04的Echo引脚连接到Arduino的数字引脚 10。实操心得接线时尽量使用不同颜色的线区分电源红、地黑和信号黄、绿等这样在排查问题时一目了然。如果连接后模块不工作首先用万用表检查5V和GND之间电压是否正常这是最基础的电源问题。常见连接问题排查模块不亮HC-SR04上通常有一个电源指示灯。如果不亮检查VCC和GND是否接反或接触不良。数据全为0或超大值检查Trig和Echo线是否接错、接触不良或者代码中引脚定义与实际连接不符。数据跳动剧烈可能是电源干扰。尝试给Arduino单独供电或者使用一个100uF的电解电容并联在HC-SR04的VCC和GND之间以平滑电源波动。3.2 核心代码逐行解析下面是一个最基础、最稳定的HC-SR04测距代码。我们将它上传到Arduino打开串口监视器波特率设为9600就能看到实时距离数据。// 定义HC-SR04模块连接的引脚 const int trigPin 9; // 触发引脚 const int echoPin 10; // 回波引脚 // 定义声速厘米/微秒以20°C干燥空气为基准 const float soundSpeed 0.0343; // 343米/秒 0.0343厘米/微秒 void setup() { // 初始化串口通信用于输出结果 Serial.begin(9600); // 配置引脚模式 pinMode(trigPin, OUTPUT); // Trig引脚需要输出控制信号 pinMode(echoPin, INPUT); // Echo引脚需要读取输入信号 // 初始状态确保Trig引脚为低电平 digitalWrite(trigPin, LOW); // 等待模块稳定建议至少2ms delayMicroseconds(2000); } void loop() { // 1. 发送一个10微秒的高脉冲触发测距 digitalWrite(trigPin, HIGH); delayMicroseconds(10); // 维持高电平10微秒 digitalWrite(trigPin, LOW); // 拉低触发完成 // 2. 读取回波引脚的高电平持续时间 // pulseIn()函数会等待echoPin变为高电平开始计时直到再次变为低电平返回持续的微秒数。 // 设置超时时间为30000微秒30ms对应大约5米的距离往返。 long duration pulseIn(echoPin, HIGH, 30000); // 3. 计算距离 // 距离 (时间 * 声速) / 2 float distance duration * soundSpeed / 2.0; // 4. 输出结果到串口 Serial.print(Distance: ); Serial.print(distance); Serial.println( cm); // 5. 延迟一段时间避免测量过于频繁 delay(100); // 每100ms测量一次 }代码关键点解析pulseIn(pin, value, timeout)函数这是核心。它监听指定引脚等待其变为value这里是HIGH状态开始计时直到状态改变返回持续的微秒数。第三个参数timeout是超时时间微秒如果超过这个时间引脚仍未变为目标状态函数会返回0。设置一个合理的超时如30000us可以防止在测距超出范围或失败时程序长时间卡住。单位换算duration单位是微秒soundSpeed我们用了“厘米/微秒”0.0343这样计算出的distance单位直接就是厘米比较直观。稳定性延迟loop()最后的delay(100)很重要。HC-SR04模块需要一段恢复时间。如果两次测量间隔太短前一次测量的余波可能会干扰后一次导致数据异常。60-100ms的间隔是一个比较稳妥的选择。3.3 首次上电测试与数据解读上传代码打开串口监视器你应该能看到类似这样的输出Distance: 15.23 cm Distance: 15.19 cm Distance: 15.31 cm ...用手或一本书在模块前方移动距离数据应该会相应变化。恭喜你一个最简单的超声波测距仪已经工作了如何解读数据数据在小范围内跳动±0.5cm这是正常的。超声波在空气中传播本身会受到气流、温度微小波动的影响模块的测量也有一定误差。这种跳动可以通过软件滤波来平滑我们后面会讲。数据始终为0可能是物体太近小于最小测距或者Echo引脚一直为低电平检查接线和代码。数据为一个巨大的固定值如20000cm这通常是pulseIn()超时返回了0然后计算出的距离也是0。可能前方没有障碍物超出最大测距或者Echo引脚没有收到有效信号检查模块方向、障碍物材质。超声波对柔软、多孔的物体反射很差。数据偶尔跳变成一个极大值这是典型的干扰或误触发。可能是电源噪声也可能是测量间隔太短。确保供电稳定并适当增加loop()中的延迟。4. 精度提升与软件滤波让数据更“稳”基础代码跑通了但你会发现数据总是在跳。对于显示来说可能还行但如果要用这个数据去控制舵机、电机这种抖动是无法接受的。我们需要让数据变得更平滑、更可靠。4.1 多次测量与均值滤波最直接有效的方法是进行多次测量然后取平均值。这能有效抑制随机误差。const int trigPin 9; const int echoPin 10; const float soundSpeed 0.0343; const int numReadings 5; // 设定采样次数 void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(trigPin, LOW); delayMicroseconds(2000); } float getDistance() { // 发送触发信号 digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // 读取回波时间 long duration pulseIn(echoPin, HIGH, 30000); // 计算单次距离 float distance duration * soundSpeed / 2.0; // 对无效数据进行简单过滤 if (distance 0 || distance 400) { // 假设有效范围0-400cm distance 0; // 或返回一个错误值 } return distance; } void loop() { float totalDistance 0; int validReadings 0; for (int i 0; i numReadings; i) { float d getDistance(); if (d 0) { // 只累计有效数据 totalDistance d; validReadings; } delay(40); // 每次测量间稍作延迟避免相互干扰 } if (validReadings 0) { float averageDistance totalDistance / validReadings; Serial.print(Average Distance: ); Serial.print(averageDistance); Serial.println( cm); } else { Serial.println(Measurement failed!); } delay(100); // 主循环延迟 }这个方法简单但有个缺点如果某一次测量受到突发干扰产生一个极大或极小的异常值即使取平均也会对结果造成较大影响。4.2 中值滤波与滑动窗口滤波更稳健的方法是中值滤波。我们取N次测量将它们排序然后取中间的那个值作为最终结果。这种方法对“脉冲噪声”即偶尔出现的极大或极小值有很好的抑制作用。另一种在实时控制中更常用的是滑动窗口滤波或移动平均。它维护一个固定长度的数据队列每次新的测量值进入队列最旧的值被移除然后计算队列中所有值的平均值。这样输出的数据变化更连续、平滑。下面是一个滑动窗口滤波的简化示例const int windowSize 5; // 滑动窗口大小 float distanceWindow[windowSize]; int windowIndex 0; void setup() { // ... 初始化代码同上 for (int i 0; i windowSize; i) { distanceWindow[i] 0.0; // 初始化窗口 } } void loop() { float currentDistance getDistance(); // 使用前面的getDistance函数 // 将新数据放入窗口 distanceWindow[windowIndex] currentDistance; windowIndex (windowIndex 1) % windowSize; // 循环移动索引 // 计算窗口内数据的平均值 float sum 0; int validCount 0; for (int i 0; i windowSize; i) { if (distanceWindow[i] 0) { sum distanceWindow[i]; validCount; } } float smoothedDistance (validCount 0) ? (sum / validCount) : 0; Serial.print(Smoothed Distance: ); Serial.print(smoothedDistance); Serial.println( cm); delay(100); }注意事项滑动窗口的大小需要权衡。窗口越大数据越平滑但对距离变化的响应也越慢滞后。对于快速移动的物体如避障小车窗口大小设为3-5比较合适对于静态或慢速测量可以设到10。4.3 温度补偿提升绝对精度前面提到声速随温度变化。如果项目环境温度变化大或者对绝对精度要求高比如测量液位高度就必须进行温度补偿。公式很简单V 331.4 0.6 * T其中V是声速米/秒T是摄氏温度。我们需要一个温度传感器比如常用的DS18B20或DHT11。这里以DS18B20为例假设你已经连接好并导入了OneWire和DallasTemperature库。#include OneWire.h #include DallasTemperature.h // HC-SR04引脚定义 const int trigPin 9; const int echoPin 10; // DS18B20引脚定义 const int oneWireBus 2; OneWire oneWire(oneWireBus); DallasTemperature sensors(oneWire); void setup() { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(trigPin, LOW); sensors.begin(); // 启动温度传感器 delayMicroseconds(2000); } float getSpeedOfSound(float temperatureC) { // 计算当前温度下的声速 (米/秒) return 331.4 0.6 * temperatureC; } void loop() { // 1. 读取温度 sensors.requestTemperatures(); float tempC sensors.getTempCByIndex(0); // 获取第一个传感器温度 // 2. 计算当前声速 (转换为 厘米/微秒) float speedOfSoundMs getSpeedOfSound(tempC); float speedOfSoundCmPerUs speedOfSoundMs * 100.0 / 1000000.0; // 米/秒 - 厘米/微秒 // 3. 测量距离 digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration pulseIn(echoPin, HIGH, 30000); // 4. 使用温度补偿后的声速计算距离 float distance duration * speedOfSoundCmPerUs / 2.0; Serial.print(Temp: ); Serial.print(tempC); Serial.print( C | Distance: ); Serial.print(distance); Serial.println( cm); delay(500); }加入温度补偿后你的测距系统在不同季节、不同室内外环境下都能保持较高的绝对精度。这是从“玩具级”迈向“实用级”的关键一步。5. 进阶应用与项目集成不止于串口打印掌握了稳定、精确的测距能力后我们就可以把它融入到实际项目中去了。下面介绍几个经典的应用场景和集成方法。5.1 驱动舵机实现雷达扫描这是一个非常直观且有趣的应用。将HC-SR04安装在一个舵机上让舵机缓慢旋转超声波模块不断测量不同角度下的距离从而绘制出一个简单的二维雷达图。硬件连接HC-SR04连接不变。舵机信号线接Arduino的某个PWM引脚如3号引脚。舵机VCC和GND接Arduino的5V和GND注意如果舵机功率较大建议使用外部电源供电避免Arduino板载稳压器过载。代码思路控制舵机从0度旋转到180度每次步进1-2度。在每个角度触发HC-SR04进行一次测量。将角度和距离数据通过串口发送到电脑。在电脑端使用Processing、Pythonmatplotlib或串口绘图器软件将极坐标角度距离转换为直角坐标并绘图。这里提供Arduino端的核心逻辑#include Servo.h Servo myServo; const int trigPin 9; const int echoPin 10; const float soundSpeed 0.0343; void setup() { Serial.begin(115200); // 提高波特率传输数据更快 myServo.attach(3); // 舵机接在3号引脚 pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(trigPin, LOW); delay(1000); } float measureDistance() { digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration pulseIn(echoPin, HIGH, 30000); return duration * soundSpeed / 2.0; } void loop() { for (int angle 0; angle 180; angle 2) { // 每次扫描步进2度 myServo.write(angle); delay(30); // 等待舵机稳定到位 float dist measureDistance(); // 格式化输出角度,距离 Serial.print(angle); Serial.print(,); Serial.println(dist); delay(50); // 测量间隔 } // 反向扫描 for (int angle 180; angle 0; angle - 2) { myServo.write(angle); delay(30); float dist measureDistance(); Serial.print(angle); Serial.print(,); Serial.println(dist); delay(50); } }在电脑上你可以用Python的pyserial和matplotlib库轻松接收这些数据并实时绘制雷达图。这个项目完美结合了执行器控制、传感器数据采集和上位机可视化。5.2 构建简易超声波避障小车这是创客空间的经典项目。在小车前方安装1-3个HC-SR04根据测得的距离控制电机动作实现自动避障。核心逻辑单传感器前方检测持续测量前方距离。如果距离大于安全阈值如30cm小车直行。如果距离小于安全阈值但大于紧急阈值如15cm小车减速或缓慢转向。如果距离小于紧急阈值小车立即停止然后后退一段距离再随机向左或向右转弯绕过障碍物。这里涉及到电机驱动如L298N模块的控制代码会复杂一些但逻辑清晰。关键在于设置合理的距离阈值和反应逻辑避免小车在障碍物前“抽搐”频繁切换前进/后退状态。通常需要加入状态机和延时让小车在一个动作完成后稳定一段时间再进行下一次判断。5.3 与OLED显示屏集成制作独立测距仪不想总是连着电脑看串口加一块I2C接口的OLED显示屏如0.96寸SSD1306就能做成一个便携的独立数字测距仪。连接HC-SR04连接不变。OLED的VCC接5VGND接GNDSCL接A5SDA接A4对于UnoI2C引脚固定为A4-SDA, A5-SCL。你需要安装Adafruit_SSD1306和Adafruit_GFX库。代码框架#include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h // ... HC-SR04引脚和变量定义 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, -1); void setup() { // 初始化OLED if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F(SSD1306 allocation failed)); for(;;); // 卡住 } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); // ... HC-SR04初始化 } void loop() { float dist getFilteredDistance(); // 使用滤波后的距离函数 display.clearDisplay(); display.setCursor(0, 0); display.print(Dist:); display.setCursor(0, 25); display.print(dist, 1); // 显示一位小数 display.print( cm); display.display(); delay(200); // 刷新率不用太高 }这样一个带有数字显示的迷你测距仪就做好了非常直观。6. 常见问题深度排查与性能优化即使按照步骤操作你也可能会遇到一些奇怪的问题。下面我把常见的问题、原因和解决办法整理成表并分享一些优化技巧。6.1 问题排查速查表现象可能原因排查与解决方法数据始终为01. 物体太近2cm2. Echo引脚一直为低电平3. 模块损坏1. 测量稍远距离物体5cm2. 检查Echo引脚接线用示波器或逻辑分析仪看是否有脉冲信号。检查代码pulseIn参数是否正确。3. 更换模块测试数据固定为超大值如100001.pulseIn()超时返回02. 前方无障碍物超出量程3. 障碍物吸音如海绵、布料1. 检查pulseIn超时设置确保有障碍物在量程内。2. 在模块前方放置一个平整硬质物体如木板。3. 更换为硬质、平整的障碍物测试。数据跳动剧烈±5cm以上1. 电源干扰2. 测量间隔太短3. 环境噪声其他超声波源4. 障碍物表面不平或角度不对1. 在模块VCC和GND间并联一个100uF电解电容和0.1uF陶瓷电容。2. 增加测量间隔至60ms以上。3. 远离其他超声波设备如另一个HC-SR04。4. 确保超声波正对平整障碍物。使用软件滤波均值/中值。测量距离明显偏短或偏长1. 声速常数不准确未温度补偿2. 模块个体差异3. 供电电压不足1. 引入温度传感器进行补偿。2. 用一个卷尺作为基准测量一个固定距离如50.0cm计算出一个校准系数校准系数 真实距离 / 测量距离后续将测量结果乘以此系数。3. 确保供电电压稳定在5V。同时使用多个HC-SR04相互干扰超声波信号串扰1.分时复用同一时间只触发一个模块错开测量时间。2.物理隔离在不同模块的发射端套上短的橡胶管或海绵起到一定的指向和隔离作用。3.编码触发这是高级用法需要修改硬件让不同模块发射不同频率的超声波但HC-SR04是固定40kHz难以实现。6.2 电源与抗干扰优化HC-SR04对电源质量比较敏感。当舵机或电机启动时会引起电源电压的瞬间跌落可能导致超声波模块工作异常产生错误数据。优化方案独立供电为HC-SR04单独提供一路5V电源并与Arduino、电机驱动板共地。电源去耦在HC-SR04的VCC和GND引脚之间尽可能靠近模块焊接一个10uF-100uF的电解电容和一个0.1uF的陶瓷电容。电解电容应对低频波动陶瓷电容滤除高频噪声。这是成本最低、效果最显著的稳定性提升方法。信号线加短Trig和Echo的信号线尽量短如果必须延长建议使用屏蔽线或双绞线减少信号受到电磁干扰。6.3 代码层面的鲁棒性增强除了滤波代码本身也可以做得更健壮。超时与重试机制pulseIn的超时时间可以根据你的最大量程设定。如果超时不要使用0值进行计算而是将其视为一次无效测量在滤波时丢弃或进行重试。范围限制与异常值剔除在计算距离后立即判断其是否在合理范围内如2cm到400cm。对于超出范围的离谱值直接丢弃不参与后续平均或显示。状态机管理在复杂的项目如避障小车中不要简单地用if-else链。使用状态机例如enum State { MOVING, STOPPED, TURNING }来管理小车的不同行为模式并在状态切换时加入适当的延时或条件避免行为混乱。6.4 测量盲区与光束角的影响这是两个容易被忽略但很重要的物理特性。盲区HC-SR04在探头前方约2-3cm内存在一个盲区在这个区域内无法有效测量。如果你的应用需要检测非常近的物体需要考虑这个因素或者选择盲区更小的型号如US-015。光束角超声波不是一条理想的直线而是以一个锥形角度扩散HC-SR04大约15度。这意味着它测到的距离是锥形区域内最近物体的距离。如果障碍物不在正前方但在光束角内也可能被检测到。在设计安装位置和解读数据时要考虑到这个“探测锥”的存在。玩转HC-SR04从点亮到稳定再到集成应用整个过程就像在解一道有趣的工程题。它涉及硬件连接、时序理解、软件滤波、抗干扰设计等多个层面。我最深的体会是稳定性永远比单一数据的精度更重要。一个偶尔跳变的数据足以让一个自动控制系统发疯。所以多花心思在电源滤波、软件算法和错误处理上你的项目成功率会高很多。最后别忘了动手尝试把测距数据和舵机、电机、显示屏甚至物联网模块结合起来你会发现这个小小的超声波模块能打开的创意空间远比想象的要大。