1. 项目概述:一个“社交距离伙伴”能解决什么问题?
最近几年,大家对于公共空间里的个人距离和健康防护意识有了显著提升。无论是去超市购物、在办公室办公,还是在公园散步,我们都希望能有一个简单、无感的方式,来提醒自己与他人保持一个合适的距离。传统的办法是靠目测,但这不仅不准确,还容易分心。于是,一个名为“Social Distancing Buddy”(社交距离伙伴)的小玩意儿就进入了我的视野。本质上,它就是一个便携式的电子设备,核心功能是当有其他人进入你预设的安全距离范围内时,它会通过振动、灯光或声音发出警报,从而帮助你更轻松地维持社交距离。
听起来很简单,对吧?但要把这个想法变成一个可靠、好用且成本可控的实物产品,里面涉及到的技术选型、电路设计、功耗控制和用户体验考量,每一个环节都值得深挖。这个项目非常适合电子爱好者、创客,甚至是想要了解物联网边缘设备开发的朋友。通过动手制作一个“社交距离伙伴”,你不仅能得到一个实用的个人工具,更能系统地学习到传感器应用、微控制器编程、低功耗设计等硬核技能。接下来,我就把自己从构思到实现这个项目的完整过程,以及踩过的各种坑,毫无保留地分享出来。
2. 核心方案选型与设计思路拆解
做一个距离检测装置,首先得确定“眼睛”和“大脑”。市面上测距的方案很多,我们需要根据使用场景来权衡。
2.1 传感器选型:为什么是超声波,而不是红外或激光?
最常见的近距离测距方案有红外、超声波和激光雷达。激光雷达精度高、响应快,但成本也高,用在消费级小设备上有点“杀鸡用牛刀”。红外测距,比如夏普的GP2Y0A系列,成本适中,但它对物体的颜色和材质比较敏感,深色物体可能测不准,在复杂光线的室外环境也不稳定。
所以,我最终选择了HC-SR04超声波传感器。理由很充分:首先,它价格极其低廉,十几块钱就能买到。其次,它的测距原理是发射超声波并接收回波,通过时间差计算距离,几乎不受光线、颜色影响,在室内外都能稳定工作。最后,它的有效测距范围在2cm到400cm之间,对于设定1米到2米的社交距离提醒来说,完全够用。当然,它也有缺点,比如波束角比较大(约15度),意味着探测的是一个圆锥形区域,而不是一个点;同时对于柔软吸音的材质(如窗帘)探测效果会打折扣。但在人流走动的场景下,这个波束角反而能提供一定的探测宽度,算是一个可以接受的特性。
2.2 主控芯片选择:ESP32如何平衡功能与功耗?
确定了“眼睛”,接下来是“大脑”。主控芯片需要处理传感器数据、判断距离阈值、并驱动警报装置。可选的有经典的Arduino Uno(ATmega328P)、更小巧的ATTiny85,或者功能强大的ESP32。
Arduino Uno简单易用,生态丰富,但它功耗较高,且需要额外的模块才能实现无线功能(比如后期想加个手机提醒)。ATTiny85功耗极低,但引脚和计算资源有限,扩展性差。我选择了ESP32,原因在于它提供了一个完美的平衡点:
- 双核处理器与低功耗模式:ESP32性能强大,可以轻松处理传感器数据逻辑。更重要的是,它支持深度睡眠模式,当没有检测到人时,可以让大部分电路进入休眠,仅定时唤醒检测,这对依赖电池供电的便携设备至关重要。
- 集成无线连接:ESP32自带Wi-Fi和蓝牙。虽然我们这个基础版本可能用不上,但这为未来功能升级留下了巨大空间,比如通过手机APP调整安全距离、查看警报日志、甚至多个设备组网联动。
- 丰富的IO与ADC:足够连接传感器、警报器和电池电量检测电路。
2.3 整体系统架构设计
基于以上选型,整个设备的架构就清晰了:
- 感知层:HC-SR04超声波传感器,负责持续探测前方障碍物距离。
- 控制层:ESP32开发板(如ESP32 DevKit C),作为核心处理器。
- 警报层:由一个小型振动马达(用于贴身静默提醒)、一个彩色LED(用于视觉提示)和一个有源蜂鸣器(用于声音提醒)组成。用户可以根据场合选择启用哪种警报。
- 供电层:一枚3.7V的锂聚合物电池,配合一个充放电管理模块(如TP4056),实现充电和稳压供电。
- 交互层:两个按钮,用于开关机和调整距离阈值;一个开关,用于切换警报模式。
这个架构兼顾了功能、成本和可扩展性。所有元件都很常见,总成本可以控制在百元以内。
3. 硬件电路设计与组装要点
电路是项目的骨架,设计不合理,后面编程和调试会痛苦不堪。
3.1 核心电路连接详解
ESP32与各元件的连接需要特别注意电平匹配和驱动能力。以下是详细的接线表:
| 元件 | 引脚 | 连接至 ESP32 引脚 | 说明与注意事项 |
|---|---|---|---|
| HC-SR04 | VCC | 3.3V | 切记不可接5V!虽然模块标称5V,但许多版本在3.3V下也能工作。接5V可能会损坏ESP32的GPIO。 |
| HC-SR04 | Trig | GPIO 23 | 触发测距信号输出脚 |
| HC-SR04 | Echo | GPIO 22 | 回波信号输入脚。Echo脚输出是5V电平,必须通过一个1kΩ和2kΩ电阻组成的分压电路,将电压降至3.3V左右再接入ESP32,否则必烧芯片! |
| 有源蜂鸣器 | + | GPIO 18 | 通过一个NPN三极管(如8050)驱动,ESP32的GPIO直接驱动能力有限。 |
| 振动马达 | + | GPIO 19 | 同样建议通过三极管驱动。 |
| LED (共阳) | R, G, B | GPIO 12, 14, 27 | 每个颜色通道串联一个220Ω限流电阻。 |
| 按键A (设置) | 一端 | GPIO 33 | 另一端接地。配置为内部上拉输入,按下时为低电平。 |
| 按键B (开关/切换) | 一端 | GPIO 32 | 同上。 |
| 电池电量检测 | 电池正极 | GPIO 34 (ADC) | 通过两个电阻(如100kΩ和220kΩ)分压后接入。确保分压后电压不超过3.3V。 |
注意:超声波传感器的Echo脚电平转换是硬件设计中最容易出错的地方。我一开始偷懒直接接了,结果瞬间烧掉了一个ESP32的GPIO口。教训就是,面对任何非3.3V的信号源,先量电压,再做分压,不要心存侥幸。
3.2 功耗优化设计心得
既然是便携设备,续航是硬指标。除了依赖ESP32的深度睡眠,硬件上也能做很多优化:
- 电源路径管理:使用带有“负载开关”功能的充放电管理模块。当设备通过按键唤醒时,才给传感器和警报器供电,睡眠时彻底断开它们的电源,消除静态电流消耗。
- 传感器供电控制:将HC-SR04的VCC不直接接3.3V,而是通过一个MOS管连接到ESP32的一个GPIO上。需要测距时,GPIO输出高电平打开MOS管给传感器供电;测距完毕,立即断电。实测这个操作能让整体睡眠电流从几十mA降至几百微安。
- 警报器选型:振动马达和蜂鸣器要选择低电压、低电流的型号。LED使用PWM控制亮度,在非警报状态下可以设置为极低亮度或熄灭。
3.3 PCB设计与外壳考虑
如果只是做原型,在面包板或洞洞板上搭建即可。但如果想做得精致耐用,设计一块简单的PCB是值得的。使用KiCad或立创EDA这类免费工具,把核心电路(包括电平转换、三极管驱动)集成在一块小板上,可靠性会大大提升。
外壳可以使用3D打印。设计时要注意:
- 为超声波传感器开一个干净的窗口,避免塑料外壳对声波造成干扰或折射。
- 蜂鸣器开口要对着外侧,并且做好密封,避免声音在壳内闷响。
- 预留充电接口和按键的孔位,位置要方便操作。
4. 软件逻辑与核心代码实现
硬件搭好了,接下来就是赋予它灵魂的软件部分。代码逻辑主要围绕“测量-判断-响应-睡眠”这个循环。
4.1 超声波测距的稳定化处理
HC-SR04的原始读数会有波动,尤其是探测边缘或移动物体时。直接使用单次测量值容易导致误报警。必须进行软件滤波。
// 定义引脚 const int trigPin = 23; const int echoPin = 22; float getFilteredDistance() { float distances[5]; // 采样5次 float sum = 0; int validCount = 0; for (int i = 0; i < 5; i++) { // 触发测距 digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // 读取回波时间 long duration = pulseIn(echoPin, HIGH, 30000); // 超时设置30ms,对应约5米 float distance = duration * 0.0343 / 2; // 声速取343m/s,除以2 // 有效性检查:在有效范围内(2cm - 400cm) if (distance > 2 && distance < 400) { distances[validCount] = distance; sum += distance; validCount++; } delay(30); // 两次测量间短暂延迟,防止信号干扰 } if (validCount == 0) return -1; // 无效测量 // 中位值平均滤波:排序后取中间几个值的平均 std::sort(distances, distances + validCount); float medianAvg = 0; int start = validCount / 4; // 去掉最低的1/4 int end = validCount * 3 / 4; // 去掉最高的1/4 for (int i = start; i < end; i++) { medianAvg += distances[i]; } medianAvg /= (end - start); return medianAvg; }这段代码做了几件事:多次采样、剔除明显异常值(过近或过远)、然后使用中位值平均滤波法,能有效抑制偶然的跳动,得到稳定的距离值。
4.2 多模式警报状态机实现
设备应该有不同工作模式以适应不同场景,比如“静音振动模式”、“办公室灯光模式”、“公共场所声音模式”。我用一个简单的状态机来实现。
enum AlertMode { MODE_OFF, MODE_VIBRATE, MODE_LIGHT, MODE_SOUND, MODE_ALL }; AlertMode currentMode = MODE_VIBRATE; int distanceThreshold = 150; // 默认1.5米 void handleAlert(float currentDistance) { if (currentDistance < 0 || currentDistance > distanceThreshold) { // 无目标或距离安全,关闭所有警报 digitalWrite(vibratePin, LOW); setLedColor(0, 255, 0); // 绿色,安全 digitalWrite(buzzerPin, LOW); return; } // 进入警报距离 float proximityLevel = (distanceThreshold - currentDistance) / distanceThreshold; // 0到1,越近越大 switch (currentMode) { case MODE_VIBRATE: // 振动强度随距离变近而增强(PWM控制) analogWrite(vibratePin, (int)(proximityLevel * 255)); break; case MODE_LIGHT: // 灯光从绿色渐变到红色 setLedColor((int)(proximityLevel * 255), (int)((1-proximityLevel) * 255), 0); break; case MODE_SOUND: // 蜂鸣器发出间歇性警报,频率随距离变近而加快 int beepInterval = 1000 - (int)(proximityLevel * 900); // 从1秒到0.1秒 if (millis() % beepInterval < 100) { digitalWrite(buzzerPin, HIGH); } else { digitalWrite(buzzerPin, LOW); } break; case MODE_ALL: // 组合警报 analogWrite(vibratePin, (int)(proximityLevel * 200)); setLedColor((int)(proximityLevel * 255), 0, 0); if (millis() % 500 < 100) digitalWrite(buzzerPin, HIGH); else digitalWrite(buzzerPin, LOW); break; } }状态机的优势是逻辑清晰,易于扩展新的警报模式。
4.3 低功耗睡眠与唤醒策略
为了实现长续航,大部分时间设备应在深度睡眠中。我设计了一个“按需唤醒+周期巡检”的策略。
#define uS_TO_S_FACTOR 1000000ULL // 微秒到秒转换因子 #define TIME_TO_SLEEP 10 // 深度睡眠时间(秒) void enterDeepSleep() { // 1. 关闭所有外围设备电源(通过GPIO控制MOS管) digitalWrite(sensorPowerPin, LOW); digitalWrite(alertPowerPin, LOW); // 2. 配置唤醒源:定时唤醒 或 按键唤醒 esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); esp_sleep_enable_ext0_wakeup(GPIO_NUM_32, 0); // 按键B(低电平唤醒) // 3. 进入深度睡眠 Serial.println("进入深度睡眠"); delay(100); // 等待串口输出完成 esp_deep_sleep_start(); } void setup() { // 判断唤醒原因 esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); if(wakeup_reason == ESP_SLEEP_WAKEUP_TIMER){ Serial.println("定时唤醒,开始一次检测"); performCheck(); // 执行一次完整的测量和判断 } else if(wakeup_reason == ESP_SLEEP_WAKEUP_EXT0){ Serial.println("按键唤醒,进入配置模式"); enterConfigMode(); // 长按按键,进入阈值设置模式 } // 无论何种唤醒,执行完任务后再次进入睡眠 enterDeepSleep(); }这个策略下,设备每10秒自动醒来检测一次,如果发现有人靠近并触发警报,则会在警报期间保持唤醒状态。警报解除后,如果一段时间内无新目标,则再次进入深度睡眠。按键则用于主动唤醒和配置。实测下来,一枚1000mAh的电池,可以轻松支撑一周以上的使用。
5. 调试、优化与常见问题实录
把代码烧录进去,设备跑起来,这才是挑战的开始。下面是我在调试过程中遇到的一些典型问题及解决方法。
5.1 传感器读数不稳定或跳变极大
这是最常见的问题。除了前面提到的软件滤波,硬件上也要检查:
- 电源噪声:确保给传感器供电的3.3V电源是干净的。可以在VCC和GND之间并联一个10uF和0.1uF的电容,滤除低频和高频噪声。
- 声学干扰:超声波传感器之间会互相干扰。如果两个设备靠得很近,一个发出的波可能被另一个接收到。解决方法是在代码里错开它们的触发时间,或者给传感器套上一个海绵或橡胶圈,稍微限制一下声波的扩散角度。
- 物理震动:如果设备戴在身上走动,震动可能导致传感器松动或测量基准面变化。确保传感器被牢固固定,并且探测面朝向稳定。
5.2 误报警:对非人目标(如墙壁、桌椅)也报警
这是超声波传感器波束角大的固有缺点。可以通过算法优化:
- 距离持续性判断:只有目标持续出现在警报距离内超过一定时间(如1秒),才触发警报。短暂穿过探测区域的飞虫或飘过的塑料袋会被忽略。
- 距离变化率判断:人是在移动的,距离值会持续变化。而墙壁的距离值是恒定不变的。可以计算连续几次测量的距离变化率(导数),如果变化率几乎为零,则很可能是静止物体,可以忽略。
- 安装角度调整:不要水平向前安装,而是稍微向下倾斜一个角度。这样,它的主要探测区域是地面以上的一段空间,对远处的地面或低矮物体不敏感,更专注于探测行人躯干部分。
5.3 续航时间远低于预期
如果发现设备半天就没电了,需要系统性地排查“电老虎”:
- 测量睡眠电流:使用万用表uA档,串联在电池和主板之间,在设备进入睡眠后测量。理想的整体睡眠电流应小于500uA。如果过高,依次断开外围元件(传感器、警报器、指示灯),看是哪个部分漏电。
- 检查“僵尸”外设:有些外设即使在软件里设置为
LOW或INPUT,物理上仍可能产生电流。最彻底的方法就是用MOS管或模拟开关在硬件上彻底断电。 - Wi-Fi/蓝牙未关闭:在进入深度睡眠前,确保调用
WiFi.mode(WIFI_OFF)和btStop()来关闭无线模块。 - 串口日志输出:
Serial.print在睡眠时虽然不工作,但串口模块本身可能耗电。在发布最终版本时,可以移除所有调试用的串口输出代码。
5.4 警报响应不及时或漏报
这通常是因为睡眠-唤醒周期设置得太长。如果设备每10秒才醒一次,那么有人快速从侧面经过时很可能被漏掉。需要在功耗和响应速度间权衡:
- 在人流量可能较大的环境(如超市),可以将睡眠时间缩短至3-5秒。
- 引入**运动传感器(如MPU6050加速度计)**作为二级唤醒源。当检测到设备本身在移动时(说明佩戴者在行走),自动切换到更频繁的检测模式(如每秒一次);静止时则恢复长睡眠周期。这样能智能地平衡响应与续航。
6. 功能扩展与进阶玩法
基础功能稳定后,就可以玩点花样了,这也是ESP32的优势所在。
6.1 无线功能升级:蓝牙APP配置
通过蓝牙,我们可以用手机方便地调整设置,而不用去按那个小小的按钮。
- 在ESP32上运行一个蓝牙低能耗服务,暴露几个特征值,如“距离阈值”、“警报模式”、“电池电量”。
- 开发一个简单的手机APP(可以用MIT App Inventor或Flutter快速原型),连接设备后,可以滑动滑块调整距离,点击按钮切换模式。
- 设备端收到新配置后,保存到非易失性存储中。这样一次设置,永久生效。
6.2 数据记录与统计
给设备加上一个小型SD卡模块或利用ESP32的SPIFFS文件系统,可以记录警报事件(时间、距离)。
- 这对于研究公共空间人流密度、个人社交习惯分析会很有用。
- 数据可以定期通过Wi-Fi上传到私有服务器,生成简单的统计图表。
6.3 从“伙伴”到“网络”
想象一下,在小型办公室或工作坊里,每个人都佩戴一个。设备之间通过Wi-Fi或蓝牙Mesh网络进行通信,不仅可以提醒自己,还能在后台进行简单的接触追踪。当两个设备长时间处于近距离时,各自在本地记录一个加密的接触事件。这完全在本地完成,不涉及任何中心化服务器,在保护隐私的前提下,为小集体提供额外的安全参考。
这个项目从一个小小的想法开始,到最终做出一个可靠可用的设备,整个过程充满了工程上的权衡和解决问题的乐趣。它涉及了硬件设计、嵌入式编程、传感器应用、功耗优化等多个方面,是一个非常好的综合性实践。最大的体会是,在嵌入式开发里,软件和硬件的边界非常模糊,一个稳定可靠的产品,一定是软硬件协同设计、反复调试的结果。比如那个Echo脚的电平转换问题,软件写得再好,硬件上没做防护,一上电就前功尽弃。所以,动手之前多查资料,连接电路时多一分谨慎,调试时保持耐心,这些经验比任何一个具体的代码片段都更有价值。