三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于Arduino与HC-SR04的超声波测距报警与继电器控制系统

基于Arduino与HC-SR04的超声波测距报警与继电器控制系统

最近在做一个智能小车项目,需要实现自动避障功能,核心就是利用超声波传感器测量前方障碍物的距离。在调试过程中,我发现网上很多教程要么代码不完整,要么逻辑有缺陷,导致测距不准或响应不及时。特别是想实现一个“可设定报警距离,并自动控制继电器开关”的功能时,资料更是零散。本文将分享一套基于 Arduino 和 HC-SR04 超声波传感器的完整解决方案,从原理、接线、代码编写到功能扩展,手把手教你打造一个可自定义触发距离、并能联动继电器控制风扇、灯光等负载的自动化系统。无论你是电子爱好者、学生还是正在做智能家居原型的开发者,这套方案都能直接复用。

1. 项目背景与核心概念

1.1 什么是超声波测距?

超声波测距是一种非接触式的距离测量技术。它模仿蝙蝠的声呐系统,通过计算超声波从发射到遇到物体反射回来的时间差,结合声音在空气中的传播速度,来计算出传感器与物体之间的距离。其公式非常简单:距离 = (声速 × 时间差) / 2。除以2是因为声音走了一个来回。

在电子制作和自动化领域,超声波传感器因其成本低、精度相对较好、不受光线影响等优点,被广泛应用于机器人避障、液位检测、停车辅助、安防报警等场景。

1.2 HC-SR04 超声波传感器模块

本项目使用的 HC-SR04 模块是最常见、性价比最高的超声波传感器之一。它工作电压为5V,测量范围通常在2cm到400cm之间,测量精度可达3mm。模块上有四个引脚:

  • VCC: 接5V电源。
  • Trig (触发): 接收微控制器发送的脉冲信号,触发一次测距。
  • Echo (回响): 向微控制器输出一个高电平脉冲,其持续时间与测距时间成正比。
  • GND: 接地。

其工作流程是:微控制器向Trig引脚发送一个至少10微秒的高电平脉冲,模块内部会自动发射8个40kHz的超声波脉冲,并检测回波。当检测到回波后,Echo引脚会输出一个高电平,该高电平的持续时间就是超声波从发射到返回的时间。

1.3 项目目标:可设定距离的报警与负载控制

本项目不仅仅是简单的测距显示。我们的目标是构建一个智能化的测距控制系统,它具备以下功能:

  1. 实时测距与显示:持续测量并显示传感器与前方物体的距离。
  2. 可调报警阈值:用户可以通过按键或电位器设定一个报警距离阈值(例如30厘米)。
  3. 声光报警:当实测距离小于设定的报警阈值时,系统触发蜂鸣器报警和LED指示灯。
  4. 负载自动控制:在报警的同时,自动控制一个继电器模块,进而可以开关风扇、电机、灯具等大功率负载,实现自动化控制。
  5. 免编程(可选):对于不希望深入编程的用户,我们也探讨如何使用可编程逻辑控制器(如PLC逻辑模块)或简单的比较器电路来实现基础功能,但本文核心仍以Arduino编程实现为主,因为其灵活性强。

2. 硬件准备与电路连接

2.1 所需材料清单

在开始之前,请准备好以下硬件:

  • 主控板:Arduino Uno R3 (或其他兼容板,如 Nano、Mega) ×1
  • 超声波传感器:HC-SR04 模块 ×1
  • 显示设备:0.96寸 OLED I2C 显示屏 (或1602 LCD屏) ×1【用于显示距离和阈值】
  • 输入设备
    • 旋转编码器模块 (或电位器) ×1【用于无极调节报警阈值】
    • 按键 ×2【用于阈值增减,作为编码器的替代或补充】
  • 报警与指示设备
    • 有源蜂鸣器模块 (或无源蜂鸣器) ×1
    • LED (红色) ×1, 220Ω 电阻 ×1
  • 负载控制设备:5V 单路继电器模块 ×1
  • 负载示例:小风扇、LED灯带等 (工作电压需与继电器输出匹配)
  • 连接线:杜邦线 (公对公、公对母) 若干
  • 电源:USB数据线为Arduino供电,继电器控制的负载需单独供电。

2.2 电路接线图与说明

正确的接线是项目成功的第一步。请参照下表进行连接:

Arduino Uno 引脚连接的模块与引脚说明
5VHC-SR04 VCC, 继电器模块 VCC, 旋转编码器 VCC, OLED VCC提供5V电源。注意总电流不要超过板载稳压器限额。
GNDHC-SR04 GND, 继电器模块 GND, 旋转编码器 GND, OLED GND, 蜂鸣器 GND, LED阴极共地,所有模块的GND必须连接在一起。
D2HC-SR04 Trig数字输出,发送触发脉冲。
D3HC-SR04 Echo数字输入,接收回响脉冲。建议:此引脚通过一个1kΩ电阻连接到Echo,再并联一个2kΩ电阻下拉到GND,以稳定信号。
A4 (SDA)OLED SDAI2C数据线。
A5 (SCL)OLED SCLI2C时钟线。
D4旋转编码器 CLK (或 SW)编码器时钟引脚,用于检测旋转。
D5旋转编码器 DT (或按键A)编码器数据引脚。如果用按键,则接按键A用于增加阈值。
D6旋转编码器 SW (或按键B)编码器开关引脚(按下)。如果用按键,则接按键B用于减少阈值。
D7继电器模块 IN数字输出,高电平触发继电器吸合。
D8有源蜂鸣器模块 I/O数字输出,高电平触发蜂鸣器鸣响。
D9LED阳极 (通过220Ω电阻)数字输出,高电平点亮报警LED。

接线注意事项

  1. 电源:如果控制大功率负载(如12V风扇),务必为负载单独供电,切勿尝试从Arduino的5V引脚取电,否则可能损坏主板。继电器模块的输入侧(VCC, GND, IN)接Arduino,输出侧(COM, NO, NC)接负载和外部电源。
  2. 上拉/下拉电阻:Echo引脚和编码器/按键的输入引脚,在内部或外部配置上拉电阻,可以避免引脚悬空导致读数不稳定。Arduino内置上拉电阻,可以通过pinMode(pin, INPUT_PULLUP)启用。
  3. 引脚分配:上表引脚可自定义,只需在代码中同步修改即可。

3. 软件环境与库安装

3.1 Arduino IDE 配置

  1. 前往 Arduino 官网下载并安装最新版 Arduino IDE。
  2. 安装完成后,打开IDE,确保板卡类型选择正确:工具->开发板->Arduino AVR Boards->Arduino Uno
  3. 选择正确的端口:工具->端口,选择对应的COM口(如COM3, COM4等)。

3.2 第三方库安装

本项目需要用到OLED显示库,推荐使用功能强大的Adafruit_SSD1306Adafruit_GFX库。

  1. 在Arduino IDE中,点击项目->加载库->管理库...
  2. 在库管理器中搜索Adafruit SSD1306,找到后点击安装。
  3. 搜索Adafruit GFX Library,同样点击安装。安装SSD1306时可能会提示你安装GFX库,按提示操作即可。
  4. (可选)如果你使用旋转编码器,可以搜索并安装Encoder库,它提供了更稳健的编码器读数处理。

4. 核心代码实现与分步讲解

我们将代码分为几个功能模块进行讲解,最后整合成完整程序。

4.1 超声波测距函数

这是项目的核心测量功能。我们需要编写一个函数,负责触发HC-SR04并计算返回的距离。

// 定义超声波引脚 const int trigPin = 2; const int echoPin = 3; // 初始化超声波引脚 void setupUltrasonic() { pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(trigPin, LOW); // 初始保持低电平 delayMicroseconds(2); } // 测量一次距离的函数,返回单位为厘米(cm) float measureDistance() { // 发送一个10微秒的高脉冲触发测距 digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // 读取 Echo 引脚的高电平持续时间(单位:微秒) // pulseIn函数会等待引脚变为高电平,开始计时,再变回低电平时停止计时。 long duration = pulseIn(echoPin, HIGH, 30000); // 设置超时30ms,对应约5米距离 // 计算距离(厘米) // 声速在空气中约340m/s,即 0.034 cm/微秒。距离 = (时间 * 声速) / 2 float distance = duration * 0.034 / 2.0; // 如果超时或距离异常,返回一个错误值(如-1) if (distance <= 0 || distance > 400) { return -1.0; } return distance; }

代码关键点解释

  • pulseIn(pin, value, timeout): 这是一个非常实用的函数,用于测量指定引脚上脉冲的持续时间。第三个参数timeout(单位微秒)很重要,它设定了等待脉冲的最大时间,避免程序卡死。30ms对应大约5米的测量上限,符合HC-SR04的规格。
  • 声速校准:公式中的0.034是近似值。声速受温度影响,更精确的公式是331.4 + 0.6 * temp(单位:m/s)。如果对精度要求高,可以加入温湿度传感器进行补偿。
  • 错误处理:返回-1用于标识测量失败,后续显示或逻辑判断时可以过滤掉这些无效值。

4.2 OLED显示功能

我们将实时距离和设定的报警阈值显示在OLED屏幕上。

#include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setupDisplay() { if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C地址通常是0x3C或0x3D Serial.println(F("SSD1306 allocation failed")); for(;;); // 初始化失败,死循环 } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println("Ultrasonic System"); display.display(); delay(2000); } void updateDisplay(float currentDist, float alarmThreshold) { display.clearDisplay(); display.setCursor(0, 0); display.setTextSize(1); display.print("Distance: "); display.setTextSize(2); display.setCursor(0, 10); if(currentDist < 0) { display.print("Error"); } else { display.print(currentDist, 1); // 显示一位小数 display.print(" cm"); } display.setTextSize(1); display.setCursor(0, 35); display.print("Alarm Threshold:"); display.setCursor(0, 45); display.setTextSize(2); display.print(alarmThreshold, 0); // 阈值显示为整数 display.print(" cm"); // 如果当前距离小于阈值,在屏幕下方显示警告信息 if(currentDist > 0 && currentDist < alarmThreshold) { display.setTextSize(1); display.setCursor(0, 55); display.print("!! TOO CLOSE !!"); } display.display(); }

4.3 报警阈值设置(使用旋转编码器)

旋转编码器可以平滑地增加或减少报警阈值。

// 使用简单的引脚状态检测编码器(库版本更佳,此处展示原理) const int encoderCLK = 4; const int encoderDT = 5; const int encoderSW = 6; int lastCLKState = HIGH; float alarmThreshold = 30.0; // 默认报警阈值30厘米 int minThreshold = 5; int maxThreshold = 200; void setupEncoder() { pinMode(encoderCLK, INPUT_PULLUP); pinMode(encoderDT, INPUT_PULLUP); pinMode(encoderSW, INPUT_PULLUP); } void readEncoder() { int currentCLKState = digitalRead(encoderCLK); // 检测CLK引脚的下落沿(从HIGH到LOW) if (currentCLKState != lastCLKState && currentCLKState == LOW) { // 根据DT引脚的状态判断旋转方向 if (digitalRead(encoderDT) != currentCLKState) { // 顺时针,增加阈值 alarmThreshold += 5.0; } else { // 逆时针,减少阈值 alarmThreshold -= 5.0; } // 限制阈值在合理范围内 alarmThreshold = constrain(alarmThreshold, minThreshold, maxThreshold); } lastCLKState = currentCLKState; // 检测按键是否被按下(重置阈值到默认值) if (digitalRead(encoderSW) == LOW) { delay(50); // 简单消抖 if (digitalRead(encoderSW) == LOW) { alarmThreshold = 30.0; while(digitalRead(encoderSW) == LOW); // 等待按键释放 } } }

4.4 报警与负载控制逻辑

这是系统的决策核心,根据测量距离和设定阈值,控制蜂鸣器、LED和继电器。

const int relayPin = 7; const int buzzerPin = 8; const int ledPin = 9; bool alarmState = false; void setupOutputs() { pinMode(relayPin, OUTPUT); pinMode(buzzerPin, OUTPUT); pinMode(ledPin, OUTPUT); digitalWrite(relayPin, LOW); // 继电器初始断开 digitalWrite(buzzerPin, LOW); digitalWrite(ledPin, LOW); } void controlAlarmAndLoad(float currentDist) { if (currentDist > 0 && currentDist < alarmThreshold) { // 触发报警 alarmState = true; digitalWrite(ledPin, HIGH); // 点亮LED digitalWrite(buzzerPin, HIGH); // 蜂鸣器响(有源蜂鸣器) digitalWrite(relayPin, HIGH); // 继电器吸合,接通负载 // 如果是无源蜂鸣器,这里需要用tone()函数产生特定频率的声音 // tone(buzzerPin, 1000); // 1kHz频率 } else { // 关闭报警 alarmState = false; digitalWrite(ledPin, LOW); digitalWrite(buzzerPin, LOW); digitalWrite(relayPin, LOW); // noTone(buzzerPin); // 如果是无源蜂鸣器 } }

4.5 完整代码整合与主循环

将以上所有模块整合,并编写setup()loop()函数。

#include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h> // 引脚定义 #define TRIG_PIN 2 #define ECHO_PIN 3 #define ENC_CLK 4 #define ENC_DT 5 #define ENC_SW 6 #define RELAY_PIN 7 #define BUZZER_PIN 8 #define LED_PIN 9 // OLED定义 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // 全局变量 float alarmThreshold = 30.0; int lastCLKState = HIGH; unsigned long lastMeasureTime = 0; const long measureInterval = 200; // 测量间隔200ms void setup() { Serial.begin(9600); // 初始化各模块 pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); digitalWrite(TRIG_PIN, LOW); pinMode(ENC_CLK, INPUT_PULLUP); pinMode(ENC_DT, INPUT_PULLUP); pinMode(ENC_SW, INPUT_PULLUP); pinMode(RELAY_PIN, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(LED_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); digitalWrite(BUZZER_PIN, LOW); digitalWrite(LED_PIN, LOW); // 初始化OLED if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("OLED init fail")); for(;;); } display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.display(); delay(1000); } void loop() { // 1. 读取编码器,更新阈值 readEncoder(); // 2. 定时测量距离(非阻塞式,避免delay卡死程序) unsigned long currentMillis = millis(); if (currentMillis - lastMeasureTime >= measureInterval) { lastMeasureTime = currentMillis; float distance = measureDistance(); // 3. 根据距离和阈值控制报警与负载 controlAlarmAndLoad(distance); // 4. 更新OLED显示 updateDisplay(distance, alarmThreshold); // 5. (可选)串口打印调试信息 Serial.print("Distance: "); Serial.print(distance); Serial.print(" cm, Threshold: "); Serial.print(alarmThreshold); Serial.print(" cm, Alarm: "); Serial.println(alarmState ? "ON" : "OFF"); } } // --- 以下是前面定义过的函数,需放在此处 --- float measureDistance() { /* 同上 */ } void readEncoder() { /* 同上 */ } void controlAlarmAndLoad(float dist) { /* 同上 */ } void updateDisplay(float dist, float threshold) { /* 同上 */ }

5. 系统调试与常见问题排查

将代码上传到Arduino后,系统可能不会立即完美工作。以下是常见问题及解决方法。

问题现象可能原因排查与解决思路
OLED屏幕不亮或白屏1. I2C地址错误。
2. 接线松动(SDA, SCL)。
3. 未正确初始化库。
1. 用I2C扫描程序(Arduino IDE示例中有)确认OLED地址是0x3C还是0x3D,并修改代码。
2. 检查接线,确保SDA接A4,SCL接A5。
3. 确认已安装Adafruit_SSD1306Adafruit_GFX库。
距离显示为固定值或“Error”1. Echo引脚信号不稳定。
2. 测量物体超出范围或表面不反射超声波。
3.pulseIn超时设置过短。
1. 为Echo引脚添加下拉电阻(如2kΩ到GND)。
2. 确保测量物体在2-400cm内,且表面非海绵等吸音材料。
3. 检查pulseIn的超时参数,确保大于最大测量距离所需时间(400cm约需23ms)。
旋转编码器调节不灵敏或跳变1. 机械编码器抖动。
2. 代码消抖逻辑不完善。
1. 使用Encoder库替代手动检测,它内部有硬件消抖处理。
2. 在手动检测代码中增加更严格的消抖判断,例如检测到变化后延时几毫秒再确认状态。
继电器频繁开关或状态不对1. 距离测量波动大。
2. 继电器模块触发方式理解错误。
1. 对距离值进行软件滤波(如取最近N次测量的平均值)。
2. 确认你的继电器模块是高电平触发还是低电平触发,修改digitalWrite(RELAY_PIN, HIGH/LOW)
蜂鸣器不响或常响1. 有源/无源蜂鸣器类型用错。
2. 引脚驱动能力不足。
1.有源蜂鸣器:给高电平就响,用digitalWrite
无源蜂鸣器:需要PWM频率驱动,用tone()函数。
2. 蜂鸣器电流可能较大,尝试通过一个三极管或MOS管来驱动,而非直接接单片机引脚。
系统运行一段时间后复位1. 电源功率不足。
2. 继电器通断引起电源波动。
1. 确保Arduino使用稳定的电源适配器(9V-12V),而非仅靠USB供电,特别是继电器吸合时电流较大。
2. 在继电器线圈两端并联一个续流二极管(如1N4007),阴极接VCC,阳极接控制引脚,以吸收反向电动势。

6. 功能扩展与最佳实践

一个基础系统搭建完成后,可以考虑以下优化和扩展方向,使其更稳定、更智能。

6.1 软件滤波与数据平滑

超声波传感器容易受到环境噪声干扰,导致单次测量值跳动。采用滑动平均滤波可以显著提升读数稳定性。

#define FILTER_SIZE 5 // 滤波窗口大小 float distanceBuffer[FILTER_SIZE]; int bufferIndex = 0; float getFilteredDistance() { float rawDist = measureDistance(); if(rawDist > 0) { // 只将有效数据加入缓冲区 distanceBuffer[bufferIndex] = rawDist; bufferIndex = (bufferIndex + 1) % FILTER_SIZE; } // 计算平均值 float sum = 0; int count = 0; for(int i=0; i<FILTER_SIZE; i++) { if(distanceBuffer[i] > 0) { sum += distanceBuffer[i]; count++; } } return (count > 0) ? (sum / count) : -1; } // 在loop()中,使用 getFilteredDistance() 代替 measureDistance()

6.2 增加报警延时与 hysteresis(迟滞)

防止物体在阈值边缘晃动导致继电器和蜂鸣器频繁动作,这对于控制电机等设备尤为重要。

#define HYSTERESIS 2.0 // 迟滞范围2厘米 bool lastAlarmState = false; void controlAlarmAndLoadAdvanced(float currentDist) { bool shouldAlarm = (currentDist > 0 && currentDist < alarmThreshold); // 应用迟滞:只有当距离越过“阈值 - 迟滞”时才触发,越过“阈值 + 迟滞”时才解除 if(shouldAlarm && !lastAlarmState && currentDist < (alarmThreshold - HYSTERESIS)) { lastAlarmState = true; } else if(!shouldAlarm && lastAlarmState && currentDist > (alarmThreshold + HYSTERESIS)) { lastAlarmState = false; } // 根据最终状态控制输出 if(lastAlarmState) { digitalWrite(ledPin, HIGH); digitalWrite(buzzerPin, HIGH); digitalWrite(relayPin, HIGH); } else { digitalWrite(ledPin, LOW); digitalWrite(buzzerPin, LOW); digitalWrite(relayPin, LOW); } }

6.3 多种控制模式扩展

你可以通过增加一个模式切换开关,让系统拥有更多功能。

  • 模式1:即时报警模式(当前模式)。
  • 模式2:区间报警模式(物体进入和离开特定区域报警)。
  • 模式3:数据记录模式(通过SD卡模块定期记录距离数据)。
  • 模式4:跟随模式(通过舵机云台,使传感器始终对准最近物体)。

6.4 生产环境注意事项

如果打算将这个系统用于更严肃的场合,需要考虑以下几点:

  1. 电源隔离:控制端(Arduino)和被控负载(如220V交流电机)的电源必须物理隔离,通常使用光耦或隔离继电器模块,确保安全。
  2. 外壳与防护:为电路制作一个绝缘外壳,避免短路。超声波传感器探头保持清洁。
  3. 看门狗定时器:在代码中启用Arduino的内部看门狗(#include <avr/wdt.h>),防止程序跑飞导致系统死机。
  4. 参数存储:使用EEPROM存储用户设定的报警阈值,这样即使断电重启,设置也不会丢失。
    #include <EEPROM.h> void saveThreshold(float th) { EEPROM.put(0, th); } float loadThreshold() { float th; EEPROM.get(0, th); return (isnan(th))?30.0:th; }
  5. 抗干扰设计:信号线(如Trig, Echo)尽量短,或使用屏蔽线。在电源入口处增加滤波电容(如100uF电解并联0.1uF瓷片电容)。

7. 项目总结与学习路线

通过这个“超声波测距报警与控制负载”项目,我们不仅学会了HC-SR04的使用,更掌握了一个完整嵌入式系统的构建流程:传感器数据采集 -> 信号处理与滤波 -> 人机交互(显示与输入)-> 逻辑判断 -> 执行器控制(继电器)。这是物联网和自动化设备的通用模型。

你可以在此基础上继续深化:

  • 通信联网:加入Wi-Fi模块(如ESP8266/ESP32),将距离数据和报警状态上传到云平台(如阿里云、ThingsBoard),实现远程监控。
  • 多传感器融合:增加一个红外避障传感器,与超声波数据互补,提高检测可靠性。
  • 可视化界面:用Processing或Python的Tkinter编写一个电脑上的上位机,通过串口接收数据并绘制实时距离曲线。
  • 机械结构:将整个系统安装到小车或舵机云台上,制作一个自动跟踪或避障机器人。

硬件编程的魅力在于软硬结合,从代码到物理世界的反馈是即时的。希望这份详细的教程能成为你探索自动化世界的一块坚实跳板。动手过程中遇到的任何问题,欢迎在社区交流,往往解决问题的过程就是收获最大的时刻。

← 返回列表