💡 划重点
- 用 ESP32-S3 搭配 INMP441 数字麦克风,在卧室里实时采集呼吸声与鼾声;通过 FFT 提取低频能量,驱动 WS2812 灯环做柔光渐变。呼吸平稳时灯光缓慢呼吸,鼾声出现时灯光轻微增亮提醒,同时可经 Wi-Fi 上报日志。整套物料控制在 90 元内,适合床头低侵入部署。
需求洞察
目标用户是关注睡眠质量的普通人、轻度打鼾者、以及被伴侣鼾声影响的同睡者。真实痛点有三个:一是普通夜灯只能照明,无法感知睡眠状态;二是半夜醒来开灯刺眼,重新入睡困难;三是手机睡眠监测应用需要整晚录音,隐私感差且耗电。这个项目值得做,是因为它把“呼吸声”变成一种非接触式信号,不需要穿戴设备,也不直接诊断疾病,只用柔光反馈和本地日志帮助用户感知睡眠节奏。对老人卧室或婴儿房,也能作为呼吸活动提示的轻量辅助。
方案设计
感知层:INMP441 以 I2S 输出 24 位数字音频,避免模拟麦克风在卧室长线下的底噪。采样率设为 16 kHz,每帧 512 点,覆盖 50 到 500 Hz 的呼吸与鼾声主频段。
控制层:ESP32-S3 负责 I2S 读取、FFT 计算、能量平滑和阈值判断。低频段能量均值经过一阶低通滤波,避免单次翻身或环境噪声误触发。
执行层:WS2812 灯环使用暖白到淡蓝的柔光渐变。呼吸平稳时亮度低、色温暖;鼾声能量升高时,灯光在 1 秒内轻微增亮,随后缓慢回落,不突然闪烁。
日志与联网层:串口输出能量值用于调试;Wi-Fi 开启后可按分钟聚合,通过 MQTT 或 HTTP 上报到本地服务器。若不需要联网,可仅保留本地灯效,降低隐私顾虑。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 合计 |
|---|---|---|---|---|
| ESP32-S3 开发板 | 1 | 主控与 Wi-Fi | 32 元 | 32 元 |
| INMP441 麦克风模块 | 1 | 呼吸声采集 | 10 元 | 10 元 |
| WS2812B 灯环 12 位 | 1 | 柔光反馈 | 12 元 | 12 元 |
| 5V 电源与 USB 线 | 1 | 供电 | 10 元 | 10 元 |
| 杜邦线、洞洞板 | 1 套 | 接线与固定 | 8 元 | 8 元 |
| 磨砂柔光罩/外壳 | 1 | 遮光与扩散 | 10 元 | 10 元 |
| 合计 | 82 元 |
接线与结构
INMP441 与 ESP32-S3 按给定引脚连接:SCK=4,SD=5,WS=6。模块 VDD 接 3.3V,GND 接地,L/R 接 GND 选择左声道。WS2812 数据引脚接 GPIO 10,5V 供电,GND 共地。若灯环较远,数据线串 330Ω 电阻,电源端并 1000µF 电容。结构上,麦克风朝床铺方向开小孔,灯环藏在磨砂罩内,避免直射眼睛。ESP32-S3 放在床头塑料盒中,USB 供电即可。
核心代码
以下为 Arduino 骨架,依赖 driver/i2s.h、arduinoFFT、Adafruit_NeoPixel。
#include <Arduino.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>
#include <Adafruit_NeoPixel.h>
#define I2S_SCK 4
#define I2S_SD 5
#define I2S_WS 6
#define LED_PIN 10
#define LED_COUNT 12
#define SAMPLE_RATE 16000
#define FFT_SIZE 512
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
ArduinoFFT FFT = ArduinoFFT();
double vReal[FFT_SIZE];
double vImag[FFT_SIZE];
void setup() {
Serial.begin(115200);
strip.begin();
strip.show();
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 256,
.use_apll = false
};
i2s_driver_install(I2S_NUM_0, &cfg;, 0, NULL);
i2s_pin_config_t pin = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_SD
};
i2s_set_pin(I2S_NUM_0, &pin;);
}
void loop() {
for (int i = 0; i < FFT_SIZE; i++) {
int32_t sample = 0;
size_t bytesRead = 0;
i2s_read(I2S_NUM_0, &sample;, sizeof(sample), &bytesRead;, portMAX_DELAY);
vReal[i] = (double)(sample >> 8);
vImag[i] = 0;
}
double energy = 0;
for (int i = 1; i < 40; i++) energy += vReal[i];
energy /= 39.0;
static double smooth = 0;
smooth = smooth * 0.8 + energy * 0.2;
int brightness = constrain(map((long)smooth, 200, 3000, 8, 160), 8, 160);
for (int i = 0; i < LED_COUNT; i++) {
uint8_t r = brightness * 0.65;
uint8_t g = brightness * 0.50;
uint8_t b = brightness * 0.30 + (brightness * i / LED_COUNT) * 0.15;
}
strip.show();
Serial.println(smooth);
delay(10);
}
调试步骤
第一步,只烧录 I2S 读取代码,串口观察是否有原始数据,确认 SCK、WS、SD 接对。第二步,加入 FFT 并打印低频能量,安静卧室下记录基线值。第三步,对着麦克风轻声呼吸和模拟鼾声,观察能量峰值,把阈值设在基线 2 到 3 倍处。第四步,调节灯环亮度和渐变速度,确保不刺眼。第五步,连续运行一晚,检查是否因翻身、空调声误触发。第六步,装上磨砂罩后重新校准,因为外壳会衰减高频并改变麦克风灵敏度。