☰
FPGA+STM32打造相位体制LFM雷达:从数字波束形成到脉冲压缩的工程实战
2026/11/2 10:35:39 网站建设 项目流程

1. 项目缘起:为什么我要折腾一台相位体制雷达

PLFM_RADAR 这个名字,拆开看就是 Phased Linear Frequency Modulation Radar,翻译过来是相位体制线性调频雷达。说白了,就是一台用电子扫描代替机械转动的雷达,靠控制每个天线单元发射信号的相位差,让波束在空间里“甩”来“甩”去,不用转天线座。这东西在民用领域其实已经悄悄铺开了,比如车载毫米波雷达、无人机避障、周界安防、甚至一些工业液位计都在用。

我最早接触这个方向,是因为手头有一批 FPGA 和 STM32 的板子闲着,想找个能同时吃透高速 ADC 采样、多端口 DDR 读写、实时信号处理、上位机通信的综合性项目。翻了一圈,发现相控阵雷达这个题目几乎把嵌入式硬件工程师能碰到的硬骨头全占了:射频前端要控相位,中频要高速采样,FPGA 要做数字波束形成和脉冲压缩,STM32 要管系统调度和人机交互,上位机还要做点迹显示。一个项目把整条链路串起来,比单独玩某个模块有意思得多。

这篇文章面向的是有一定 FPGA 和 STM32 基础、想往雷达信号处理方向靠的工程师,或者正在找综合性毕业设计题目的同学。我会把整个系统的设计思路、关键参数计算、FPGA 内部模块划分、STM32 的调度逻辑、以及我在调试过程中踩过的坑,尽量完整地摊开来讲。不是教科书式的原理罗列,而是我实际做下来觉得“这个地方如果没人告诉我,我可能要卡三天”的那些细节。

提示:本文涉及的所有参数和代码结构均基于我实际搭建的一套 X 波段实验平台,工作频率、阵元数量等可根据你的射频前端和 FPGA 资源灵活调整,核心思路不变。

2. 系统整体架构与方案选型

2.1 为什么选 LFM 加相位扫描这套组合

雷达体制有很多种,脉冲多普勒、调频连续波、相位编码、线性调频,各有各的适用场景。我选线性调频加相位扫描,主要基于三个考虑。

第一,LFM 信号对多普勒频移不敏感。简单说,目标在动,回波频率会偏,但 LFM 的匹配滤波输出峰值位置几乎不受这个频偏影响,只是幅度略降。这对一个实验平台太重要了,因为我没法保证目标静止,用 LFM 就不用反复补偿多普勒。

第二,相位扫描的波束控制完全靠数字移相器或者直接在中频用数字方式实现,不需要机械伺服。这意味着波束指向的切换速度可以做到微秒级,而且可以同时形成多个波束。对于 FPGA 来说,无非就是给每个通道乘一个复数加权系数,运算量可控。

第三,LFM 的脉冲压缩比容易做高。假设发射脉宽 10 微秒,带宽 50 兆赫兹,压缩比就是 500,距离分辨率从 1500 米直接压到 3 米。这个提升幅度对于实验演示来说非常直观,上位机画出来的距离剖面图一眼就能看出压缩前后的差别。

2.2 FPGA 和 STM32 的分工逻辑

很多人会问,为什么不用一片 FPGA 全包了,或者用 STM32 硬扛。我的分工原则是:FPGA 管“流”,STM32 管“控”。

FPGA 负责的是高速数据通路。ADC 采样率 40 兆样本每秒以上,数据像流水一样进来,必须实时做数字下变频、抽取、波束形成、脉冲压缩。这些操作要求每个时钟周期都处理固定数量的数据,延迟确定,不能有操作系统调度带来的抖动。FPGA 的并行结构天然适合这种活。

STM32 负责的是系统控制和人机交互。它要管的事情包括:给 FPGA 发波束指向指令、配置射频前端的衰减器和移相器、读取温度传感器做增益补偿、驱动显示屏、通过串口或者网口跟上位机通信、记录日志。这些事情对实时性要求没那么苛刻,但逻辑复杂,用 STM32 写 C 代码比在 FPGA 里搭状态机舒服得多。

两者之间的接口我用的是 SPI 加几根中断线。SPI 跑 20 兆赫兹,STM32 作为主机,FPGA 作为从机。STM32 往 FPGA 的寄存器里写波束角度、脉冲参数,FPGA 处理完一帧数据后拉中断通知 STM32 来读结果。实测下来,这个接口传一帧 1024 点的距离剖面数据大概 200 微秒,完全够用。

2.3 关键器件选型与参数计算

射频前端我用的是一套现成的 X 波段收发模块,中心频率 10.525 吉赫兹,带宽 50 兆赫兹。这个频段的好处是波长约 2.85 厘米,阵元间距取半波长约 1.4 厘米,一个 8 阵元的线阵总长度才 10 厘米出头,桌面就能摆下。

ADC 选的是 AD9238,双通道 12 位 40 兆样本每秒。为什么是 40 兆?因为 LFM 带宽 50 兆,按照带通采样定理,采样率只要大于两倍带宽就行,40 兆略低于 50 兆,但实际信号带宽我设的是 20 兆,40 兆采样率有足够余量。这里有个细节:带通采样时采样率的选择要让信号频谱搬移后不混叠,我算过几个组合,40 兆对应中频 30 兆是最干净的。

FPGA 用的是 Xilinx Artix-7 系列的 XC7A100T,逻辑单元够用,关键是它有足够的 DSP48 切片做复数乘法。数字波束形成每个通道每个采样点要做一次复数乘加,8 通道就是 8 次,40 兆采样率下每秒 3.2 亿次复数乘加,每个复数乘加消耗 4 个 DSP 切片,总共需要 12.8 亿次乘法操作。XC7A100T 有 240 个 DSP 切片,跑 200 兆时钟,理论算力 480 亿次乘加每秒,余量充足。

STM32 选的是 STM32F407,168 兆主频,带浮点单元,跑 FreeRTOS 做任务调度。它要处理的任务不少,但都是控制类的,负载不高。

器件型号关键参数选型理由
FPGAXC7A100T240 DSP, 4.9Mb BRAMDSP 资源足够做 8 通道波束形成
STM32STM32F407168MHz, FPU, 192KB RAM浮点运算和任务调度兼顾
ADCAD9238双通道 12 位 40MSPS带通采样中频 30MHz 无混叠
DDRMT41K256M16256Mb, 16 位缓存多脉冲数据做相参积累

3. FPGA 内部核心模块拆解

3.1 数字下变频与多相抽取

ADC 进来的数据是实信号,中心频率 30 兆赫兹。要做波束形成,必须先变成复基带信号,也就是 I/Q 两路。这个过程叫数字下变频,本质上是把信号乘以一个复本振,然后低通滤波。

我在 FPGA 里用 NCO 产生 30 兆的正余弦序列,和 ADC 数据相乘。乘法器用 DSP48 实现,18 位输入,输出截断到 16 位。乘完之后信号里既有基带分量也有 60 兆的高频分量,需要低通滤波。这里我用的是多相抽取滤波器,因为后续波束形成不需要 40 兆那么高的采样率,8 通道波束形成后数据率可以降到 5 兆。

多相抽取的好处是把滤波和降采样合并在一起做,运算量比先滤波再抽取省一半。具体实现是把 40 兆采样数据分成 8 相,每相 5 兆,用一个 64 阶的 FIR 滤波器对每相分别滤波,然后按相序输出。这样每个时钟周期只需要做一次 64 阶乘加,而不是 8 次。

注意:多相抽取的相位对齐很关键。如果 8 个通道的抽取相位不一致,波束形成时会出现通道间相位误差,直接导致波束指向偏移。我的做法是在每个通道的抽取滤波器后面加一个可编程延迟线,用 STM32 在系统启动时发送校准脉冲,FPGA 自动测量通道间延迟并补偿。

3.2 数字波束形成的复数加权实现

波束形成的数学本质很简单:对每个阵元的信号乘以一个复数权重,然后求和。权重决定了波束指向。假设阵元间距 d,波长 λ,波束指向 θ,第 n 个阵元的权重相位是 -2π n d sin(θ) / λ。

在 FPGA 里,这个复数乘法要拆成实部和虚部。假设输入信号是 I + jQ,权重是 cosφ + j sinφ,乘积的实部是 I cosφ - Q sinφ,虚部是 I sinφ + Q cosφ。四个乘法,两个加法。用 DSP48 实现时,可以把 cosφ 和 sinφ 预先算好存在 BRAM 里,根据波束角度查表。

8 个通道的加权结果要加起来。这里有个时序问题:8 个通道的数据必须严格对齐。我的做法是用一个 8 级移位寄存器链,每个时钟周期把 8 个通道的加权结果依次推入,第 8 个周期后所有通道的数据都到齐了,再用一个加法树求和。加法树是 3 级,第一级 4 个加法器,第二级 2 个,第三级 1 个,总共 7 个加法器,延迟 3 个时钟周期。

整个波束形成模块的延迟是固定的,从 ADC 输入到波束输出共 12 个时钟周期,在 200 兆时钟下是 60 纳秒。这个延迟对雷达来说可以忽略,因为脉冲重复周期是毫秒级的。

3.3 脉冲压缩的匹配滤波设计

脉冲压缩是雷达信号处理里最核心的一步。发射的是宽脉冲,回波也是宽脉冲,直接看的话距离分辨率很差。匹配滤波就是把回波和发射波形的共轭反转做卷积,宽脉冲被压缩成窄脉冲,峰值位置对应目标距离。

在 FPGA 里做卷积,最直接的方法是 FIR 滤波器。假设发射脉宽 10 微秒,采样率 5 兆,那就是 50 个点。匹配滤波器的阶数就是 50。每个采样点要做 50 次复数乘加,5 兆采样率下每秒 2.5 亿次复数乘加,DSP 资源够用。

但这里有个坑:匹配滤波器的系数是复数,而且每个脉冲的系数可能不同(如果发射波形变了)。我的做法是把系数存在 BRAM 里,STM32 在发射前把新的系数写进去。FPGA 用双端口 BRAM,一个端口给 STM32 写,一个端口给滤波器读,读写冲突用乒乓缓冲解决。

脉冲压缩的输出是复数,取模后得到幅度。取模运算在 FPGA 里用 CORDIC 算法实现,比开平方省资源。CORDIC 迭代 16 次,精度足够。

3.4 多端口 DDR 读写与数据缓存

雷达是脉冲工作的,发射时不接收,接收时发射已停。一个脉冲周期内,接收到的数据要先存起来,等整个脉冲接收完再做处理。我的脉冲重复周期是 1 毫秒,接收窗口 200 微秒,5 兆采样率下每个通道每个脉冲有 1000 个采样点。8 个通道就是 8000 个复数点,每个点 32 位,总共 256 千位,FPGA 内部 BRAM 放不下,必须用外部 DDR。

DDR 的读写是 FPGA 项目里最容易出问题的部分。我用的是 Xilinx 的 MIG 控制器,但 MIG 生成的接口是给单个端口用的,而我的系统里 ADC 数据要写、波束形成要读、STM32 也要读结果,三个端口抢一个 DDR。解决办法是用 AXI 互联矩阵,把 MIG 的 AXI 从端口扩展成三个主端口,每个端口分配不同的地址段。

地址分配是这样的:ADC 原始数据存在 0x00000000 到 0x00100000,波束形成后的数据存在 0x00100000 到 0x00200000,脉冲压缩结果存在 0x00200000 到 0x00300000。每个区域用独立的 FIFO 做跨时钟域缓冲,AXI 互联矩阵自动仲裁。

实测下来,三个端口同时访问时 DDR 带宽利用率约 60%,还有余量。关键是 AXI 的突发长度要设对,我设的是 64,太短了效率低,太长了延迟大。

4. STM32 控制层与系统调度

4.1 基于 FreeRTOS 的任务划分

STM32 这边跑的是 FreeRTOS,我分了五个任务。优先级从高到低分别是:雷达时序控制、SPI 通信、传感器采集、显示刷新、日志记录。

雷达时序控制任务优先级最高,因为它要精确控制脉冲发射和接收窗口的切换。这个任务用定时器触发,每 1 毫秒执行一次,先拉高发射使能,延时 10 微秒,拉低发射使能,然后等待 200 微秒接收窗口结束,再通知 SPI 任务去读 FPGA 的结果。

SPI 通信任务负责和 FPGA 交换数据。它平时阻塞在信号量上,雷达时序任务发信号量后它才运行。它先通过 SPI 读 FPGA 的状态寄存器,确认数据就绪,然后突发读取 1024 个点的脉冲压缩结果,存到 STM32 的 SRAM 里。

传感器采集任务每 100 毫秒跑一次,读温度传感器和电源电压,做增益补偿计算。显示刷新任务每 50 毫秒跑一次,把最新的距离剖面画到 LCD 上。日志记录任务优先级最低,把关键事件写到 SD 卡里。

实操心得:FreeRTOS 的任务栈大小要留足。我一开始给 SPI 任务只留了 512 字节栈,结果读数据时偶尔 HardFault。后来用 uxTaskGetStackHighWaterMark 查了一下,实际用了 480 字节,太悬了。改成 1024 字节后稳定运行。

4.2 SPI 寄存器映射与通信协议

STM32 和 FPGA 之间的 SPI 通信,我定义了一套简单的寄存器映射。STM32 写 FPGA 的寄存器用地址 0x00 到 0x7F,读 FPGA 的状态用 0x80 到 0xFF。

写寄存器的格式是:第一个字节是地址,第二个字节开始是数据,高位在前。读寄存器时,STM32 先发地址,然后发空字节,FPGA 在第二个字节开始返回数据。

关键寄存器包括:波束角度寄存器(0x10,16 位,单位 0.01 度)、脉冲宽度寄存器(0x12,16 位,单位 0.1 微秒)、发射使能寄存器(0x14,1 位)、数据就绪状态寄存器(0x80,1 位)、数据长度寄存器(0x82,16 位)。

SPI 的时钟极性设的是 CPOL=0,CPHA=0,也就是空闲低电平,第一个边沿采样。这个要和 FPGA 那边的 SPI 从机严格一致,否则数据错位。我调试时因为 CPHA 设反了,读回来的数据全是 0xFF,查了半天才发现。

4.3 上位机通信与数据上传

STM32 和上位机之间用串口转 USB 通信,波特率 921600。上传的数据包格式是:帧头 0xAA 0x55,然后是包序号、数据长度、1024 个点的幅度值(每个 16 位)、校验和。

上位机我用 Python 写了一个简单的界面,用 PyQt 画距离剖面图。数据解析用 struct 模块,把字节流解成整数数组,然后 matplotlib 画图。刷新率大概 10 帧每秒,够用了。

这里有个细节:串口上传 1024 个点,每个点 2 字节,加上包头包尾大概 2.1 千字节,921600 波特率下传一帧要 23 毫秒。如果雷达脉冲重复周期是 1 毫秒,那数据率是 1 帧每毫秒,串口根本传不过来。所以我在 STM32 里做了降采样,每 10 个脉冲上传一帧,这样数据率降到 100 帧每秒,串口勉强够用。如果要实时上传每一帧,得换网口或者 USB 高速。

5. 调试过程中踩过的坑与排查实录

5.1 波束指向偏移问题

系统刚搭起来的时候,我设波束指向 0 度,但实际测出来最大增益在 15 度左右。一开始怀疑是移相器校准问题,后来用信号源直接注入每个通道,测通道间相位差,发现第 3 通道和第 7 通道的相位比其他通道多了约 30 度。

查下来是 PCB 走线长度不一致导致的。射频走线对长度非常敏感,波长 2.85 厘米,30 度相位差对应走线长度差约 2.4 毫米。我的 PCB 上这两个通道的走线确实绕了一下,多了大概 2 毫米。

解决办法是在 FPGA 里给每个通道加一个可编程相位补偿。STM32 在系统启动时,控制信号源依次给每个通道注入同相信号,FPGA 测量通道间相位差,把补偿值写到相位校正寄存器里。这个过程自动完成,不需要人工干预。

问题现象可能原因排查方法解决方案
波束指向偏移通道间相位不一致注入同相信号测相位差FPGA 可编程相位补偿
脉冲压缩峰值低匹配滤波器系数错误对比 MATLAB 仿真结果重新生成系数并写入 BRAM
DDR 读写错误AXI 突发长度不当用 ILA 抓 AXI 波形调整突发长度为 64
SPI 读回全 0xFFCPHA 设置错误示波器看 SPI 时序改为 CPHA=0

5.2 脉冲压缩旁瓣过高

匹配滤波的输出除了主瓣还有旁瓣,旁瓣太高会淹没小目标。我一开始没加窗,旁瓣只有 -13 分贝,两个目标距离差得远还行,差得近就分不开了。

加窗是标准做法,我用的是汉明窗。窗函数系数在 MATLAB 里算好,和匹配滤波器系数逐点相乘,然后一起写到 FPGA 的 BRAM 里。加窗后旁瓣降到 -40 分贝以下,代价是主瓣展宽了约 1.5 倍,距离分辨率从 3 米降到 4.5 米。这个 trade-off 我觉得值。

注意:加窗会降低信噪比,因为窗函数对信号边缘有衰减。如果目标是弱信号,加窗后可能反而检测不到。我的做法是同时输出加窗和不加窗两路结果,STM32 根据信号强度自动选择。

5.3 STM32 与 FPGA 的时序配合问题

STM32 通过 SPI 读 FPGA 数据时,偶尔会读到半帧旧数据和半帧新数据。查下来是 STM32 读数据的过程中,FPGA 又更新了数据缓冲区。

解决办法是双缓冲。FPGA 里开两个数据缓冲区,一个给 STM32 读,一个给新数据写。STM32 读完后发一个切换命令,FPGA 把读写缓冲区互换。这样 STM32 读的时候数据不会变。

STM32 这边的代码逻辑是:先读状态寄存器确认数据就绪,然后发切换命令,等 1 微秒让 FPGA 完成切换,再读数据。切换命令和读数据之间不能有中断打断,所以我把这段代码放在临界区里。

5.4 电源噪声对 ADC 的影响

ADC 对电源噪声非常敏感。我一开始用开关电源给整个系统供电,结果 ADC 采样数据里能看到明显的周期性纹波,脉冲压缩后距离剖面上出现虚假目标。

用示波器测 ADC 电源引脚,纹波峰峰值约 50 毫伏,频率是开关电源的 500 千赫兹。这个纹波和雷达信号混在一起,下变频后落在基带里,形成了假目标。

解决办法是给 ADC 单独用低压差线性稳压器供电,输入输出都加磁珠和电容滤波。改完后纹波降到 5 毫伏以下,假目标消失。这个坑让我明白,射频和高速 ADC 的电源不能省,线性稳压器虽然效率低,但噪声小得多。

6. 系统性能实测与优化方向

6.1 实测性能数据

系统搭好后我做了几组测试。在微波暗室里,用角反射器做目标,距离 5 米。发射功率 10 分贝毫瓦,天线增益 15 分贝,接收增益 20 分贝。

测下来,脉冲压缩后的信噪比约 25 分贝,距离分辨率实测 4.2 米(理论 4.5 米,因为目标不是点目标)。波束宽度实测 12 度(8 阵元半波长间距理论值约 12.8 度),副瓣电平 -18 分贝。波束扫描范围正负 45 度,扫描速度 1 度每微秒。

这些数据对于一个桌面实验平台来说已经不错了。当然和商用雷达比还有差距,但作为学习平台,能把整条链路跑通,看到真实的距离剖面和波束方向图,收获比看一百篇论文都大。

6.2 可以继续优化的几个方向

第一个方向是增加阵元数量。现在 8 个阵元,波束宽度 12 度,如果加到 16 个,波束宽度能压到 6 度,角度分辨率翻倍。代价是 FPGA 的 DSP 资源要翻倍,XC7A100T 可能不够,得换更大的片子。

第二个方向是提高脉冲重复频率。现在 1 千赫兹,最大不模糊距离 150 公里,但实际我只测 5 米,浪费了。如果把重频提到 10 千赫兹,数据率提高 10 倍,可以做更快的扫描。但 FPGA 的 DDR 带宽和 STM32 的串口都会成为瓶颈。

第三个方向是加动目标显示。现在只做了脉冲压缩,没做多普勒处理。如果对多个脉冲做 FFT,就能测目标速度。这个在 FPGA 里加一个 FFT 模块就行,但数据缓存要重新设计,因为要存多个脉冲的数据。

6.3 给后来者的几点建议

如果你也想做类似的项目,我的建议是先从单通道做起。把发射、接收、下变频、脉冲压缩这一条链路跑通,看到距离剖面图,再扩展到多通道做波束形成。一上来就搞 8 通道,调试量太大,容易卡住。

FPGA 的仿真验证一定要做。我每个模块都写了 testbench,用 MATLAB 生成激励数据,和 FPGA 仿真结果对比。虽然写 testbench 花时间,但比上板调试快得多。特别是 DDR 控制器和 AXI 互联,上板出问题很难查,仿真里一目了然。

STM32 的代码要模块化。我把 SPI 驱动、传感器驱动、显示驱动都封装成独立的.c 和.h 文件,主任务里只调用接口。这样调试某个模块时不用动其他代码,减少引入新问题的风险。

最后分享一个小技巧:FPGA 的 ILA 调试核非常有用,但会消耗 BRAM 资源。我的做法是在关键路径上放 ILA,但设置触发条件为“仅触发一次”,抓完就停,不持续占用 BRAM。这样既能抓到问题,又不影响系统运行。

这个项目我断断续续做了大半年,从最开始的一堆散件到现在能稳定出图,中间经历的调试过程比最终结果更有价值。雷达这东西,理论看再多,不动手搭一套,永远不知道那些公式背后的工程细节有多磨人。希望这篇记录能给正在这条路上摸索的人省下几个通宵。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询