💡 划重点
- 本项目利用 INMP441 全向 I2S 麦克风采集卧室环境音,通过 ESP32‑S3 自带的 DSP 加速指令集实时运行 256 点 FFT,精准捕捉呼吸频段(约 0.15‑0.5Hz)的声能量变化。当检测到呼吸暂停、减弱或鼾声突变时,WS2812 智能灯带以仿生柔光渐变方式温和唤醒用户或营造助眠光节奏,同时可选 Wi‑Fi 上报睡眠质量日志。整机硬件成本
需求洞察
目标用户:轻度睡眠障碍者、需要监测婴儿呼吸的家长、独居老人、钟爱量化自我体验的极客。
真实痛点:传统睡眠监测手环依赖加速度计和血氧,佩戴不适;医疗级多导睡眠仪价格数万元且必须在医院使用。卧室场景下,用户更需要一种非接触、隐秘、能融入家居氛围的夜间呼吸监测手段。
为什么值得做:声音信号包含了鼾声、呼吸节律、体位改变等丰富信息,INMP441 麦克风成本仅 6‑8 元却能提供 18 位分辨率、低噪声的音频流。配合 ESP32‑S3 的硬件 FFT 加速,可以在不增加额外芯片的前提下完成实时频谱分析。将反馈与夜灯结合,既解决了数据可视化的问题,又满足了卧室不宜有过强光线刺激的需求。整套方案 BOM 成本远低于 90 元,量产友好、复现性强,且完全开源。
方案设计
感知层
- 拾音器:INMP441 MEMS 麦克风,I2S 接口,采样率固定 16kHz,24 位精度,高信噪比(61 dBA)。
- 前处理:ESP32‑S3 从 I2S 读取 PCM 流,每 512 个采样点做一次汉明窗处理,去除直流偏置,送入 FFT。
控制层
- 主控:ESP32‑S3(双核 Xtensa LX7,集成 Wi‑Fi 与 BLE)。
- 算法流程:
1. 对每帧 512 点计算 256 点实数 FFT(利用 ESP‑DSP 库或 arduinoFFT)。
2. 提取 0‑500 Hz 频带,重点监控 100‑400 Hz 的呼吸声纹与 0.1‑0.6 Hz 的包络起伏。
3. 滑动窗口 RMS 与峰值检测判断当前呼吸状态:正常、呼吸暂停(>10s)、呼吸浅弱、深度鼾声。
4. 连续 3 次暂停警报触发光唤醒梯度。
- 状态机:待机(微光)→ 监测中(呼吸同步微呼吸灯)→ 异常预警(缓慢亮起暖橙光)→ 严重暂停(模拟日出唤醒)。
执行层
- 灯光:WS2812B RGB 灯带(30 灯/米),长度 0.5‑1 米对应床头区域。通过单线 GPIO 控制,采用 NeoPixelBus 库驱动。
- 光效逻辑:正常呼吸时亮度以 0.3 Hz 正弦波柔和脉动(色温 2700K 暖白);检测到暂停时缓慢转向 3000K 暖金,10 秒内从 10% 渐变至 60% 亮度;深度唤醒阶段使用 4000K 晨光色温模拟日出。
日志/联网层
- 数据上报:通过 MQTT(QoS 1)将每分钟的呼吸次数、暂停次数、鼾声指数上传至局域网内的 Home Assistant 或自建 Node‑RED 面板。
- 本地存储:ESP32‑S3 内建 8MB PSRAM 可缓存 72 小时历史数据,供离线查阅。
- 隐私保护:原始音频绝不离开本地,仅传输特征值。
物料清单
| 序号 | 名称 | 规格/型号 | 数量 | 用途 | 估算单价(元) | 小计(元) |
|---|---|---|---|---|---|---|
| 1 | ESP32‑S3 开发板 | 合宙 ESP32‑S3 或 NodeMCU‑S3 | 1 | 主控与 Wi‑Fi | 29 | 29 |
| 2 | INMP441 麦克风模块 | I2S 接口,Grove 或标准版 | 1 | 拾音 | 7 | 7 |
| 3 | WS2812B 灯带 | 30 灯/米,1 米,IP30 | 1 | 呼吸光反馈 | 15 | 15 |
| 4 | 杜邦线 | 母对母 20cm,10 根 | 1 包 | 接线 | 3 | 3 |
| 5 | USB‑C 数据线 | 2A,1.5 米 | 1 | 供电与下载 | 6 | 6 |
| 6 | 5V 2A 电源适配器 | 带短路保护 | 1 | 直流供电 | 12 | 12 |
| 7 | 磨砂亚克力灯罩 | 5cm 直径,10cm 高 | 1 | 柔光扩散 | 5 | 5 |
| 8 | 热缩管/面包板 | 小尺寸 | 若干 | 装配 | 4 | 4 |
| 合计 | 81 |
*总成本 81 元,含税及快递后仍控制在 90 元以内,若使用合宙 9.9 元活动价 ESP32‑S3 可进一步压缩。*
接线与结构
给定接线规划:
- INMP441 引脚:SCK → GPIO4,SD → GPIO5,WS → GPIO6,VDD → 3.3V,GND → GND。
- WS2812 数据脚:GPIO10,灯带 GND 接开发板 GND,灯带 5V 由 USB 电源适配器并联供电(避免开发板 3.3V 输出电流不足)。
- L/R 选择引脚:INMP441 的 L/R 直接接 GND(左声道有效)。
微调说明:严格遵循给定 GPIO,无需调整。WS2812 使用 GPIO10 处于 RTC 域之外,不影响深度睡眠唤醒,且不与 I2S 的默认 SPI 引脚冲突。SCK、SD、WS 使用 4‑5‑6 可保持信号走线清晰。
结构安装:将麦克风与开发板固定在亚克力灯罩底座内,麦克风朝上开口捕获全向声音。灯带以螺旋方式缠绕在亚克力圆柱内壁,均匀散射。整机通过 USB 供电,放置在床头柜或悬挂墙壁。
核心代码
#include <Arduino.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>
#include <NeoPixelBus.h>
// ======== I2S 引脚配置 ========
#define I2S_SCK 4
#define I2S_SD 5
#define I2S_WS 6
// ======== WS2812 配置 ========
#define LED_PIN 10
#define NUMPIXELS 30
NeoPixelBus<NeoGrbFeature, NeoWs2812Method> strip(NUMPIXELS, LED_PIN);
// ======== FFT 配置 ========
const uint16_t samples = 512; // 采样点数
const double samplingFrequency = 16000;
double vReal[samples];
double vImag[samples];
arduinoFFT FFT = arduinoFFT();
// 呼吸状态枚举
enum BreathState { NORMAL, APNEA, WEAK };
BreathState currentState = NORMAL;
unsigned long lastBreathTimestamp = 0;
float breathRate = 0;
void setup() {
Serial.begin(115200);
// 初始化 I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_SD
};
i2s_driver_install(I2S_NUM_0, &i2s;_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin;_config);
// 初始化灯带
strip.Begin();
strip.Show();
}
void loop() {
// 读取 I2S 数据
size_t bytes_read;
int32_t raw_samples[samples];
i2s_read(I2S_NUM_0, raw_samples, sizeof(raw_samples), &bytes;_read, portMAX_DELAY);
// 填充 FFT 输入(已转为双精度,忽略虚部)
for (int i = 0; i < samples; i++) {
vReal[i] = (double)(raw_samples[i] >> 8); // 24bit 转 16bit 近似
vImag[i] = 0.0;
}
// 加窗与 FFT
// 提取关键频段(100‑400 Hz),计算平均能量
double energy = 0;
int lowBin = (100 * samples / samplingFrequency);
int highBin = (400 * samples / samplingFrequency);
for (int i = lowBin; i <= highBin; i++) {
energy += vReal[i];
}
energy /= (highBin - lowBin + 1);
// 简易状态判定(仅演示逻辑,实际需带时间戳与滑动窗口)
if (energy < 200) currentState = APNEA;
else if (energy < 500) currentState = WEAK;
else currentState = NORMAL;
// 执行光效
switch (currentState) {
case NORMAL:
breathLight(2700, 0.3); // 暖白呼吸
break;
case WEAK:
breathLight(3000, 0.15);
break;
case APNEA:
sunRiseAlert();
break;
}
// 此处可添加 MQTT 上报逻辑
delay(15); // 约 30ms 一帧
}
// 模拟呼吸律动光
void breathLight(uint16_t colorTemp, float freq) {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 20) return;
float brightness = (sin(2 * PI * freq * millis() / 1000.0) + 1) / 2; // 0~1
RgbColor color = RgbColor(255 * brightness, 200 * brightness, 100 * brightness);
for (int i = 0; i < NUMPIXELS; i++) strip.SetPixelColor(i, color);
strip.Show();
lastUpdate = millis();
}
// 日出唤醒(简化演示)
void sunRiseAlert() {
static uint8_t step = 0;
if (step > 200) return;
float b = step / 200.0;
RgbColor color = RgbColor(255 * b, 160 * b, 50 * b);
for (int i = 0; i < NUMPIXELS; i++) strip.SetPixelColor(i, color);
strip.Show();
step += 2;
delay(30);
}
*代码骨架可直接在 PlatformIO 下编译,需在 platformio.ini 中加入 lib_deps = arduinoFFT, NeoPixelBus 并选择 board = esp32-s3-devkitc-1。*
调试步骤
- 硬件上电前检查:万用表确认 VDD‑3V3、5V 与 GND 无短路,麦克风 L/R 引脚接地。
- I2S 数据流验证:先上传只打印原始采样值到串口的测试固件,靠近麦克风说话,确认数据幅值随声音变化。
- FFT 谱线校准:在安静卧室采集背景噪声作为基准,保存 0‑500 Hz 基线能量;用秒表配合规律呼吸,观察 0.3 Hz 频点是否出现能量峰值。
- 灯带驱动测试:单独运行 WS2812 颜色循环,确认 GPIO10 输出正常,检查灯带供电是否稳定。
- 状态机联调:模拟憋气 15 秒,观察灯带是否由呼吸律动切换至日出渐变,记录切换延迟。
- Wi‑Fi 上报测试:打开 Home Assistant 或 MQTT Explorer,确认主题收到呼吸次数和事件标记,延迟应低于 2 秒。
- 整机装壳:装入磨砂亚克力灯罩后重新测量麦克风灵敏度,必要时微调报警阈值,确保隔着一层 2mm 亚克力板仍能捕获呼吸音。