写在前面,这个项目是去年底帮一位做智能床垫的朋友做的原型验证。当时他手里的方案是压电薄膜传感器,贴在床垫底下测心率呼吸,折腾了两个月,问题一个接一个:人翻身压到传感器就爆数据,冬天铺电热毯后基线飘得没法看,最要命的是用户对“床垫里藏了东西”这件事本身有心理抵触。后来我们聊到毫米波雷达,我说换个思路试试,于是就有了这套基于STM32的智能睡眠监测原型。从硬件选型到算法调通,前前后后花了大概三周,这篇文章把整个系统的设计思路、踩坑记录和核心代码逻辑完整梳理一遍,给正在做类似项目的朋友一个参考。需要说明的是,本文不是商用量产方案,所有数据和结论都来自实验室评测和有限样本的家庭实测,但思路和坑是通用的,应该能帮你少走不少弯路。
1. 为什么睡眠监测的传感器选型最终落在毫米波雷达上
1.1 非接触式方案里,隐私和舒适性是硬指标
睡眠监测这件事,说到底是采集人体在床上的生理信号和活动状态。传统的接触式方案有压电薄膜、心率带、手环等,非接触式方案有摄像头、红外热成像、WiFi感知、毫米波雷达等。我在选型时给自己列了三个硬指标:不能侵犯隐私、不需要用户佩戴任何东西、不能被被子枕头遮挡后失效。
摄像头第一个被排除,哪怕只做姿态估计不录像,用户对“卧室里有摄像头”这件事的接受度依然很低,而且夜间全黑环境下需要红外补光,体验很差。红外热成像不涉及隐私问题,但分辨率低、成本高,对呼吸引起的体表温度变化分辨能力有限。WiFi感知(CSI指纹)虽然完全无感,但鲁棒性太差,家里有人走动、门开关都会改变多径环境,很难稳定提取呼吸信号。
毫米波雷达的优势在于:它发射的是毫米级波长的电磁波,可以穿透被子、床单等非金属遮挡物,同时对人体胸腔的微小起伏(呼吸时位移大概只有1到5毫米)非常敏感。而且它不采集任何图像信息,输出只是距离-多普勒数据,天然没有隐私问题。放到“智能睡眠监测”这个场景里,毫米波雷达几乎是为这个需求定制的。
1.2 毫米波雷达与主流睡眠监测方案的实测对比
我把市面上几种主流方案放在同一个环境下做了对比测试:同一张床、同一个测试者、同样的时间段。这里直接给结论。
| 方案类型 | 隐私性 | 抗遮挡能力 | 呼吸检测稳定性 | 翻身/体动识别 | 综合成本(模块级) |
|---|---|---|---|---|---|
| 压电薄膜 | 高 | 差(受压才有效) | 中(翻身即中断) | 弱 | 低 |
| 手环/心率带 | 中(需佩戴) | 不适用 | 中(受睡眠动作干扰) | 弱 | 中 |
| 摄像头 | 低 | 差(被子遮挡) | 中 | 强 | 高 |
| 红外热成像 | 高 | 差(被子遮挡) | 弱 | 中 | 很高 |
| 毫米波雷达 | 高 | 强(穿透被子) | 强 | 强 | 中 |
实测里最明显的感觉是:压电薄膜在你正常平躺时数据很漂亮,但只要你侧睡、翻身、或者把手压在胸上,数据就会有一段很难看的毛刺,算法得花很大力气去滤波去重。毫米波雷达即使在被子完全蒙头的情况下,呼吸波形依然稳定,因为雷达捕捉的是胸腔表面的机械振动,而不是皮肤表面温度或压强。
1.3 24GHz与60GHz频段怎么取舍
标题里提到了“毫米波雷达”,但毫米波其实覆盖很宽,汽车上用的77GHz/79GHz,工业上常用的24GHz和60GHz。睡眠监测这个场景下,24GHz和60GHz是当前消费级模块的主流选择。
24GHz的优势是价格低、国产模块成熟(很多用在人体感应灯、楼宇感应上),功耗相对可控。缺点也很明显:带宽小,距离分辨率差(通常只有几十厘米级别),想要区分“呼吸引起的胸腔微动”和“翻身带来的大幅度运动”主要靠多普勒维度的信号强度,而不是距离维度的精细分辨。60GHz带宽大得多,距离分辨率能到毫米级,理论上更能精细刻画呼吸波形,但模块贵,功耗也高一些,数据处理量更大。
我这个项目用的是24GHz模块,理由是:呼吸检测在原理上不依赖高距离分辨率,只要有稳定的微多普勒信号就行;24GHz的成熟模块方案直接把I/Q中频信号引出来,STM32可以直接采样处理,整个系统的技术难度和成本都控制在比较舒服的范围。如果你追求更高的信号质量和更精细的体动分类,60GHz是更好的选择,代价是主控可能要升级到带DSP指令的M4/M7内核。
2. 系统整体架构与关键器件的取舍逻辑
2.1 从天线到App的完整数据链路
先说我最终实现的系统拓扑,用文字描述就是:24GHz毫米波雷达模块负责发射并接收回波信号,输出I/Q两路模拟中频信号;STM32通过片内ADC对这两路信号做同步采样,采样率100Hz;原始采样数据经过滑动窗口处理,提取出呼吸频率、体动能量和有人无人状态;然后STM32把处理结果通过串口发给ESP8266模块,ESP8266以MQTT协议上传到本地服务器或者云平台,同时驱动一个OLED显示屏做现场展示。
这里有个关键点:我选的是“输出原始中频信号”的雷达模块,而不是“直接输出处理结果”的智能模块。有些雷达模块内部已经集成了人体存在检测算法,直接通过串口输出“有人/无人”“运动/微动”这样的指令级数据,开发很方便,但灵活性太差,你拿不到原始的呼吸波形,没法做深度的睡眠分期分析。所以我宁可在STM32里多做一层数据处理,也要把原始信号握在自己手里。
2.2 主控为什么选STM32而不是其他方案
选STM32,最直接的原因是生态。Keil、STM32CubeMX、HAL库这套工具链太成熟了,社区资料多得看不完,遇到问题几乎都能搜到解决方案。我做这个项目时用CubeMX初始化外设只花了十几分钟,剩下的时间全花在算法调试上,这对项目周期来说很关键。
具体型号上,我用了STM32F103VET6,容量256KB Flash、64KB RAM,主频72MHz。这个配置跑100Hz采样、256点FFT、滑动窗统计完全够用。有一点要提醒的是,F103不带硬件浮点单元,做FFT时如果直接用浮点库会偏慢,但256点FFT在72MHz主频下也只是毫秒级的事,只要不追求同时做大量实时处理,完全没有瓶颈。如果后续要上神经网络做更复杂的睡眠分期,那必须换F4系列或H7系列,至少要有FPU。
2.3 雷达模块的选型细节:I/Q输出比你想的更重要
雷达模块我测试过两家,一家是只输出模拟多普勒信号的,一家是输出I/Q两路正交中频信号的。只输出单路信号的问题在于:你不知道目标是靠近还是远离,而且当人体呼吸引起的信号幅度很微弱时,单路信号容易淹没在直流偏置和低频漂移里。
I/Q两路正交信号的物理意义是:发射信号与回波信号的相位差被分解成两个正交分量,你和我的直观理解就是,I路相当于看“左右”,Q路相当于看“上下”,两路合在一起就能唯一确定目标微动的相位轨迹。通过I/Q解调,胸腔微动引起的相位变化可以被精确提取出来,这比单纯看幅度可靠得多。
选型时要注意模块的数据手册里是否给出了中频输出带宽。呼吸信号频率很低(0.1到0.5Hz),但翻身、肢体动作会产生几十到几百Hz的多普勒信号。如果中频带宽太窄(有些模块为了抑制工频干扰会把带宽压到10Hz以内),体动信号就被滤得差不多了,睡眠状态判断会失真。我选的模块中频带宽是20Hz到500Hz,呼吸和体动都在范围内,你选型时可以按这个思路来。
3. 睡眠状态感知的算法链路:从原始I/Q数据到呼吸频率
这套系统算法层面要解决的核心问题,其实可以拆成四层:
- 从I/Q数据里判断床上有没有人。
- 从I/Q数据里提取呼吸波形,算出呼吸频率。
- 从I/Q数据里识别翻身、体动等大幅度运动。
- 综合以上信息,把睡眠过程划分为醒来、浅睡、深睡等状态。
很多人以为睡眠监测最重要的是“测准心率”,其实睡眠分期的本质是“体动频率+呼吸节律”。人入睡后体动次数明显减少,深睡期几乎不动,呼吸也变得平稳规律。所以我把算法重点放在“微动检测+呼吸频率提取”上,而不是死磕心率(毫米波雷达测心率在信噪比不足时非常容易翻车,我后面细说)。
3.1 静止目标下的呼吸信号:为什么幅度大却不靠谱
最先想到的方法是直接看中频信号的幅度:有人呼吸时胸腔起伏,雷达回波的多普勒频移会让中频信号幅度周期性变化,幅度大而且有规律,应该就能算出呼吸频率。
实际跑完数据以后发现这条路有问题。24GHz雷达发射波长为12.5mm,呼吸引起的胸腔位移在1到5mm之间,对应的相位变化也就是几十度,反应到I/Q幅度上确实有变化,但这个变化幅度受距离影响极大:人在0.5米处和2米处,同样的呼吸幅度,信号幅度差好几倍。直接用幅度阈值判断呼吸,环境适应性很差。更麻烦的是,有人在睡觉时会轻微翻身、调整姿势,这些动作产生的多普勒信号幅度远大于呼吸,会直接淹没呼吸波形。
3.2 相位解调:I/Q信号里藏着的才是呼吸波形
正确的做法是:不要把I/Q信号直接当幅度看,而是要把它们组合成相位。
雷达发射信号遇到人体胸腔反射回来,回波相位变化量与目标位移成正比。设I = A·cos(θ),Q = A·sin(θ),那么目标微动导致的相位变化就是:
θ = atan2(Q, I)
每次呼吸,胸腔起伏几毫米,对应相位会周期性变化。把每一帧的相位算出来,再对相位序列做去直流、解缠绕、滤波,就能得到一条跟胸腔位移成比例的呼吸波形。
这段用代码表示就是:
float phase = atan2f(q_sample, i_sample); // 当前相位 float delta = phase - prev_phase; // 相位增量 if (delta > PI) delta -= 2.0f * PI; // 解缠绕 if (delta < -PI) delta += 2.0f * PI; unwrapped_phase += delta; prev_phase = phase;解缠绕那两行特别关键。atan2的输出范围是-π到π,如果真实相位跨越了边界,不做解缠绕的话,相位序列里会出现虚假的2π跳变,后面的滤波就全乱套了。
拿到连续相位序列后,我会先做一次高通滤波(截止频率0.1Hz),把身体缓慢移动、环境温度漂移引起的低频基线去掉,再做一次低通滤波(截止频率1Hz),把高频噪声和工频干扰滤掉。剩下中间这个0.1到1Hz子带里的信号,主要就是呼吸贡献的。
3.3 呼吸频率的计算方法:时域峰值计数和频域功率谱
呼吸波形出来以后,有两种主流方法算呼吸频率:时域上找波峰/波谷,数一段时间内有多少个完整波形;频域上做FFT,找到功率谱里的主峰位置。
我实测下来,时域峰值检测在信号干净时很准,但睡眠场景里经常会有小幅度体动干扰(比如手指动一下、腿微屈一下),波形上会多出一个小凸起,导致误计。频域FFT更稳,因为它本质上是窗口内信号的统计平均,瞬时毛刺对功率谱的影响比较小。
我的具体做法是:对呼吸波形取30秒滑动窗口,做256点FFT(此时需要先重采样或插值到256点,或者窗口就取25.6秒,直接用100Hz采样率得到2560个点,然后截取256点做FFT,这取决于你的窗口设计)。FFT后找0.1Hz到0.5Hz范围内功率最大的谱峰,对应的频率乘以60就是每分钟呼吸次数。
这里必须强调窗函数的作用。直接对截断信号做FFT会引入频谱泄漏,我用的是汉宁窗:
for (int i = 0; i < N; i++) { windowed_input[i] = raw_signal[i] * 0.5f * (1.0f - cosf(2.0f * PI * i / (N - 1))); }加窗后的频谱主瓣更干净,旁瓣泄漏大幅减少,呼吸频率的谱峰辨识度好很多。如果不加窗,安静状态下还能凑合,一旦有体动干扰,频谱里会出现一堆杂峰,逻辑判断就会乱。
3.4 体动检测:短时能量法和滑窗方差法怎么选
呼吸频率是睡眠状态的“底色”,体动检测则是“事件检测”。我的实现思路比较直接:对I/Q信号做高速滤波(截止频率50Hz)后取幅度平方,这个数值物理上对应短时能量。然后我维护两个滑窗:一个1秒短窗,一个10秒长窗。当短窗能量超过长窗能量的3倍,或者绝对能量超过预设阈值时,就认为发生了一次体动事件。
这个方案的好处是不用调一堆频域参数,计算量小,实时性高。我在STM32上用100Hz采样率、每1秒输出一个体动标签,实测下来翻身、起身、挠头这类动作几乎都能抓到,误报主要来自床板被别人坐一下或拍一下床沿这样的外部传递振动,这个在单人床上可以接受。
3.5 睡眠状态机的判定:四种状态的划分逻辑
有了呼吸频率和体动标签,就可以做一个简单的睡眠状态机。这个状态机不需要很复杂,医学上PSG多导睡眠图能把睡眠分成N1、N2、N3、REM四期,但在消费级产品上,能把“醒着、浅睡、深睡”分得八九不离十,体验就已经很好了。
我的判定逻辑是这样:
- 离床状态:多普勒能量长时间低于静默底噪,连续1分钟没有检测到微动信号,判定为离床。
- 清醒状态:短时间内出现多次体动事件,或呼吸频率高于每分钟20次。
- 浅睡状态:体动事件偶发,呼吸频率在12到20次/分钟之间。
- 深睡状态:连续10分钟以上无体动事件,呼吸频率稳定在每分钟12次以下且波动很小。
这套状态机表面上看非常简单,工程上的难点在于状态的阻尼和滞后。比如一个人刚躺下的时候,呼吸频率可能还在18次/分钟,如果立即判成浅睡,过几分钟他呼吸变慢,状态就会跳来跳去。我的做法是:状态切换需要一个“确认时间”,比如从浅睡切到深睡需要连续10分钟满足深睡条件,从深睡切回浅睡只需一次体动事件。这样睡眠报告中不会出现噪声一样的锯齿状态,用户看到的是一条清晰可读的睡眠曲线。
4. 基于STM32的工程实现:从CubeMX配置到核心代码
4.1 CubeMX初始化:外设配置的几处关键细节
硬件连接上,雷达模块的I/Q两路输出分别接到STM32的ADC1_IN0和ADC1_IN1(PA0和PA1),ESP8266的串口接USART1,OLED用I2C1或者SPI1。我用的是SPI接口的0.96寸OLED,刷新速度快一些。
CubeMX里的关键配置项:
- ADC1:开启两个通道,扫描模式,连续转换,分辨率选12位。采样时间可以适当拉长(比如55.5周期),以降低输入阻抗不匹配带来的信号失真。
- 定时器3:配置为100Hz更新中断,在中断服务函数里触发ADC转换。为什么不用循环里delay?因为雷达信号采样需要严格等间隔,用定时器驱动才能保证时序稳定,否则FFT结果里会混入采样抖动噪声。
- USART1:配置为115200、8N1,对接ESP8266。
- 时钟树:我配了72MHz主频,ADC时钟12MHz(ADCCLK过高会增加采样误差)。
这里要特别说一个容易踩的坑:ADC的参考电压。STM32F103的VREF+通常直接接到VDD(3.3V),但雷达模块的中频输出可能只到1.8V或2.5V满幅,如果用3.3V参考,量化分辨率会浪费。我最终的解法是在雷达模块输出和ADC输入之间加了一级运放跟随,并且把直流偏置调到1.65V附近,让0到3.3V的动态范围尽量匹配中频信号的实际摆幅。如果你不想加运放,至少要做电阻分压和直流偏置,保证信号中心在1.65V而不是0V,不然负半周会被ADC削掉。
4.2 数据采集与算法调度的双缓冲机制
ADC采样数据不能直接丢给算法。原因很简单:采样中断每秒100次,FFT窗口需要连续字典数据,而FFT本身计算耗时可能几毫秒。如果在采样中断里做FFT,中断会占掉大把CPU时间,后续采样就延迟了。
我的做法是用双缓冲。ADC的DMA把采样结果轮流写入两个缓冲区,一个缓冲区满了立即切到另一个,同时置一个“数据就绪”标志。主循环里检测到这个标志后,把满的缓冲区交给算法处理,算法处理期间DMA继续往另一个缓冲区写。这样采样和算法计算彻底解耦,DMA在后台搬运数据,CPU只做算法,完美利用STM32的DMA外设。
双缓冲的核心代码逻辑:
uint16_t adc_buf[2][BUF_SIZE]; volatile uint8_t active_buf = 0; volatile uint8_t dma_done = 0; // DMA传输完成中断回调 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { dma_done = 1; active_buf ^= 1; // 切换缓冲区 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf[active_buf], BUF_SIZE); }主循环里,检测到dma_done后,处理的是上一次填满的缓冲区(active_buf ^ 1),这样可以确保在得到结果时,新数据已经正在往另一个缓冲区写,不会覆盖正在被处理的数据。
4.3 算法调度的主循环框架
主循环比较简洁,每轮执行以下步骤:
- 检查dma_done标志,有新的ADC数据块就交给process_radar_frame()处理。
- process_radar_frame()里做的第一件事是I/Q相位解调,得到当前帧的相位值。
- 相位序列进入环形缓冲区,每积累256点(约2.56秒)做一次加窗FFT,更新呼吸频率估计。
- 同时更新体动检测短时能量缓冲。
- 每1秒跑一次睡眠状态机,刷新当前睡眠状态。
- 状态有变化时,通过USART1打包发MQTT数据给ESP8266。
主循环C伪代码:
while (1) { if (dma_done) { dma_done = 0; process_radar_frame(adc_buf[active_buf ^ 1]); if (++frame_count >= 100) { // 1秒 frame_count = 0; update_sleep_state(); } if (state_changed) { mqtt_publish_sleep_data(); oled_refresh(); } } }这里我故意让睡眠状态机1秒才跑一次,而不是每帧都跑。原因是状态判定本身就是时长的统计结果,太快更新只会引入抖动。100Hz的相位计算是每帧都做,但高层判定保持低频更新,符合嵌入式系统“高频采集、低频决策”的原则。
4.4 数据上云与现场展示
睡眠数据我设计成两种方式同时输出:本地OLED显示实时状态,ESP8266走MQTT把结构化数据传到服务器。OLED显示内容很直接:当前睡眠状态(离床/清醒/浅睡/深睡)、实时呼吸频率、体动计数值。这三个字段足以让用户一眼看清当前睡眠情况。
MQTT上报的数据包我做成JSON格式,方便上层解析,形如:
{"device_id":"sleepmon01","state":3,"resp_rate":14.5,"body_moves":2,"ts":1712345678}ESP8266侧用AT指令固件,STM32通过串口发AT指令控制连接。这里提醒一句:如果追求更稳的链路,ESP8266可以考虑用SDK二次开发代替AT指令,AT指令在长时间运行中偶尔会出现指令丢失或响应延迟,需要在STM32侧做超时重试机制。我在实际项目里加了简单的AT应答重试逻辑,连续三个月运行下来比较稳定,基本没有掉线后无法恢复的情况。
5. 调试阶段踩过的坑与稳定性优化记录
5.1 天线摆放角度与覆盖范围的教训
调试初期我把雷达模块平放在床头柜上,正面朝向枕头方向,结果发现侧睡时呼吸波形大幅衰减,几乎不可用。后来查资料才知道,毫米波雷达的微动检测对目标运动方向与雷达视线方向的夹角特别敏感。雷达发射波和回波的多普勒频移来自“径向分量”,如果胸腔微动的方向正好垂直于雷达视线,多普勒频移为零,雷达根本看不见。
正确的摆放方式是让雷达正面斜向下对准人体胸部区域,让呼吸的位移方向(垂直于胸骨表面向外)在雷达视线方向上有一个可观的投影分量。我实测下来,模块倾斜约30度到45度,距离目标0.8到1.5米时,呼吸波形的信噪比最好。如果你做的是侧睡场景,最好在床头左右各放一个雷达,交叉覆盖,或者选用能广角覆盖的雷达天线类型。
5.2 电源纹波对呼吸信噪比的破坏
这可能是所有干扰里最隐蔽也最致命的一个。最开始样机用USB供电,呼吸波形总是时好时坏,用手捏住电源线波形反而变清晰。用示波器看雷达模块的电源引脚,纹波有80mV左右,而且纹波频率出现在100Hz附近(开关电源的二次谐波),正好落在呼吸滤波通带边缘。
呼吸信号在微动检测里本来就只有毫伏级,电源纹波一上来,呼吸波形就被淹没在周期扰动里,FFT主峰经常锁到错误的频率上。解决办法有三层:一是雷达模块用单独的LDO供电,不和MCU数字部分共电;二是电源输入端加LC滤波;三是在ADC采样配置里把采样时间尽量拉长,等效于一次RC低通,抑制高频毛刺。做完这三步后,底噪降了一个数量级,呼吸波形干净了很多。
5.3 串口数据丢帧问题:DMA双缓冲的另一个应用
ESP8266的上行数据发送也踩过坑。最开始我用阻塞方式发送,发一条100字节的JSON,算上AT指令等待和网络握手时间,MCU卡顿明显,甚至有时候发着发着就把ADC的中断处理给卡住了。
后来我把串口驱动也改成了DMA发送加环形发送缓冲区。上层往环形缓冲区写数据,串口DMA空闲时自动把缓冲区里的数据搬出去,上层不阻塞。这个改进对系统稳定性的提升立竿见影,ADC采样和算法计算的时序不再被通信过程干扰。
5.4 呼吸误判的典型场景:谐波干扰与窗户振动
有几天夜里,呼吸频率总是显示在25次/分钟左右,而实际只有14次。排查后发现是窗外大货车经过时带来超低频的机械振动,床板产生共振,雷达探测到的不是胸腔而是整个床面在动。这个频率落在呼吸带内,但波形很不规律。
解决思路是加信号质量评估:在FFT主峰之外,计算主峰能量占整个呼吸频带能量的比例。正常呼吸时,能量集中在一个窄峰上,占比通常在0.5以上;振动干扰时能量分散,主峰占比往往不到0.3。比例低于阈值时,报告“信号质量低”,不更新呼吸频率,等待下一个窗口。
5.5 夜间功耗与长期运行稳定性
整机功耗实测:STM32全速运行约60mA,雷达模块约50mA,ESP8266待机连接时约70mA(发送时会飙到200mA以上)。合计约180mA,5V供电时接近1W。如果拿电池供电,这个功耗并不能撑太久,这也是睡眠监测设备一般选择直插电源的原因。
如果确实要电池方案,建议让ESP8266低功耗模式,只在状态发生明显变化时唤醒上传,而不是每小时持续上报。我实测下来,把上报频率从每分钟一次改成“状态切换时上报+每10分钟心跳”,整机平均电流降到了约90mA,一节2000mAh锂电池(配合升压电路)能支撑约20小时,覆盖一整夜没问题。
6. 连续7晚实测的数据表现与方案的局限在哪
6.1 实测结果汇总
找了一位同事在家里的卧室连续测了7晚,床为1.8米双人床,雷达放在床头右角斜向下45度。下表是其中一晚的数据汇总:
| 指标 | 测试结果 | 备注 |
|---|---|---|
| 呼吸频率中位数 | 14.6次/分钟 | 与医用级呼吸带传感器对比,偏差±1次/分钟 |
| 呼吸频率数据有效率 | 87.5% | 剩余为翻身时段和信号质量低时段 |
| 离床检测准确率 | 100% | 7晚共起身6次,全部命中 |
| 翻身/体动识别准确率 | 74/80次 | 有6次是小幅手臂动作误判为翻身 |
| 深睡时长与手环对比 | 偏差约12分钟 | 手环自身有较大误差,无法断言谁更准 |
呼吸频率的对比验证,我用的是一台借来的医用呼吸感应体积描记设备,同样的场景下对比了20分钟。整体趋势跟随得很好,但每次翻身后的前10秒左右,雷达会短暂丢失呼吸信号,这是姿态快速变化时不可避免的物理现象,算法上需要用状态机缓冲一下,不要让用户看到呼吸频率跳成0。
6.2 单人场景没问题,多人共用和隔床干扰还得再开发
这套系统是针对单人床和“床上只有一个人”的场景设计的。如果夫妻同床,两个人体反射信号会叠加在一起,目前的算法没法区分哪个呼吸信号属于哪个人。三个方向可以拓展:一是利用距离维度的分辨率(需要更高带宽的雷达,比如60GHz甚至77GHz),把人按不同距离分开;二是在算法里对两个呼吸频率做盲源分离(我试过简单的ICA,效果一般,样本一多就飘);三是干脆定位为“床垫级传感器”,不区分人,只看整体睡眠质量数据。
隔床干扰也是个现实问题,如果两张床距离小于1米,左右两个床的人可能会被同一台雷达捕捉到串扰信号。实测1米间距时,我的翻身检测准确率从93%掉到了80%左右,明显被邻床干扰。
6.3 后续可以做的方向:跌倒检测、鼾声与环境联动
既然毫米波雷达的信号质量能做到比较可靠的呼吸和体动检测,那么在同一个硬件平台上再叠加跌倒检测、睡眠呼吸暂停筛查(OSA初筛)等算法,是水到渠成的事。雷达回波里其实还有距离-多普勒的丰富信息,只是我的STM32F103平台跑完整的距离-多普勒二维FFT比较吃力,会卡顿,这更合适由带FPU和DSP的STM32F4/H7或者外挂DSP来做。
另外一个我比较看好的方向是把睡眠数据联动到智能家居:夜里检测到深睡状态后,客厅灯光自动调暗、空调温度自动降半度;早上根据离床事件和呼吸清醒状态触发起居室窗帘打开或咖啡机预热。这种“环境跟着睡眠状态走”的体验,比单纯在手机App里看一条睡眠曲线更有实际价值。
7. 我在整个项目过程中最想分享的几个体会
回头看整个项目的开发过程,有几个体会想单独拎出来说。
第一,选传感器平台时,“能输出原始信号”远比“内置算法方便”重要。我见过很多开发者选带算法的雷达模块,开发阶段进度飞快,但到了真正做差异化功能时,却发现被模块厂商的算法黑盒卡死了,要什么数据都没有,只能换硬件重来。原始数据在手里,算法想怎么改都行。
第二,呼吸检测这种弱信号提取任务,真正决定成败的不是算法多高级,而是前端的信号链路有多干净。ADC参考电压、电源纹波、天线摆放角度、运放失调电压,任何一个细节做得糙一点,后面算法再精巧都是白搭。我后来调电源的功夫比调算法花的还多,但效果立竿见影。
第三,睡眠分期的算法不要贪心。医学级PSG有EEG信号才敢做精确的REM分期,单靠毫米波雷达的体动和呼吸数据,能把“离床、清醒、浅睡、深睡”四态做准已经非常优秀了。我在测试中还尝试过加入心率变异性(HRV)的特征去辅助判断REM期,但由于毫米波雷达在被子覆盖下测得的心率信号质量不稳定,最终放弃了这个特征。先保证基础状态可靠,再去碰高难度的分期特征,这个顺序不能颠倒了。
这个项目目前还只是一个功能完整的原型,距离产品化还有不少路要走,比如外壳的声学/电磁兼容设计、多场景的老化测试、真正的医学级算法验证。但如果你也想动手做一套智能睡眠监测装置,按照这篇文章的思路从雷达模块选型到STM32数据处理,再到简单的状态判定,整个流程完全可以自己复现一遍。夜里的呼吸波形第一次在串口里画出稳定曲线的那一刻,你会发现前面的坑没有白踩。