刚接触毫米波雷达的人第一次打开24GHz雷达模块的datasheet时看到FMCW三个字母大概率会愣一下。明明叫“连续波”雷达却能同时测距离和速度这跟教科书里那个只靠多普勒频偏测速的连续波雷达到底差在哪我最近在用24GHz FMCW模块给单片机小车做测速和避障后面又顺手把一套77GHz雷达接到摄像头做目标融合把这个链路从高频前端一路追到单片机里的FFT代码才算把FMCW测速彻底捋顺。这篇文章就围绕FMCW毫米波雷达测速这一个主题把原理、参数设计、硬件选型、C实现和实测标定串起来讲给想自己搭测速系统或者在研究雷达测速算法的朋友一个可以直接参考的版本。1. 为什么FMCW能同时拿距离和速度它跟多普勒雷达不是一回事1.1 单频连续波雷达只能测速度FMCW多了一个维度在真正上手雷达模块之前我曾经想当然地以为所谓测速就是靠多普勒效应求相对速度就像超市感应门、雷达测速枪那样发一个固定频率的信号出去等它弹回来读频偏速度就出来了。这个思路没错但它只适用于单频连续波雷达也就是CW雷达。CW雷达有个致命问题它测不到距离。因为固定频率回波的相位随距离变化但你没法仅凭一个频点判断目标在40米还是4米只能知道它动了没有、动得多快。这种雷达用来做门禁感应、简单测速枪可以但放到公路上就麻烦了——你不知道目标从哪来、在哪个距离自然也就分不清正在测的到底是哪辆车。FMCW的全称是Frequency Modulated Continuous Wave线性调频连续波。它把发射频率做成随时间线性变化的斜坡这个斜坡在行业里通常叫chirp。发射频率在扫回波信号回来后会有一个时间延迟把回波和本振信号一混频就能得到一个差频也就是常说的拍频。这个拍频的大小和目标距离直接相关。与此同时目标还在动每个chirp之间的回波相位在持续累积变化而相位变化率就是多普勒频率。于是FMCW就在同一套微波前端里同时完成了“用差频测距离”和“用相移测速度”两件事。我见过不少刚入门的人把FMCW理解成“多普勒雷达的增强版”其实更准确的说法是FMCW把时间测量转换成了频率测量。光速太快直接测电磁波往返时间在大多数场景下不现实但把延时折算成差频之后中频信号只有几百kHz甚至更低ADC和单片机都很好处理。这也是FMCW能在车载雷达、工业液位计、周界安防里大面积普及的根本原因。1.2 一次扫描能拿到什么距离-速度二维矩阵如果调制方式用的是锯齿波也就是发射端反复发出线性上升的chirp信号接收端每个chirp都会得到一个中频信号。对单个chirp内的采样点做一次FFT得到的每个频点对应一个距离门这叫快时间FFT也叫距离FFT。连续发射N个chirp对同一个距离门里的N个复数采样点再做一次FFT就叫慢时间FFT输出对应的是多普勒频率也就是速度门。两次FFT之后得到的是一个二维矩阵横轴是距离纵轴是速度每个格子的能量代表该距离和该速度上是否有目标。不同雷达厂商对这个矩阵的叫法不太一样TI的文档里叫Range-Doppler Map简称RDM有的SDK里叫Range-Doppler Heatmap。你在单片机上做测速本质上就是维护并搜索这个矩阵。理解了这张二维地图后面所有算法都顺了。这里要特别提醒一点距离FFT的输出一定是复数不能只保留幅值。因为慢时间FFT需要用到同一个距离门上的相位信息。相位丢了多普勒就没了。所以中间数据必须用 complex 类型存这在单片机内存规划上是第一个要注意的地方。2. FMCW测速的关键参数设计先定参数后面才不返工2.1 距离分辨率由带宽决定速度分辨率由帧时间决定很多朋友拿到雷达模块第一反应是找上位机把点云数据读出来但真正自己写测速程序时参数设计才是最容易被忽略的一步。chirp带宽、chirp周期、chirp数量、ADC采样率这四个参数基本锁死了系统性能后面改代码是救不回来的。我把最核心的公式整理成下面这张表做参数设计时直接对照它算。参数公式说明距离分辨率ΔR c / (2B)B是chirp带宽带宽越大分辨距离越细最大测量距离Rmax c·f_IFmax / (2S)受ADC采样率和中频滤波器带宽限制速度分辨率Δv λ / (2·N·Tc)N为chirp数量Tc为chirp周期最大不模糊速度vmax λ / (4·Tc)超过这个速度多普勒频率会折叠距离分辨率这条最好理解带宽越大距离分辨率越高。24GHz频段在ISM应用里通常能拿到200到250MHz带宽对应距离分辨率大约0.6到0.75米。77GHz车载雷达带宽更大能做到更高分辨率。速度分辨率则取决于整个帧的时间长度因为慢时间FFT的频率分辨力等于1除以总观测时间。N个chirp每个chirp周期Tc那总帧时间就是N·Tc所以Δv λ/(2·N·Tc)。最大不模糊速度更关键。慢时间FFT的采样率等于1/Tc根据奈奎斯特采样定理能无模糊检测的多普勒频率上限是1/(2Tc)换算成速度就是λ/(4Tc)。如果目标实际速度超过这个值它的多普勒频率会折叠到低速区域你会看到一辆明明开得很快的车测出来却只有几km/h。这个问题在后面的代码实现里必须专门处理不能只靠提高Tc来解决因为Tc一变大最大不模糊速度反而变小了这是互相拉扯的两个指标。2.2 一个24GHz模块做40米测速的设计实例之前有个项目要用24GHz毫米波雷达模块给小车测速指标是能测40米范围内的目标径向速度范围要覆盖0到30m/s也就是大约108km/h。这类需求在校园物流车、园区安防上非常典型。我们按上面的公式实际推一遍参数。先定带宽B 200MHz那么距离分辨率就是ΔR c/(2B) 3×10^8 / (2×2×10^8) 0.75米这个分辨率在40米范围内够用两个相距0.75米以上的目标能够从距离维分开。再来定chirp周期Tc。Tc不能太长否则最大不模糊速度不够也不能太短否则每个chirp里的采样点数太少距离FFT效果差。我们取Tc 100微秒24GHz对应的波长λ约等于0.0125米于是vmax λ/(4Tc) 0.0125 / (4×100×10^-6) 31.25m/s正好覆盖0到30m/s的需求留了一点余量。接着取chirp数量N 64那么速度分辨率Δv λ/(2·N·Tc) 0.0125 / (2×64×100×10^-6) ≈ 0.98m/s如果不做插值两个速度相差1m/s以内的目标在速度维上很难区分。实际测速时我又加了一个抛物线插值把速度峰值精度提升到0.2m/s左右。ADC采样率也很好算。最大40米距离对应最大拍频f_IFmax S × 2Rmax/c (B/Tc) × 80/3×10^8 2×10^12 × 2.67×10^-7 ≈ 533kHz模数转换器采样率至少要大于两倍的533kHz也就是差不多1.07MHz。实际我选了2Msps一个chirp内能采200个点再做256点距离FFT。这套参数跑下来一个帧的观测时间是64×100微秒 6.4毫秒测速更新率能到150Hz给小车控制用完全够。3. 从天线到ADC24GHz模块和单片机小车的硬件链路怎么搭3.1 24GHz和77GHz到底怎么选做测速项目第一件事不是看算法而是选频段。24GHz和77GHz是目前毫米波雷达最主流的两个频段但它们的定位差距很大。24GHz频段的好处是模块成本低天线尺寸大手工焊接和调试相对容易很多国产模块还直接集成了MCU串口输出目标的速度和距离。缺点是可用带宽有限距离分辨率做不高而且体积大不适合对天线尺寸敏感的应用。77GHz频段的车载雷达已经非常成熟4D毫米波雷达基本都是这个频段带宽能做更大距离分辨率能到几厘米级别但射频前端和天线设计门槛高很多模块要整板买调试仪器也更贵。如果你只是给单片机小车做40米以内的测速或者做一个教学演示24GHz的集成模块完全够用。但如果你要检测行人和车辆目标并想把点云做得足够密那还是老老实实用77GHz方案。我见过有人用24GHz模块做行人检测距离分辨率0.75米一个行人反射点本来就弱再加多径干扰结果就是虚警率居高不下。3.2 天线通道数量对测速结果的影响硬件上最容易忽略的是天线通道数。很多低端24GHz模块只有一发一收也就是1T1R。这种配置只能测距离和速度完全测不了角度。为什么因为单根接收天线只能拿到目标的距离和速度信息但目标从哪个方向来需要多根天线之间接收信号的相位差来求解方向。要做目标检测至少需要1T2R也就是一个发射天线加两个接收天线这样可以通过两组回波的相位差解出方位角。这也是很多车规雷达入门配置是1T3R或2T4R的原因。通道数越多雷达等效的虚拟孔径越大角度分辨率也就越高。4D毫米波雷达之所以能测高度本质上就是用了MIMO技术通过多发多收形成更大的虚拟阵列仰角维才被“撑”出来。对纯测速来说单通道也不是不能用。速度信息藏在相位变化里一根天线就够了。但测速系统一旦遇到多目标比如公路上同时有车和行人单通道就没有足够信息去区分它们的角度位置只能靠距离和速度两个维度去筛。实际做下来1T2R是底线。3.3 单片机端的数据通路和内存规划毫米波雷达模块和单片机之间最常见的数据接口是SPI、LVDS和串口。24GHz模块很多已经把中频处理和FFT做在了内部直接输出目标列表这种对单片机最友好MCU只需要做应用层逻辑。但如果你要自己写FFT算法或者模块输出的只是ADC原始数据那数据量就要仔细算了。按前面2.2节那个例子一个chirp有200个采样点64个chirp就是12800个复数采样点。复数分I/Q两路各占2字节原始一帧就是12800×2×2 51.2KB。这个量级对STM32H7这类单片机来说还能承受但对普通STM32F103就非常紧张。所以实际项目里我一般不会把全部原始数据都存下来而是采用流式处理每个chirp到达后立刻做距离FFT只保留距离FFT谱丢掉时域原始点。这样内存占用能从51.2KB降到256×64×2字节也就是32KB左右而很多单片机都能轻松容纳。如果你在选单片机建议关注三点一是ADC采样率和位数至少2Msps、12位以上二是RAM大小至少要有32KB以上给雷达数据用三是浮点运算能力。带FPU的Cortex-M4和Cortex-M7跑FFT会快很多否则就得把数据转成定点数用Q格式运算代码复杂度会上一个台阶。4. 单片机上的C测速代码从原始数据到速度值的完整流程4.1 距离FFT和多普勒FFT的数据组织方式把原始ADC数据变成距离-多普勒矩阵是测速算法的第一个关键步骤。很多教程里讲二维FFT画起来很简单但落到单片机上要特别注意数据搬移和内存复用。下面这段代码展示的是我常用的处理流程先对每个chirp做距离FFT再把距离谱按距离门重新组织最后对每个距离门做多普勒FFT。// 参数定义每个chirp采样点数N_RANGEchirp数量N_DOPPLER const int N_RANGE 256; const int N_DOPPLER 64; // 输入rawData[chirpIndex][sampleIndex] // 输出dopplerMap[rangeBin][dopplerBin]幅度谱 // 第一步每个chirp做距离FFT Complex rangeData[N_RANGE]; for (int chirpIdx 0; chirpIdx N_DOPPLER; chirpIdx) { for (int i 0; i N_RANGE; i) { rangeData[i].real rawData[chirpIdx][i]; rangeData[i].imag 0.0f; } applyWindow(rangeData, N_RANGE, HANNING); // 加窗抑制距离维旁瓣 fft(rangeData, N_RANGE); // 基2 FFT需要自己实现或移植 // 把距离FFT结果暂存到距离多普勒缓冲区 for (int rangeBin 0; rangeBin N_RANGE; rangeBin) { rangeDopplerBuffer[rangeBin][chirpIdx] rangeData[rangeBin]; } } // 第二步对每个距离门做多普勒FFT Complex dopplerData[N_DOPPLER]; for (int rangeBin 0; rangeBin N_RANGE; rangeBin) { for (int dopplerIdx 0; dopplerIdx N_DOPPLER; dopplerIdx) { dopplerData[dopplerIdx] rangeDopplerBuffer[rangeBin][dopplerIdx]; } applyWindow(dopplerData, N_DOPPLER, HANNING); fft(dopplerData, N_DOPPLER); // 计算幅度谱存到dopplerMap for (int dopplerIdx 0; dopplerIdx N_DOPPLER; dopplerIdx) { dopplerMap[rangeBin][dopplerIdx] mag(dopplerData[dopplerIdx]); } }这里有两个细节值得说。第一是FFT输入顺序。雷达数据到达的顺序是chirp优先也就是一个chirp的采样点连续存放但第二步需要的是“同一个距离门、不同chirp”的数据所以你必须做一次矩阵转置。这个转置在内存里必然导致反复读写我在STM32上测试时发现直接按内存顺序访问比随机访问快了将近一倍。如果有条件可以把第一个循环和第二个循环的存储布局提前设计好减少转置开销。第二是加窗。距离FFT和多普勒FFT我都加了汉宁窗。每个chirp的时间有限信号被截断FFT一定会产生频谱泄漏。如果不加窗强目标的旁瓣会把附近弱目标淹没。汉宁窗对测速场景足够它把主瓣宽度略微展宽了一点但换来的是旁瓣降低约30dB这个代价非常值得。4.2 从距离-多普勒矩阵里找目标峰值搜索和CFAR门限二维FFT做完接下来就是从距离-多普勒矩阵中提取速度。最简单的方法是全局峰值搜索把幅度谱里最大的点找出来然后由它的距离门和多普勒门反算出距离和速度。但这只适合单目标、高信噪比的场景一旦有两个目标或者有异常噪声尖峰直接取最大值就错了。更可靠的做法是CFAR检测。CFAR全称恒虚警率检测它的核心思想是给每个检测单元动态计算一个门限被检测单元周围有一圈参考单元根据参考单元的平均噪声水平乘一个系数得到当前单元的门限。这样强目标旁边的弱目标不会被强目标旁瓣压掉噪声高的区域和噪声低的区域也能用统一虚警率来检测。CA-CFAR的实现逻辑并不复杂关键是滑窗范围怎么取。距离维和多普勒维我一般各取6到8个保护单元和12到16个参考单元。保护单元紧挨着待检测单元不进平均值否则强目标会把自己的能量带入门限导致检测不到相邻的目标。参考单元则代表周围的噪声背景。这个滑动窗口的尺寸没有统一标准我建议用仿真数据或实测数据先扫一遍把虚警率和漏检率调到一个可接受的平衡。峰值搜索和CFAR可以先配合使用先用CFAR找出所有超过门限的单元再用局部峰值搜索确定每个目标的精确位置。这样既避免了全局最大值丢失弱目标又不会把杂波边缘误判为目标。4.3 速度解算、速度折叠和输出滤波CFAR检测到目标单元后由多普勒门的索引换算多普勒频率f_d (dopplerBin - N_DOPPLER/2) / (N_DOPPLER × Tc)注意FFT输出通常把零频放在中间所以负频率对应的是索引小于N/2的区域。多普勒频率换算成径向速度v f_d × λ / 2这里出来了实际测速值。但前面说过速度有模糊问题。如果目标实际速度超过vmax多普勒谱上会折叠到低速区。破解方法包括使用两个不同的chirp周期Tc1和Tc2分别测速再利用中国剩余定理解模糊或者让chirp周期在帧间交替变化。此外还可以跟踪目标帧间的位置变化用位置差分速度去解速度模糊这是个笨但有效的办法因为在连续跟踪场景下距离变化量是连续可信的。速度解算完之后建议加一个滑动平均或者一阶低通滤波因为单帧FFT受噪声影响会有抖动。我习惯用一阶IIR滤波器系数取0.6到0.8之间的新帧权重这样测速输出既平滑又不会滞后太多。千万别一开始就上卡尔曼滤波很多场景一阶滤波已经够了卡尔曼的调参成本明显更高。在实际小车测速里还有个细节当目标静止时速度输出会不断在0附近跳变看起来非常不稳定。这是因为强静止杂波和干扰导致第0个多普勒门有很高的能量而旁边几个门也有泄漏。我一般会检测多普勒中心附近的能量如果最大值和第0个多普勒门的距离小于两三个bin就直接把速度置0。这个小技巧看着简单但能让整个测速系统的观感提升非常多。5. 实测中的标定与误差处理安装角、栅瓣、多径这些坑5.1 安装角度带来的速度比例误差测速算法的前提是雷达波束方向与目标运动方向一致但实际安装时几乎不可能完全做到。雷达测到的速度其实是目标实际速度在雷达视线方向上的投影用公式表示就是v_radar v_actual × cos(θ)θ是目标运动方向与雷达视线方向的夹角。如果这个夹角是30度那么实际速度100km/h的目标雷达只能测到86.6km/h误差超过13%。这是系统性的比例误差不是随机噪声靠滤波完全滤不掉。处理办法有两种。第一种是机械标定安装时用激光测距仪或水平仪把雷达波束方向校准到与运动方向一致。第二种是算法补偿在标定阶段让已知速度的目标通过反推出安装角θ然后在测速公式里除以cos(θ)。第二种方法在现场更实用但要注意θ会随目标位置变化目标从雷达正前方偏离到侧面时角度就变了。所以严格来说固定一个补偿系数只能改善中心区域真正要全覆盖必须用测角功能先算出目标方位角再做径向速度补偿。这是我在公路测速项目里踩过最大的坑之一。刚装好时测出来的车速整体偏慢一开始还以为是天线有问题排查了很久才发现是安装支架有大约18度的偏转角。那台设备后来校正安装后数据立刻恢复正常。所以大家拿到任何测速设备都别急着相信出厂标定先找一个速度可控的校准目标验证一遍。5.2 天线栅瓣、旁瓣和距离维“鬼影”雷达天线都有自己的方向图存在主瓣和旁瓣。如果天线阵元间距过大方向图上还会出现栅瓣。栅瓣的指向角度和主瓣不同但增益接近所以一条真实目标可能同时出现在两三个不同角度上。对纯测速来说栅瓣不一定直接造成速度错报但它会把能量分散导致目标幅度降低进入CFAR检测时可能漏检。距离维的旁瓣则会在距离-多普勒图上造成“鬼影”。强目标旁边的旁瓣单元能量很高一旦超过CFAR门限就会被当成一个虚假目标。我在调24GHz模块时就遇到过这种情况明明只有一辆车距离维上却出现了两个相距1.5米的目标。后来查下来问题出在两个地方一是chirp带宽内的线性度不够好导致距离维旁瓣偏高二是没有加窗旁瓣直接抬到了-13dB比加了汉宁窗以后高得多。解决办法就是加窗或者在CFAR门限里对旁瓣区域做额外的抑制。这也解释了为什么前面代码里我没有省略加窗步骤。5.3 多径、静止杂波和雨雾天气的干扰实测环境中地面、护栏、桥墩和车辆侧面都会产生多径反射。最典型的现象是近距离处出现一个拖尾的虚假目标沿着距离维一直延伸。因为多径回波比直达波多走了一段路程测出来的距离会偏大而且它和真实目标的多普勒速度往往一致所以在速度维上很难分辨。对付多径我用的方法是在距离维上做“最大值回溯”当多个距离门在同一个多普勒频率上连续出现高能量时只取能量最大的那一个作为真实目标其余当作多径丢弃。这个方法简单但在车辆侧翻、金属护栏密集的立交桥下误删真实目标的情况也时有发生。更稳妥的做法是利用雷达的微多普勒特征结合目标历史轨迹判断连续性但这就已经超出基础测速范畴了。雨雾天气对毫米波雷达的影响相对较小但大雨时水膜会让近距离目标的回波发生衰减。另外来车大灯、对向车道的强雷达干扰也会在距离-多普勒图上产生随机尖峰。针对随机干扰我在CFAR检测前加了一个简单的背景归一化先把每一列多普勒门上的能量求均值再用原始幅度除以均值。这个操作能明显压低均匀分布的干扰而对局部目标影响很小。6. 从“测速”扩展到“目标检测”4D雷达、摄像头同步与融合6.1 距离-速度-角度同时测量才是真正的目标检测纯测速只用了距离-多普勒图它回答的是“这个距离上有没有目标以这个速度在运动”。但目标检测通常还要回答“目标在哪个方位、是不是真的车辆、会不会撞上”。这时候就需要角度维信息。角度测量依赖多根接收天线之间的相位差。目标从不同方向回来波程差不同各天线的接收相位也就不同。对同一距离-速度单元的天线维数据再做一次FFT就叫角度FFT。此时数据的维度变成距离-速度-角度三个维度这就是常说的3D点云。基于FMCW的毫米波雷达测速本质上是3D点云处理的第一步速度维反而是最容易做的一维。我在做摄像头与雷达融合时速度信息是关联匹配的关键。摄像头能在像素平面上框出目标但单目相机无法直接获得精确距离和速度雷达能给出准确的距离、速度却缺少纹理信息无法判断目标是车还是人。用雷达的速度和距离去约束摄像头产生的检测框能大幅减少误匹配。比如摄像头在一帧里检测到多个目标我先把雷达点云映射到图像坐标系再用速度一致性和距离一致性做最近邻匹配准确率比纯IoU匹配高很多。6.2 摄像头和毫米波雷达的时空同步摄像头和雷达融合绕不开“时空间步”四个字。时间同步的核心问题是两种传感器帧率不同雷达一帧通常在10到50毫秒摄像头一帧通常是20到40毫秒。如果直接用各自最新帧做匹配最坏情况下会有半个帧周期的时间偏差换算到高速运动目标上位置误差可能达到数米。工程上常用的做法是用硬件同步信号把两种传感器的采样时刻拉齐。雷达和摄像头分别接收PPS脉冲或帧同步信号把曝光开始时间和chirp起始时刻对齐这样每对数据的时间偏差能控制在毫秒级。如果没有硬件同步线也可以通过插值法在软件里补偿记录每帧雷达和图像的时间戳把雷达点云插值到图像时间戳上。这个方法我用过精度不如硬件同步但对大多数非实时应用够用。空间同步就是标定外参把雷达坐标系和相机坐标系关联起来。最简单的标定方法是找一个棋盘格同时在雷达和相机中检测用一组对应点求解旋转和平移矩阵。但雷达点云对棋盘格这种弱反射目标并不友好所以我一般用角反射器也就是三个互相垂直的金属板组成三面角在雷达图像中会产生明显亮斑。把角反射器摆在几十个不同位置采集对应的雷达坐标和像素坐标就能解出外参。注意角反射器的尺寸要覆盖雷达波长24GHz对应波长12.5mm反射器边长做五到十倍波长就够了。6.3 什么时候该升级到4D毫米波雷达4D毫米波雷达是这两年的热门词它多了“高度维”的输出所以被称为4D也就是距离、速度、水平角、俯仰角四个维度。传统毫米波雷达在处理立交桥、高架下目标时很容易把桥上目标和桥下目标混淆因为只有一个水平角无法区分目标是在路面上还是在桥面上。4D雷达通过MIMO虚拟孔径和俯仰维测角能把这些不同高度的目标分开。但4D雷达不是万能的。它的点云比传统雷达密得多对后端的处理和存储要求随之暴增。一颗4D雷达的点云数据每秒可能达到几万个点普通单片机已经很难实时处理通常要上带GPU的域控制器。如果你的应用只是小车测速、防撞预警或液位计2D雷达加基础测速算法就够了。升级到4D雷达主要看两个指标目标是否分布在不同高度层以及系统是否有足够算力消化密集点云。从我实际测试看4D雷达对行人的检测改善最明显。行人高度低回波体散射面积小在传统雷达里经常和栏杆、路沿混在一起4D雷达多了俯仰角后同一个距离-速度单元上的人和栏杆能在高度维拉开虚警率能降一半以上。如果你正在做的项目要在城市道路或者园区里做目标检测而不是简单测速那4D雷达值得重点关注。我在几轮实测里的体会是FMCW毫米波雷达测速的原理并不复杂真正拉开差距的是参数设计、FPGA或单片机上的工程实现以及实际安装环境里的标定和调试。这些环节没有哪一步能靠“调大某个参数”一劳永逸每一版代码和每一套硬件都要按实际场景反复迭代。如果你打算从零搭建一套测速系统建议先按第二部分的公式把整个参数表算一遍再决定芯片型号和天线配置最后才动手写FFT和CFAR。顺序反了后面大概率要返工。