💡 划重点

  • 本项目利用 INMP441 全向 I2S 麦克风采集卧室环境音,通过 ESP32‑S3 自带的 DSP 加速指令集实时运行 256 点 FFT,精准捕捉呼吸频段(约 0.15‑0.5Hz)的声能量变化。当检测到呼吸暂停、减弱或鼾声突变时,WS2812 智能灯带以仿生柔光渐变方式温和唤醒用户或营造助眠光节奏,同时可选 Wi‑Fi 上报睡眠质量日志。整机硬件成本

需求洞察

目标用户:轻度睡眠障碍者、需要监测婴儿呼吸的家长、独居老人、钟爱量化自我体验的极客。

真实痛点:传统睡眠监测手环依赖加速度计和血氧,佩戴不适;医疗级多导睡眠仪价格数万元且必须在医院使用。卧室场景下,用户更需要一种非接触、隐秘、能融入家居氛围的夜间呼吸监测手段。

为什么值得做:声音信号包含了鼾声、呼吸节律、体位改变等丰富信息,INMP441 麦克风成本仅 6‑8 元却能提供 18 位分辨率、低噪声的音频流。配合 ESP32‑S3 的硬件 FFT 加速,可以在不增加额外芯片的前提下完成实时频谱分析。将反馈与夜灯结合,既解决了数据可视化的问题,又满足了卧室不宜有过强光线刺激的需求。整套方案 BOM 成本远低于 90 元,量产友好、复现性强,且完全开源。

方案设计

感知层

控制层

1. 对每帧 512 点计算 256 点实数 FFT(利用 ESP‑DSP 库或 arduinoFFT)。

2. 提取 0‑500 Hz 频带,重点监控 100‑400 Hz 的呼吸声纹与 0.1‑0.6 Hz 的包络起伏。

3. 滑动窗口 RMS 与峰值检测判断当前呼吸状态:正常、呼吸暂停(>10s)、呼吸浅弱、深度鼾声。

4. 连续 3 次暂停警报触发光唤醒梯度。

执行层

日志/联网层

物料清单

序号 名称 规格/型号 数量 用途 估算单价(元) 小计(元)
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 可进一步压缩。*

接线与结构

给定接线规划

微调说明:严格遵循给定 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。*

调试步骤

  1. 硬件上电前检查:万用表确认 VDD‑3V3、5V 与 GND 无短路,麦克风 L/R 引脚接地。
  2. I2S 数据流验证:先上传只打印原始采样值到串口的测试固件,靠近麦克风说话,确认数据幅值随声音变化。
  3. FFT 谱线校准:在安静卧室采集背景噪声作为基准,保存 0‑500 Hz 基线能量;用秒表配合规律呼吸,观察 0.3 Hz 频点是否出现能量峰值。
  4. 灯带驱动测试:单独运行 WS2812 颜色循环,确认 GPIO10 输出正常,检查灯带供电是否稳定。
  5. 状态机联调:模拟憋气 15 秒,观察灯带是否由呼吸律动切换至日出渐变,记录切换延迟。
  6. Wi‑Fi 上报测试:打开 Home Assistant 或 MQTT Explorer,确认主题收到呼吸次数和事件标记,延迟应低于 2 秒。
  7. 整机装壳:装入磨砂亚克力灯罩后重新测量麦克风灵敏度,必要时微调报警阈值,确保隔着一层 2mm 亚克力板仍能捕获呼吸音。

设计图

睡眠呼吸声监测夜灯结构flow
1
感知输入:INMP441 麦克风 → I2S 音频流
2
控制处理:ESP32-S3(FFT 分析、状态机、光效算法)
3
执行反馈:WS2812 柔光渐变灯带(唤醒 / 助眠)
4
数据上报:MQTT 推送呼吸事件至智能家居平台