1. 这个项目的由来:PLFM_RADAR到底解决什么问题
最开始动这个念头,其实是源自一次挺尴尬的踩坑。当时我一直在做室内人体存在检测的嵌入式方案,先用了PIR红外传感器,结果夏天室温一上来,人体辐射和背景温差区分度骤降,人坐在工位上一动不动超过十分钟,输出就飘了;后来又试了摄像头方案,识别率倒是上去了,但隐私问题在办公环境实在绕不开——没有人喜欢自己工位上方的角落挂着一个一直“睁着眼睛”的东西。折腾一圈之后,我把目光转向了雷达,也就是后来被命名为PLFM_RADAR的这个项目。
PLFM其实是Platform的缩写,这个项目从一开始就不是想只做一个能用就行的原型,而是把它当成一套可复用的雷达感知平台来设计,用来完成室内人员的定位、测距、存在状态检测,还有最基础的微动感知。整块板子基于调频连续波体制(FMCW),探测范围设定在10米以内,距离分辨力做到了厘米级,输出帧率10Hz。这个参数组合不是随手定的,而是根据实际应用场景反推出来的:室内房间的对角线距离一般不会超过10米,人体走动速度低于3m/s,呼吸带来的胸腔起伏幅度大约在几毫米到十几毫米之间,如果连这个级别微动都要能捕捉到,那么距离分辨力和多普勒解算就得按对应的量级来设计。
这个项目适合谁参考?如果你是做嵌入式感知、智能家居里的存在检测、或者是想在低成本方案里引入毫米波雷达做人员感知,那么这套设计思路和踩坑记录应该有挺大的参考价值。因为市面上雷达模组和开发板有不少,但大多数资料要么过于偏重理论推导,要么只给了API调用示例,真正讲清楚中间信号处理链路怎么落地到单片机上、以及实测会遇到哪些问题的内容很少。这篇文章我打算从头梳理PLFM_RADAR的完整设计过程,包括硬件选型、信号处理链路、嵌入式移植,以及实际测试中的数据和那些让我熬夜排查的bug。
2. 硬件架构设计:为什么最终选了“雷达前端模组 + MCU”而不是射频自研
硬件部分的决策过程比我想象中要长,主要纠结的点是:要不要自己画射频收发链路。坦白说,以我的能力做60GHz的射频前端设计,不是不可能,但风险和成本都太高了,一片毫米波收发芯片的BOM成本、PCB板材要求和调试设备门槛都不是小数目。最终我选择了市售的60GHz雷达前端模组作为核心感知器件,配合一颗主控MCU来完成数据采集和算法处理。
2.1 模组选型:数据接口比指标参数更要紧
选模组的时候,很多人的第一反应是看探测距离和多普勒灵敏度,但实际上对于嵌入式集成来说,数据接口的友好程度才是最影响开发进度的因素。同样是60GHz毫米波前端,有的模组直接输出原始中频ADC数据,需要通过外部处理器做距离FFT、速度FFT这些算法;有的模组则内置了部分处理,直接输出目标点迹。PLFM_RADAR最终选的是一颗输出原始I/Q数据的模组,理由有两个:
第一,原始数据意味着算法完全可控。我可以根据自己的需求自由调整距离分辨力、检测门限、目标判定逻辑,不受模组内置策略的限制。第二,这颗模组通过SPI接口输出数据,带宽约12Mbps,对主控来说完全吃得下,不需要昂贵的高速接口。
这里要提醒一个容易踩的坑:模组的天线方向图和雷达截面(RCS)的关系。FMCW雷达检测人体的能力和天线波束宽度密切相关,波束太窄会导致靠近雷达两侧的区域有盲区,太宽又会拉低增益。我选的是90度方位角、约30度俯仰角的微带阵列天线,这个配置在普通室内场景下比较均衡,天花板安装和桌面放置都能覆盖合理的范围。
2.2 主控MCU的算力估算:为什么STM32H7够用
然后是主控选型。很多人一听说雷达信号处理,第一反应是上FPGA或者DSP,其实对于单路FMCW雷达、点数不多的场景,一颗带浮点单元的高性能MCU完全够用。PLFM_RADAR的主控用的是STM32H750,主频480MHz,带双精度FPU,板载32MB外部SDRAM用来存中频数据帧。
我做了个很粗略的算力估算,以单帧为例:中频采样率4MSPS,一个chirp采样点数是512点,一次快扫包含128个chirp,那么一帧的原始数据量是512×128×2(I/Q两路)×2字节,大约256KB。距离FFT要做128次(每个chirp一次512点复数FFT),速度FFT要在距离维基础上再做128点FFT,总共256次FFT。以Cortex-M7的FFT库效率来估算,512点复数FFT大约需要50~80微秒,128点FFT大约12微秒左右,整帧处理耗时可以控制在15毫秒以内,加上CFAR检测和目标提取,一帧总时间预算在40毫秒以内,而10Hz帧率要求是100毫秒一帧,所以算力有明显的余量。这个结论就是选MCU而不选FPGA的直接依据:明明单核MCU就能跑完,没有必要给自己增加FPGA开发的复杂度。
2.3 板级布局和电源的几条实践经验
硬件布局方面有几点实际经验值得写下来。首先是电源噪声,雷达前端对电源纹波非常敏感,尤其是本振的相位噪声会直接影响中频信噪比。实测下来,直接用DC-DC给雷达模组供电,底噪会比用LDO供电高出大约6dB,这个差距在近距离检测时不太明显,但一旦目标距离超过6米,检测概率就会明显下降。所以PLFM_RADAR上单独用了一路低噪声LDO给雷达模组供电,与主控的数字电源严格分开,PCB上做了开槽隔离。
其次是晶振的问题。雷达模组和MCU如果共用同一颗参考时钟,在某些频段会产生串扰,表现为中频信号里出现固定的谱线。我的处理方式是让雷达模组使用独立的25MHz温补晶振,MCU的时钟系统完全独立,实测这种固定干扰谱线基本消失了。如果你的设计里只能用一颗晶振,那么在PCB布局上至少要把时钟走线做包地处理,并远离中频输出走线。
3. 雷达信号处理的完整链路:从ADC数据到目标坐标的每一步
信号处理是PLFM_RADAR的核心,也是最容易让初学者一头雾水的部分。这一节我按数据流向逐步拆解整条链路,每步都说明“为什么这么做”,而不是只贴公式。
3.1 FMCW测距的第一性原理:一个容易被忽略的换算
FMCW雷达发射的是频率随时间线性升高的chirp信号,回波信号与本地发射信号混频后,得到的中频信号频率正比于目标距离。换算关系是:中频频率 = 距离 × 距离分辨率因子。对于带宽B=3GHz的60GHz FMCW雷达,扫频时间Tchirp = 128微秒,距离分辨率是c/(2B)=5cm,中频频率与距离的对应关系大约是每米约6.7kHz。为什么要自己算这个?因为很多模组手册只给原始ADC数据,你需要自己确定频率轴到距离轴的映射系数,这一步算错的话后面所有距离读数都是错的。
我在调初始版本的时候犯过一个错误:直接用模组示例代码里的默认距离分辨率参数,结果测出来1.5米处的目标显示成1.62米,排查很久才发现是厂商固件里把有效带宽标成了2.7GHz而不是实际扫频范围3GHz。从那以后我坚持自己用已知距离的强反射目标来标定距离轴偏移量,固定放置一个金属板在2米和5米处,记录峰位反算系数。
3.2 距离FFT和速度FFT:数据组织方式是关键
原始中频数据在内存中的排列方式是[chirp序][采样点序],做距离FFT时,对每一个chirp按采样点维度做512点FFT,得到的是距离-慢时间二维数据;再做速度FFT时,对同一个距离单元、跨chirp做128点FFT,得到的就是速度维信息。两维FFT的顺序不能反,这也是很多初次接触雷达信号处理的人最容易搞错的地方。
距离FFT之后,目标的能量会汇聚到对应的距离单元上;速度FFT之后,运动目标的能量会转移到对应的多普勒单元。静止目标的多普勒频率为零,因此落在速度轴的中心点上。这个特性后面会结合静止目标检测问题再展开。
3.3 CFAR检测:固定门限为什么不可靠
FFT做完之后,理论上在频谱图上找峰就行了,但实际环境里不可能这么理想。比如5米处有一面墙,墙的反射能量可能比远处的人体回波还强;再比如雷达正下方放了一个金属垃圾桶,它产生的固定杂波可能一直霸占某个距离单元。如果使用固定门限,要么误报多,要么漏检多。PLFM_RADAR里实现的是单元平均恒虚警检测(CA-CFAR),每个待测距离单元的周围取左右各8个参考单元,计算平均值后乘以一个门限系数,再与待测单元能量比较。
门限系数的选择需要平衡检测率和虚警率。我最终采用的系数是12dB,也就是参考平均能量的约4倍。经过半年的室外加室内实测,这个值在10米范围内虚警率约为每小时1次,而在强反射目标附近误报的概率也很低。需要指出的是,CFAR最怕两个目标距离很近,比如两个人并肩站在距离分辨单元5厘米以内,这种情况下参考单元里会混入目标自身的能量,导致门限被抬高,弱目标被淹没。室内部署时要接受这个物理限制,没有算法能在距离分辨率之内把两个目标稳定区分开。
3.4 目标角度估计:单通道雷达的极限
PLFM_RADAR的前端模组是单发单收(1T1R)天线配置,这意味着它本质上没有测角能力,只有距离和速度信息。那么怎么得到目标在房间内的二维位置?我的方案是利用距离-多普勒数据辅以应用层逻辑来近似定位,单纯径向距离只能告诉你目标在雷达前方多远,不能告诉你在左边还是右边。
如果期望获得真正的方位角信息,硬件上要换1T2R或2T4R的多通道模组,用相位干涉法测角。这个改动会显著提升定位能力,但也会带来更大的数据量和校准复杂度。PLFM_RADAR保持单通道是有意为之——项目定位是检测“人存在与否”和“大致距离、移动状态”,而不是做精准空间定位。这里必须诚实说明硬件边界,不然后续应用开发会被“为什么没有方位角”反复质问。
4. 嵌入式端的系统工程:缓存、时序与实时性优化
算法在PC上跑通只是第一步,真正让PLFM_RADAR成为一套可用系统的是把它搬到MCU上的过程。这一章记录几个关键工程决策。
4.1 DMA乒乓缓存:谁也别想阻塞雷达数据流
模组通过SPI以突发模式输出一帧原始数据的时候,速度接近每秒4MB,如果让CPU在中断里逐字节搬运,那么整个系统基本腾不出时间做算法。PLFM_RADAR使用的是两个DMA缓冲区交替接收,当DMA写满缓冲区A时,自动切换到缓冲区B继续接收,同时给CPU发出完成中断,CPU处理缓冲区A里的数据;等下次中断到来时,处理缓冲区B。这就是标准的乒乓缓冲结构,它能保证数据采集不丢帧、算法处理不吃力。
实际调试这个部分的时候有个细节特别容易忽略:DMA中断服务函数里要尽快清除标志位,但不要做任何复杂操作,只做缓冲区指针切换和置一个事件标志,真正的处理放到主循环或者RTOS任务里去。如果把FFT放到中断里跑,一旦FFT耗时超过相邻SPI传输间隔,下一次DMA完成中断就会丢失,整个链路就像堵车一样越来越乱。
4.2 帧同步和时间戳:数据对了才有意义
雷达数据只有配合时间戳才真正有价值。单个帧本身可以算出目标的径向距离和速度,但如果不知道这一帧对应哪个时刻,那么跨帧的轨迹关联、速度平滑、目标计数就都无从谈起。PLFM_RADAR在每个数据帧的头部打上MCU的64位微秒级时间戳,同步基准用RTC和系统时钟交叉校准。这样后续做目标追踪或者与摄像头等其他传感器做时间对齐时,才有一个可靠的公共时钟参照。
其实这里还有一个容易忽略的点:模组输出的每一次SPI突发数据并不一定对齐物理帧的边界。有的模组设计上是分片传输,比如一帧分成8次SPI读操作。你必须根据模组手册里的帧头字段来拼接数据,而不是默认“每次中断一帧”。我最初版本里就犯了想当然的错误,导致数据拼接错位,CFAR检测到的“目标”实际上是拼接出来的虚假频谱峰。
4.3 实时性预算表:每一毫秒都算得明明白白
为了让系统稳定运行在10Hz帧率,我把每一帧的处理耗时做了明确预算,通过逐段计时不断优化。
| 处理阶段 | 耗时 | 说明 |
|---|---|---|
| 数据搬移与预处理(窗函数、去直流) | 4.2ms | 512×128点浮点乘加,开启FPU后很快 |
| 距离FFT(128次512点) | 6.5ms | 使用STM32H7的硬件三角函数加速器 |
| 速度FFT(512次128点) | 3.1ms | 距离-多普勒图生成 |
| CFAR检测与目标提取 | 1.2ms | 遍历所有多普勒通道的峰值 |
| 数据打包与输出 | 0.8ms | 通过UART以JSON格式输出目标列表 |
| 合计 | 15.8ms | 低于100ms的帧周期预算 |
做完优化后整帧耗时16ms左右,CPU占用率约15%,剩余算力可以留给上层应用,比如后期加微多普勒手势识别或者房间占用状态机。不要一开始就上RTOS的多任务设计,先把裸机主循环跑通,用时间戳打印每一阶段的耗时,找到瓶颈之后再决定要不要上多线程架构。
5. 实测数据与踩坑记录:真实性能往往和手册不一样
这一章是偏差最大的部分,也是我认为这篇文章最有价值的部分。理论计算再准确,实测环境的总会带来一些书本上没说的事。以下数据都是在普通办公室/会议室环境中测得,没有经过任何实验室级隔音、吸波处理。
5.1 测距精度与稳定性实测
目标是一个人从1米处匀速走向7米,再返回,共5个来回,统计每帧输出的距离值与真实位置的偏差:
| 真实距离 | 平均测量距离 | 标准差 | 最大偏差 |
|---|---|---|---|
| 1.0m | 1.03m | 1.8cm | 4cm |
| 2.0m | 2.01m | 1.5cm | 3cm |
| 3.0m | 3.05m | 2.2cm | 6cm |
| 5.0m | 5.04m | 3.0cm | 8cm |
| 7.0m | 7.11m | 4.5cm | 12cm |
远距离偏差增大主要来自两个因素:一是随着距离变远,回波信噪比下降,CFAR门限波动导致峰值对应的距离单元发生偏移;二是多径效应在5米以上更容易让峰值展宽,重心计算受到干扰。实际使用中,如果只做存在检测,这个精度完全够用;如果要做跌倒检测里的距离判断,建议配合多帧平滑滤波,可以进一步压到3厘米以内。
5.2 静止目标检测失败:一个经典的“挂尸”问题
FMCW雷达的原始输出对完全静止的目标不敏感,因为速度FFT上静止目标的多普勒频率为零,其能量与墙壁等静态杂波混在一起。如果直接把静态杂波滤除(高通滤波),那么静止的人也会跟着消失。这就导致一个局面:房间里安安静静坐了一个小时的人,在雷达看来可能“不存在”。
解决这个问题有两种实用的路线。第一种是短时驻留检测,利用人体微动——哪怕保持静坐,呼吸带来的胸腔起伏依然会产生微小的多普勒调制,虽然幅度比运动目标小得多,但在距离FFT上对应距离单元的能量会出现周期性起伏。处理方法是观察距离单元的慢时间序列,做能量方差统计,如果某个距离单元在几秒内的能量波动幅度超过一定阈值,就认为有活体存在。PLFM_RADAR把这个阈值设为0.8dB,实测在3米内静坐呼吸检测成功率约95%。
第二种路线是相位差分检测,利用相邻chirp之间的相位变化来放大微小位移。这种方法灵敏度极高,但实现复杂、容易受低频抖动干扰。我的建议是:如果应用场景就是“检测人是否在房间”,用第一种路线足够,还能省下不少算力。
5.3 多径反射和误报排查:一次让我怀疑人生的调试
有一阵PLFM_RADAR在会议室里持续在2.5米处报出一个“目标”,但实际上那个位置空无一人。排查过程让我深刻体会到多径反射的麻烦。那个会议室的一角放了一根朝天的不锈钢旗杆,雷达的电磁波打到旗杆上形成强反射,反射波再经由地面二次反射回到雷达,等效路径长度恰好比真实距离多出一截,形成幽灵目标。后来我把旗杆挪走后,误报立刻消失。
这说明一个现实:强反射体加二次反射路径,随时可能生成假目标。针对这类问题,我在算法里增加了一个多帧连续性校验——只有同一个距离单元连续超过3帧都存在目标,才上报为有效目标;瞬时出现的孤立峰值一律丢弃。同时,部署时要尽量避免雷达前方存在大面积的金属物体,特别是灯架、不锈钢装饰件、玻璃幕墙框架这类东西。
5.4 温度漂移:不可忽略的系统性误差
60GHz雷达对温度比较敏感,特别是本振频率随温度漂移会引起中频频率偏移,测出来的距离随之缓慢变化。PLFM_RADAR在北方冬天的室内和夏天没有空调的房间里,同样的2米距离,读数能差出近40厘米。这个漂移是温度的函数,不是随机噪声,所以可以通过查表校准。我在固件里内置了一个温度-距离补偿表,每隔30秒读取一次雷达模组的温度传感器,对距离读数做线性修正。校准之后,不同温度条件下的测距误差重新回落到了5.1节表格里的水平。
6. 后续可以继续深挖的几个方向:PLFM_RADAR的上限不止于此
项目运行稳定之后,我开始考虑那些“加了会显著增值”的方向。这里列出的三个方向都基于同一个判断:硬件平台本身不需要大的改动,主要是算法层面的升级。
6.1 微多普勒分析:从“检测到人”升级到“识别人”
微多普勒是目标在整体运动之外,身体部位微小振动产生的附加多普勒调制。人体的呼吸、心跳、手臂轻微晃动都会在速度谱上留下不同于整体运动的边带特征。PLFM_RADAR现有的10Hz帧率对呼吸检测够用,但心跳检测需要20Hz以上的有效帧率,所以要升级的话,第一件事是把chirp周期缩短、帧率提高,然后对慢时间序列做带通滤波,提取呼吸和心跳频率成分。这个方向非常适合做成室内体征监测的基础模块。
6.2 多雷达协同:把“有存在”变成“在哪”
单个单通道雷达只能测径向距离。要想得到房间平面的二维定位效果,可以用两个PLFM_RADAR分别放在房间的两个角,通过通讯协议交换各自的目标距离,然后用双基地交汇的方式解算出平面坐标。关键在于两个雷达的时间同步和距离数据标注的对应关系。实测两个雷达协同解算出来的定位精度大约在30~50厘米,对于“人躺在卧室的床上还是站在窗边”这种粒度的判断,足够用了。
6.3 与摄像头系统的多模态融合
雷达虽然不怕黑、不侵犯隐私,但它的输出分辨率低,分辨不了“坐着的是人还是两个抱在一起的娃娃”。摄像头恰恰反之。把雷达检测到目标距离和运动信息作为触发条件,引导摄像头只在对准雷达目标区域时进行抓拍识别,可以最大程度减少摄像头持续工作的隐私争议,也大幅降低视觉算法的算力消耗。这种融合策略目前已经在一些高端安防产品里出现,PLFM_RADAR的上位机协议里从一开始就预留了目标坐标输出接口,对接这套方案会比较顺手。
7. 一些实际使用中的经验之谈
算上最初的原型验证板,PLFM_RADAR前前后后改了三版硬件、重写了四次固件。有个体会值得说一下:这个项目真正难的从来不是单个算法,而是把算法放到一颗单片机上稳定跑起来,还要应对真实环境中各种不规则的东西——墙上的金属画框、桌上的保温杯、雨天窗玻璃上的水膜,这些都会变成雷达眼前“看不见的妖怪”。
如果你也打算做类似的产品,我的建议是先把需求里“到底要检测什么目标、目标多大会动多大范围、允许误报几次”这三件事想得非常具体,再去选硬件。因为雷达的参数设计几乎全是由这三个问题推导出来的。设备部署位置也要提前定,是吸顶还是壁挂、高度多少,会直接影响天线俯仰角和检测盲区,这些在软件里虽然能通过调门限补救一部分,但补救程度有限。
最后分享一个小技巧:在调试算法的时候,不要只盯着实时输出的目标列表,建议把距离-多普勒谱的二维可视化图也截下来存着。很多时候实时跑出来的结果看起来是“反正不准”,但到底为什么不准,只有回头看谱图才能发现——是峰值展宽、是CFAR门限抬升、还是静态杂波残留,一眼就能分辨。PLFM_RADAR的上位机工具里就一直保留着谱图回放功能,这个习惯省掉了我大量的重复实验时间。