为什么你的 HC-SR04 测距不准?
HC-SR04 可能是最便宜的超声波测距模块了,淘宝上 5 块钱包邮。但很多人买回去一测:误差大到怀疑人生。明明目标在 1 米处,读数却在 80cm 到 120cm 之间跳变。
问题不在模块,而在使用方法。今天这篇就来聊聊 HC-SR04 的进阶用法,让你把 5 块钱的模块用出 50 块钱的精度。
硬件清单
| 型号 | 数量 | 单价 | 备注 |
|---|---|---|---|
| HC-SR04 超声波模块 | 2 | ¥5 | 建议买带支架的 |
| Arduino Nano | 1 | ¥15 | 或 ESP32 |
| DS18B20 温度传感器 | 1 | ¥3 | 用于温度补偿 |
| 0.96 寸 OLED 显示屏 | 1 | ¥8 | 可选,用于显示 |
| 杜邦线 | 若干 | ¥5 | 公对母 |
| 总计 | ¥36 | 不含显示屏¥28 |
HC-SR04 工作原理速览
HC-SR04 的工作流程很简单:
- 给 Trig 引脚至少 10μs 的高电平触发信号
- 模块自动发送 8 个 40kHz 超声波脉冲
- Echo 引脚输出高电平,持续时间 = 声波往返时间
- 距离 = (高电平时间 × 声速) / 2
关键点:声速不是常数,它随温度变化。
声速 (m/s) = 331.4 + 0.606 × 温度 (°C)
20°C 时声速约 343m/s,但 0°C 时只有 331m/s,相差 3.5%。对于 2 米测距,这就是 7cm 的误差。
基础代码:为什么官方示例不够用
先看 Arduino 官方示例代码:
const int trigPin = 9;
const int echoPin = 10;
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
float distance = duration * 0.034 / 2;
Serial.print("Distance: ");
Serial.println(distance);
delay(100);
}
这段代码有三个问题:
- 没有温度补偿 — 声速按固定值计算
- 没有滤波 — 单次测量容易受干扰
- 没有超时处理 — 超出量程时 pulseIn 会阻塞 1 秒
进阶方案一:温度补偿算法
加上 DS18B20 温度传感器,实时补偿声速:
#include
#include
#define ONE_WIRE_BUS 2
#define TRIG_PIN 9
#define ECHO_PIN 10
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
float getTemperature() {
sensors.requestTemperatures();
return sensors.getTempCByIndex(0);
}
float getSpeedOfSound(float temp) {
return 331.4 + 0.606 * temp; // m/s
}
float measureDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000); // 30ms 超时
if (duration == 0) return -1; // 超时
float temp = getTemperature();
float speed = getSpeedOfSound(temp);
float distance = (duration / 1000000.0) * speed / 2 * 100; // cm
return distance;
}
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
sensors.begin();
}
void loop() {
float distance = measureDistance();
if (distance > 0) {
Serial.printf("Distance: %.2f cm (temp: %.1f°C)\n", distance, getTemperature());
} else {
Serial.println("Out of range");
}
delay(200);
}
加上温度补偿后,2 米范围内的误差可以从±5cm 降低到±1cm。
进阶方案二:中值滤波 + 滑动平均
单次测量容易受环境噪声干扰。更好的做法是连续测量多次,取中值后再做滑动平均:
#define NUM_SAMPLES 5
#define MEDIAN_WINDOW 5
float readings[MEDIAN_WINDOW];
int readIndex = 0;
float compareFloats(const void* a, const void* b) {
float fa = *(const float*)a;
float fb = *(const float*)b;
return (fa > fb) - (fa < fb);
}
float getMedian(float* arr, int size) {
qsort(arr, size, sizeof(float), compareFloats);
if (size % 2 == 0) {
return (arr[size/2 - 1] + arr[size/2]) / 2;
}
return arr[size/2];
}
float filteredDistance() {
float samples[NUM_SAMPLES];
// 采集 5 个样本
for (int i = 0; i < NUM_SAMPLES; i++) {
samples[i] = measureDistance();
delay(10);
}
// 取中值
float median = getMedian(samples, NUM_SAMPLES);
// 滑动平均
readings[readIndex] = median;
readIndex = (readIndex + 1) % MEDIAN_WINDOW;
float sum = 0;
for (int i = 0; i < MEDIAN_WINDOW; i++) {
sum += readings[i];
}
return sum / MEDIAN_WINDOW;
}
这个滤波组合可以消除突发噪声,同时保持对真实距离变化的响应速度。
进阶方案三:多传感器融合
当单个 HC-SR04 不够用时,可以用多个模块组成立体测距系统。注意避免超声波互相干扰:
#define NUM_SENSORS 3
#define TRIG_PINS {9, 8, 7}
#define ECHO_PINS {10, 11, 12}
const int trigPins[NUM_SENSORS] = {9, 8, 7};
const int echoPins[NUM_SENSORS] = {10, 11, 12};
void setupSensors() {
for (int i = 0; i < NUM_SENSORS; i++) {
pinMode(trigPins[i], OUTPUT);
pinMode(echoPins[i], INPUT);
}
}
float measureSensor(int index) {
digitalWrite(trigPins[index], LOW);
delayMicroseconds(2);
digitalWrite(trigPins[index], HIGH);
delayMicroseconds(10);
digitalWrite(trigPins[index], LOW);
long duration = pulseIn(echoPins[index], HIGH, 30000);
if (duration == 0) return -1;
return duration * 0.034 / 2;
}
void loop() {
// 依次触发,避免干扰
for (int i = 0; i < NUM_SENSORS; i++) {
float distance = measureSensor(i);
Serial.printf("Sensor %d: %.2f cm\n", i, distance);
delay(50); // 等待声波消散
}
Serial.println("---");
delay(500);
}
关键技巧: 每个传感器触发后至少等待 50ms,让超声波完全消散再触发下一个。否则传感器 A 的 Echo 可能收到传感器 B 发出的超声波。
实战案例:智能小车避障系统
把 HC-SR04 装在小车上,实现自动避障:
#define LEFT_SENSOR 0
#define CENTER_SENSOR 1
#define RIGHT_SENSOR 2
#define THRESHOLD 20 // 20cm 需要避障
struct Motor {
int in1, in2, pwm;
};
Motor leftMotor = {3, 4, 5};
Motor rightMotor = {6, 7, 8};
void setupMotors() {
pinMode(leftMotor.in1, OUTPUT);
pinMode(leftMotor.in2, OUTPUT);
pinMode(leftMotor.pwm, OUTPUT);
pinMode(rightMotor.in1, OUTPUT);
pinMode(rightMotor.in2, OUTPUT);
pinMode(rightMotor.pwm, OUTPUT);
}
void moveForward(int speed) {
digitalWrite(leftMotor.in1, HIGH);
digitalWrite(leftMotor.in2, LOW);
analogWrite(leftMotor.pwm, speed);
digitalWrite(rightMotor.in1, HIGH);
digitalWrite(rightMotor.in2, LOW);
analogWrite(rightMotor.pwm, speed);
}
void turnLeft(int speed) {
digitalWrite(leftMotor.in1, LOW);
digitalWrite(leftMotor.in2, HIGH);
analogWrite(leftMotor.pwm, speed);
digitalWrite(rightMotor.in1, HIGH);
digitalWrite(rightMotor.in2, LOW);
analogWrite(rightMotor.pwm, speed);
}
void turnRight(int speed) {
digitalWrite(leftMotor.in1, HIGH);
digitalWrite(leftMotor.in2, LOW);
analogWrite(leftMotor.pwm, speed);
digitalWrite(rightMotor.in1, LOW);
digitalWrite(rightMotor.in2, HIGH);
analogWrite(rightMotor.pwm, speed);
}
void stopMotors() {
digitalWrite(leftMotor.in1, LOW);
digitalWrite(leftMotor.in2, LOW);
digitalWrite(rightMotor.in1, LOW);
digitalWrite(rightMotor.in2, LOW);
}
void avoidObstacle() {
float distances[NUM_SENSORS];
for (int i = 0; i < NUM_SENSORS; i++) {
distances[i] = measureSensor(i);
}
if (distances[CENTER_SENSOR] > 0 && distances[CENTER_SENSOR] < THRESHOLD) {
// 前方有障碍
if (distances[LEFT_SENSOR] > distances[RIGHT_SENSOR]) {
turnLeft(150);
delay(500);
} else {
turnRight(150);
delay(500);
}
} else {
moveForward(200);
}
delay(100);
}
void setup() {
Serial.begin(9600);
setupSensors();
setupMotors();
}
void loop() {
avoidObstacle();
}
常见问题排查
问题 1:读数一直为 0 或超小值
可能原因:
- 接线错误(Trig/Echo 接反)
- 供电不足(HC-SR04 需要 5V)
- 触发脉冲宽度不够(必须≥10μs)
解决: 用万用表检查电压,用示波器看 Trig 波形。
问题 2:读数在两个值之间跳变
可能原因:
- 被测物体表面不平整(声波散射)
- 环境噪声干扰
- 没有做滤波处理
解决: 加装中值滤波,或在目标物体上贴一层泡沫吸音。
问题 3:测量距离比实际短
可能原因:
- 没有温度补偿(低温环境)
- 传感器老化
解决: 加上 DS18B20 做温度补偿,或更换新模块。
问题 4:多个传感器互相干扰
可能原因:
- 同时触发多个传感器
- 触发间隔太短
解决: 依次触发,每个间隔至少 50ms。或者给每个传感器加隔音罩。
精度对比测试
| 方案 | 1 米误差 | 2 米误差 | 成本 |
|---|---|---|---|
| 官方示例(无补偿) | ±5cm | ±10cm | ¥5 |
| + 温度补偿 | ±2cm | ±4cm | ¥8 |
| + 中值滤波 | ±1.5cm | ±3cm | ¥8 |
| + 滑动平均 | ±1cm | ±2cm | ¥8 |
花 3 块钱加个温度传感器,精度提升 5 倍。
总结
HC-SR04 虽然便宜,但用对方法也能达到不错的精度。关键三点:
- 温度补偿 — 声速随温度变化,必须补偿
- 滤波算法 — 中值 + 滑动平均消除噪声
- 多传感器时序 — 依次触发避免干扰
希望这篇博客文章对您有所帮助!