1. 项目概述与核心思路
最近在整理工作室的旧项目时,翻出了一个几年前做的“社交距离警示器”原型。当时正值特殊时期,大家对于保持物理距离格外关注。市面上虽然有一些成品设备,但要么价格不菲,要么功能单一。作为一个喜欢动手的硬件爱好者,我决定用手头最常见的Arduino开发板、一个超声波传感器和几个LED灯,自己做一个既直观又实用的距离警示装置。
这个装置的核心逻辑非常简单:它就像一个无声的“电子哨兵”,持续监测前方一定范围内的障碍物(比如人)。当有物体进入“安全区”边缘时,绿灯会亮起作为温和提醒;随着物体继续靠近,黄灯亮起,表示距离正在缩短;一旦物体进入预设的“危险距离”内,红灯就会亮起并可能伴随闪烁,发出明确的“请保持距离”警告。整个系统成本极低,核心部件加起来可能不到50元,但实现的效果却非常直观有效,非常适合放在办公桌隔断、商店柜台、图书馆自习区等半固定场所使用。
我选择Arduino Leonardo作为主控,一方面是因为它价格适中、性能足够,另一方面是它兼容性极佳,有丰富的社区资源和库支持。传感器方面,HC-SR04超声波模块是经典之选,它非接触式测量、成本低、精度对于这个应用场景(厘米级)完全足够。显示部分,我没有使用复杂的屏幕,而是采用了最直接的红、黄、绿三色LED灯,通过点亮不同数量和颜色的灯来传递信息,这种设计无需文字说明,任何人都能一眼看懂状态,符合“警示器”简单直接的设计初衷。
2. 核心器件选型与原理剖析
2.1 主控单元:为什么是Arduino Leonardo?
在众多Arduino板卡中,我选择了Leonardo。可能有人会问,用更便宜的Uno或者更强大的Mega不行吗?当然可以,但Leonardo有几个独特的优势让它成为这个项目的“甜点”选择。
首先,USB通信协议。Arduino Uno/Nano使用的是USB转串口芯片(如CH340、FT232),而Leonardo(以及Micro、Due)原生集成了USB功能,其ATmega32U4微控制器本身就能被电脑识别为USB设备。这意味着Leonardo可以模拟键盘、鼠标、游戏手柄等HID设备。虽然在这个警示器项目中我们暂时用不到这个高级功能,但它为未来升级留下了巨大空间——比如,当检测到有人持续闯入危险距离时,可以让Leonardo模拟键盘按下“静音”键,自动将视频会议静音,这个想法是不是很酷?
其次,引脚资源与功耗。Leonardo提供了20个数字I/O引脚(其中7个支持PWM)和12个模拟输入引脚,对于驱动几个LED和一个传感器绰绰有余,且仍有大量余量用于扩展。它的功耗也比一些高性能板卡要低,适合长期插电运行。最后是性价比,它的价格通常只比Uno略高一点,但获得了更现代的USB功能和更好的扩展性,属于“加量不加价”。
注意:如果你手头只有Arduino Uno,完全没问题,本项目代码和接线方式与Uno完全兼容。Leonardo的优势更多体现在未来功能扩展的潜力上。
2.2 感知核心:HC-SR04超声波测距模块详解
HC-SR04几乎是电子爱好者入门测距的首选模块,价格通常在5元以内。它的工作原理是超声波渡越时间法。模块上有两个圆柱形“眼睛”,一个是超声波发射器(T),一个是接收器(R)。
其工作流程如下:
- 主控给Trig引脚一个至少10微秒的高电平脉冲,触发模块发射一束8个40kHz的超声波。
- 超声波在空气中传播,遇到障碍物后反射回来。
- 模块的接收器检测到回波,Echo引脚会输出一个高电平脉冲。
- 这个高电平脉冲的持续时间,正好等于超声波从发射到返回所经历的时间。
那么距离怎么算?我们知道,在空气中,声速约为340米/秒(随温度变化,但常温下可近似)。距离等于速度乘以时间。但要注意,Echo引脚的高电平时间是超声波“往返”一趟的时间,所以单程距离需要除以2。
计算公式为:距离 = (高电平时间 × 声速) / 2
在代码中,声速通常取34000厘米/秒(换算单位)。高电平时间由pulseIn()函数读取,单位是微秒(百万分之一秒)。所以最终计算为:距离(厘米) = (高电平时间(微秒) * 0.034) / 2,简化后就是距离(厘米) = 高电平时间(微秒) * 0.017。
例如,如果pulseIn()读到2941微秒,那么距离 ≈ 2941 * 0.017 ≈ 50厘米。
实操心得:HC-SR04的测量角度约为15度,属于小角度探测,指向性较好,但这也意味着需要将它对准你想要监测的区域。它的有效测距是2cm到400cm,但超过200cm后回波信号变弱,精度和稳定性会下降。对于社交距离警示,我们通常关注50cm到200cm的范围,正好是它的最佳工作区间。
2.3 警示输出:多色LED阵列的驱动策略
为什么用多个LED而不是一个RGB LED?这里主要考虑的是可视角度和警示效果。一个RGB LED虽然能变色,但它的发光点小,可视角度有限,且亮度可能不足。而使用多个离散的LED(比如2绿、3黄、2红),可以将它们排列成一条直线或弧形,模拟“信号强度条”的效果,视觉上更直观,也从不同角度都更容易看到。
我们需要决定如何驱动这7个LED。最简单的方法是为每个LED分配一个独立的I/O口,但这会占用7个引脚。另一种方法是使用移位寄存器(如74HC595),用3个引脚控制无限多个LED。但对于只有7个灯的项目,为了简化,我选择了直接驱动,因为Leonardo的引脚足够用。
限流电阻的计算是关键一步。Arduino的I/O口输出电压是5V,单个LED的典型正向压降(Vf)根据不同颜色而不同:红光约1.8-2.2V,黄/绿光约2.0-2.4V。I/O口最大安全输出电流建议不超过20mA,我们通常设计在10-15mA以获得良好亮度且安全。
以红色LED(Vf=2.0V)为例,计算限流电阻:R = (Vcc - Vf) / I。Vcc是5V,I取15mA(0.015A)。则R = (5 - 2.0) / 0.015 = 3.0 / 0.015 = 200欧姆。我们可以取一个接近的标准值,如220欧姆。
重要提示:务必为每个LED串联一个独立的限流电阻!绝对不能将多个LED并联后共用一个电阻。因为即使同一批次的LED,其正向压降也有微小差异,并联会导致压降小的那个LED流过更大电流,从而过热烧毁。这是新手最容易犯的硬件错误之一。
3. 硬件电路设计与搭建详解
3.1 电路连接图与接线表
根据上面的选型,我们来规划具体的连接方式。为了清晰起见,我建议将LED按颜色分组,并排列成一条线,这样点亮时从绿到红的变化就像一道“距离光栅”。
元件清单:
- Arduino Leonardo x1
- HC-SR04超声波模块 x1
- 5mm LED,绿色 x2,黄色 x3,红色 x2
- 220欧姆电阻 x7(用于LED限流)
- 1k欧姆电阻 x1(可选,用于Echo引脚上拉,部分模块不需要)
- 面包板、杜邦线若干
- 外接电源(如9V电池适配器)或USB供电
接线示意图(文字描述):
超声波模块连接:
- HC-SR04 VCC → Arduino 5V
- HC-SR04 GND → Arduino GND
- HC-SR04 Trig → Arduino 数字引脚 9
- HC-SR04 Echo → Arduino 数字引脚 10
LED阵列连接(假设排列顺序为:绿1,绿2,黄1,黄2,黄3,红1,红2):
- 绿色LED1 阳极(长脚)→ 串联220Ω电阻 → Arduino 数字引脚 2
- 绿色LED2 阳极 → 串联220Ω电阻 → Arduino 数字引脚 3
- 黄色LED1 阳极 → 串联220Ω电阻 → Arduino 数字引脚 4
- 黄色LED2 阳极 → 串联220Ω电阻 → Arduino 数字引脚 5
- 黄色LED3 阳极 → 串联220Ω电阻 → Arduino 数字引脚 6
- 红色LED1 阳极 → 串联220Ω电阻 → Arduino 数字引脚 7
- 红色LED2 阳极 → 串联220Ω电阻 → Arduino 数字引脚 8
- 所有LED的阴极(短脚)→ 统一连接到 Arduino GND(或面包板的负电源轨)。
为了方便查阅,我将关键连接关系整理成下表:
| 元件 | 引脚/端 | 连接到 Arduino | 说明 |
|---|---|---|---|
| HC-SR04 | VCC | 5V | 电源正极 |
| GND | GND | 电源地 | |
| Trig | 数字引脚 9 | 触发测距信号 | |
| Echo | 数字引脚 10 | 接收回波信号 | |
| 绿色LED1 | 阳极 (+) | 数字引脚 2 (经220Ω) | 第一级警示 |
| 绿色LED2 | 阳极 (+) | 数字引脚 3 (经220Ω) | 第二级警示 |
| 黄色LED1 | 阳极 (+) | 数字引脚 4 (经220Ω) | 第三级警示 |
| 黄色LED2 | 阳极 (+) | 数字引脚 5 (经220Ω) | 第四级警示 |
| 黄色LED3 | 阳极 (+) | 数字引脚 6 (经220Ω) | 第五级警示 |
| 红色LED1 | 阳极 (+) | 数字引脚 7 (经220Ω) | 强烈警示 |
| 红色LED2 | 阳极 (+) | 数字引脚 8 (经220Ω) | 强烈警示(可闪烁) |
| 所有LED | 阴极 (-) | GND | 共地连接 |
3.2 搭建步骤与工艺要点
在面包板上搭建这个电路是学习的好方法,但如果你希望它成为一个能长期稳定工作的设备,我强烈建议进行焊接。下面是我的分步搭建流程:
第一步:规划布局。在面包板或万用板上,先不要急着插元件。用笔简单画一下布局:把Arduino放在一侧,超声波模块预计安装位置(通常要伸出外壳)附近留出空间,7个LED计划排成一排的位置。电源走线(5V和GND)尽量规划成主干道,避免飞线杂乱。
第二步:焊接电源轨。如果使用万用板,先焊接一条5V电源线和一条GND线贯穿板子,作为公共电源总线。这能极大简化后续连接。
第三步:固定并连接LED与电阻。这是最需要耐心的一步。将7个220欧姆电阻焊接到万用板上,电阻的一端预留连接至Arduino引脚。然后将每个LED的阳极(长脚)弯曲,焊接到对应电阻的空余端。务必确保LED的极性正确。最后,将所有LED的阴极(短脚)用导线并联焊接在一起,并引出一根线连接到公共GND总线。
第四步:连接传感器与控制线。将HC-SR04模块通过排针焊接到万用板上,或者直接用杜邦线引出。将其VCC和GND连接到电源总线。Trig和Echo信号线则用较细的导线连接到Arduino对应的引脚(9和10)。信号线建议不要太长,如果超过20cm,可以考虑用双绞线以减少干扰。
第五步:外壳设计与安装。一个得体的外壳能让项目从“实验品”升级为“产品”。我用的是亚克力板激光切割制作的外壳。关键点在于:
- 为超声波传感器的发射/接收头开两个大小合适的圆孔。
- 为7个LED开一排整齐的圆孔或方孔。如果想让光线更柔和,可以在LED和外壳之间加一层半透明的磨砂塑料片作为匀光板。
- 预留Arduino的USB口和电源开关的开孔。
- 考虑散热,如果长时间工作,避免完全密封。
将所有部件固定到外壳内,可以使用螺丝、热熔胶或3D打印的固定座。确保超声波传感器正面朝外,且前方无遮挡。
4. 软件程序编写与逻辑解析
硬件是躯体,软件才是灵魂。下面我们来编写让警示器“活”起来的Arduino程序,并逐段解析其逻辑。
4.1 核心测距函数与距离滤波
首先,我们需要一个稳定、可靠的函数来读取超声波传感器的距离值。原始读数可能会有跳动,因此加入简单的软件滤波。
// 定义引脚 const int trigPin = 9; const int echoPin = 10; const int ledPins[] = {2, 3, 4, 5, 6, 7, 8}; // 7个LED对应的引脚 const int numLeds = 7; // 距离阈值定义(单位:厘米) const int greenThreshold = 150; // 小于此值,第一盏绿灯亮 const int yellowThreshold = 100; // 小于此值,黄灯开始亮 const int redThreshold = 50; // 小于此值,红灯亮 long readDistance() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); // 发送10微秒的高脉冲触发 digitalWrite(trigPin, LOW); // 读取回波高电平持续时间(单位:微秒) long duration = pulseIn(echoPin, HIGH, 30000); // 超时设置为30000微秒(约5米) // 计算距离(单位:厘米),声速取340m/s,公式:距离 = (时间 * 0.034) / 2 long distance = duration * 0.017; // 简单的异常值过滤:如果距离为0或大于400,则返回上一次的有效值 static long lastValidDistance = 400; if (distance > 2 && distance < 400) { // HC-SR04有效范围约为2-400cm lastValidDistance = distance; } return lastValidDistance; }代码逻辑解读:
pulseIn(echoPin, HIGH, 30000):这个函数会等待echoPin变为高电平,并开始计时,直到其变回低电平,返回持续的微秒数。第三个参数30000是超时时间(微秒),如果超过这个时间还没收到回波,函数会返回0。这对应着测量超出量程(约5米)。distance * 0.017:这是由公式距离 = (时间 * 声速) / 2推导而来。声速34000 cm/s,除以1000000(微秒转秒),再除以2,得到系数约0.017。- 软件滤波:我们用一个静态变量
lastValidDistance来保存上一次的有效读数。如果本次读数在合理范围内(2-400cm),就更新它;如果读数异常(比如0或极大值),就返回上一次的有效值。这能有效消除偶尔出现的跳变或误触发,使显示更稳定。
4.2 多级LED显示控制逻辑
接下来是核心的控制逻辑:根据测得的距离,决定哪些LED该亮起。我们希望实现一个渐进式的效果:物体越近,点亮的LED越多,颜色从绿变红。
void updateLeds(long distance) { // 首先,关闭所有LED for (int i = 0; i < numLeds; i++) { digitalWrite(ledPins[i], LOW); } // 根据距离决定点亮哪些LED if (distance < redThreshold) { // 危险距离:点亮所有红灯(最后两个LED),并让其中一个闪烁 digitalWrite(ledPins[5], HIGH); // 红灯1常亮 // 红灯2闪烁 static unsigned long lastBlinkTime = 0; static bool red2State = false; if (millis() - lastBlinkTime > 500) { // 500ms闪烁周期 lastBlinkTime = millis(); red2State = !red2State; digitalWrite(ledPins[6], red2State ? HIGH : LOW); } // 同时,黄灯也全亮,表示已从黄区进入红区 for (int i = 2; i <= 4; i++) { // 引脚4,5,6对应三个黄灯 digitalWrite(ledPins[i], HIGH); } // 绿灯也亮,表示整个警示序列已满 digitalWrite(ledPins[0], HIGH); digitalWrite(ledPins[1], HIGH); } else if (distance < yellowThreshold) { // 警告距离:点亮所有黄灯和绿灯 int ledsToLight = map(distance, redThreshold, yellowThreshold, numLeds, 3); ledsToLight = constrain(ledsToLight, 3, numLeds - 1); for (int i = 0; i < ledsToLight; i++) { digitalWrite(ledPins[i], HIGH); } } else if (distance < greenThreshold) { // 提醒距离:只点亮部分或全部绿灯 int ledsToLight = map(distance, yellowThreshold, greenThreshold, 2, 0); ledsToLight = constrain(ledsToLight, 0, 2); for (int i = 0; i < ledsToLight; i++) { digitalWrite(ledPins[i], HIGH); } } else { // 安全距离:所有LED熄灭,或仅点亮一盏绿灯表示设备正常 // digitalWrite(ledPins[0], HIGH); // 可选:设备待机指示 } }逻辑深度解析:
- 状态清除:每次更新显示前,先关闭所有LED,这是一个好习惯,避免状态残留。
- 红区(危险)处理:当距离小于
redThreshold(50cm)时,我们不仅点亮所有红灯,还让其中一个红灯闪烁(500ms周期)。闪烁是一种更强的视觉警示。同时,我们也点亮了所有黄灯和绿灯,形成“满格”报警的效果,视觉冲击力最强。 - 黄区(警告)处理:这是最体现“渐进”效果的部分。我们使用Arduino的
map()函数,将距离值映射到要点亮的LED数量上。例如,当距离刚好等于yellowThreshold(100cm)时,我们希望点亮3个LED(2绿1黄)。当距离无限接近redThreshold(50cm)时,我们希望点亮6个LED(2绿3黄1红)。map()函数帮我们做了这个线性映射,constrain()函数则确保结果不会超出LED索引范围。 - 绿区(提醒)处理:同理,在绿区(100cm到150cm),我们将距离映射到0到2之间,即只点亮0盏、1盏或2盏绿灯。距离越近,点亮的绿灯越多。
- 安全区:在150cm以外,我们可以选择让所有灯熄灭,或者让一盏绿灯常亮作为设备电源指示。
4.3 主循环与整体程序框架
最后,将以上函数整合到setup()和loop()中,形成完整的程序。
void setup() { // 初始化串口,用于调试(可选) Serial.begin(9600); // 初始化超声波传感器引脚 pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); // 初始化所有LED引脚为输出模式 for (int i = 0; i < numLeds; i++) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); // 初始状态为熄灭 } Serial.println("Social Distance Warner Started!"); } void loop() { // 1. 读取距离 long distance = readDistance(); // 2. 通过串口打印距离值,便于调试(完成后可注释掉) Serial.print("Distance: "); Serial.print(distance); Serial.println(" cm"); // 3. 根据距离更新LED显示状态 updateLeds(distance); // 4. 添加一个短暂的延迟,避免刷新过快导致LED闪烁或传感器误触发 // 延迟时间决定了测距的频率。100ms是一个平衡点,既不会太慢,也给传感器留出处理时间。 delay(100); }程序优化提示:
loop()中的delay(100)会阻塞程序。如果你希望设备同时能响应其他事件(比如增加一个按钮来切换模式),可以考虑使用非阻塞的定时方法,例如用millis()来管理定时,这样主循环就不会被delay卡住。但对于这个简单应用,delay(100)(每秒测量10次)完全足够。
5. 校准、调试与功能扩展
5.1 系统校准与阈值调整
硬件搭建和程序烧录完成后,第一件事就是校准。你可能会发现实测距离与理论值有细微偏差,或者希望调整警示的敏感度。
校准步骤:
- 将程序中的串口调试功能打开(确保
Serial.begin(9600)和Serial.print语句未被注释)。 - 打开Arduino IDE的串口监视器(波特率设为9600)。
- 在传感器前方放置一个物体,用卷尺精确测量物体到传感器表面的距离(例如50.0cm)。
- 观察串口监视器打印出的距离值。它可能显示为52cm或48cm。
- 计算偏差。如果 consistently 偏大2cm,我们可以在
readDistance()函数的返回语句中进行修正:return lastValidDistance - 2;。更科学的做法是修改计算系数0.017。例如,实测50cm对应29000微秒,那么实际系数 = 50.0 / 29000 ≈ 0.001724,乘以1000(因为公式是厘米)得到0.01724。将程序中的0.017替换为0.01724即可。
阈值调整建议:
greenThreshold:世界卫生组织建议的社交距离是至少1米。我们可以将第一级提醒设置为1.5米(150cm),给予一个缓冲。yellowThreshold:设置为1米(100cm),达到正式建议的社交距离底线时给出明确警告。redThreshold:设置为0.5米(50cm),这是一个非常近的个人距离,需要强烈警告。 你可以根据实际应用场景(如办公桌间距、排队间隔)灵活调整这些值。
5.2 常见问题排查速查表
制作过程中,你可能会遇到一些问题。下表列出了常见故障现象、可能原因及解决方法:
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| 所有LED不亮 | 1. 电源未接通或接反。 2. Arduino未正确供电或程序未上传。 3. 公共GND线未连接。 | 1. 检查USB线或外接电源,用万用表测量5V和GND间电压。 2. 重新上传Blink示例程序,测试Arduino本身是否正常。 3. 检查所有LED阴极是否都接到了GND。 |
| 只有部分LED亮,或亮度异常 | 1. LED或电阻虚焊、焊反。 2. 限流电阻值错误或损坏。 3. 程序中对应该LED的引脚定义错误。 | 1. 用万用表通断档检查LED和电阻通路。 2. 确认电阻色环,或直接测量电阻值。 3. 检查 ledPins数组中的引脚号与实际接线是否一致。 |
| 串口显示距离为0或恒定超大值 | 1. 超声波模块Trig或Echo线接触不良。 2. 模块损坏。 3. 传感器前方有吸音材料(如海绵、厚布)。 4. 测量距离过近(<2cm)或过远(>4m)。 | 1. 重新插拔杜邦线,或检查焊接点。 2. 更换一个HC-SR04模块测试。 3. 确保传感器前方空旷,对准硬质表面(墙、木板)。 4. 在有效量程内测试。 |
| 距离读数跳动剧烈 | 1. 传感器附近有风扇、空调出风口等气流干扰。 2. 测量表面不平整或角度倾斜。 3. 电源噪声。 | 1. 远离气流源测试。 2. 对准平整、垂直的物体表面测量。 3. 尝试给Arduino使用独立的电池供电,排除电脑USB口噪声。 |
| 红灯闪烁不正常(常亮或不闪) | 1.updateLeds函数中闪烁逻辑的millis()计时被delay()干扰。2. 红灯对应的引脚模式未设置为 OUTPUT。 | 1. 确保闪烁逻辑基于millis(),且loop中的delay不能太长(如超过闪烁周期)。2. 检查 setup()中是否初始化了所有LED引脚。 |
5.3 高级功能扩展思路
基础版本完成后,这个平台还有巨大的扩展潜力:
- 添加声音报警:连接一个无源蜂鸣器到另一个数字引脚。在进入红区时,不仅红灯闪烁,还可以让蜂鸣器发出“滴滴”声甚至播放一段警报音调,实现声光双重报警。
- 增加显示模块:接上一个OLED屏幕(如SSD1306),可以实时显示精确的距离数值,并显示“安全”、“警告”、“危险”等文字提示,信息更丰富。
- 数据记录与上传:利用Leonardo的USB HID功能,或者增加一个Wi-Fi模块(如ESP-01S),可以将违规靠近的次数和时间戳记录下来,并通过串口发送到电脑,甚至上传到物联网平台进行统计分析。
- 低功耗优化:如果希望用电池供电,可以将测距间隔加大(如每秒1次),在
loop中大部分时间让Arduino进入休眠模式(需要使用特定的低功耗库),只有定时器唤醒时才进行测量和显示,这样可以极大延长电池寿命。 - 方向性增强:单个HC-SR04的探测角度有限。可以尝试使用两个或多个传感器,以一定角度布置,形成一个扇形的监测区域,减少探测盲区。
这个项目最吸引我的地方,就在于它用一个非常简单的硬件组合,实现了一个有实际意义的应用。从读取一个传感器,到控制几个LED,再到加入逻辑和状态判断,它完整地展示了一个嵌入式系统从感知、处理到执行的全过程。当你看到自己制作的设备能根据外界距离做出准确反应时,那种成就感是无可替代的。希望这份详细的教程能帮你顺利复现,并激发你更多的创意。