FPGA与STM32脉冲压缩雷达信号处理系统设计与实现 1. 项目缘起与整体设计思路PLFM_RADAR 这个名字拆开看PLFM 大概率指代的是Pulse Linear Frequency Modulation也就是脉冲线性调频信号业内更习惯叫它 LFM 或者 chirp 信号。RADAR 就不用多解释了。合在一起这是一个围绕脉冲压缩雷达信号处理展开的工程项目。从热搜词里同时出现 phased array、FPGA、STM32 这几个关键词来看这个项目的定位非常清晰用 FPGA 做高速信号采集与实时处理用 STM32 做系统控制与人机交互整体架构面向相控阵雷达的信号收发链路。我之所以对这个标题感兴趣是因为它踩中了目前嵌入式与信号处理领域最典型的一个分工模式——FPGA 管快和并行STM32 管稳和控。这个分工不是随便定的背后有非常硬的工程逻辑。雷达信号处理的核心痛点在于数据率和实时性。假设你的 ADC 采样率是 100 MSPS、位宽 14 bit那么单通道原始数据率就是 1.4 Gbps。这个量级的数据STM32 的 FSMC 或者 SPI 接口根本吃不下更别提做 FFT、脉冲压缩、CFAR 检测这些运算了。FPGA 的优势在于它可以把这些运算做成流水线每个时钟周期都在出结果延迟可以压到微秒级。而 STM32 擅长的是任务调度、协议栈处理、参数配置、状态监控这类慢工出细活的活。两者通过高速接口通常是 SPI、FSMC 或者自定义并行总线对接各司其职。从热搜词里还能看到基于 FPGA 的多端口 DDR 读写程序和FPGA 高速 ADC 采样这两个词基本坐实了数据链路的形态ADC 采样数据先进 FPGA 内部 FIFO然后通过 DDR 控制器做乒乓缓存或者大容量存储处理单元再从 DDR 里取数做运算。多端口 DDR 读写在这里的意义是——雷达系统往往需要同时处理多通道数据相控阵嘛通道数可能是 8、16 甚至更多每个通道的数据都要独立缓存但 DDR 的物理接口只有一套所以必须做多端口仲裁。STM32 这边热搜词里出现了STM32 如何做 USB 设备、agile_modbus stm32、STM32 LIN 收发器说明这个项目大概率需要对外提供调试接口或者上位机通信链路。USB 设备模式可以用来做虚拟串口或者自定义 HID方便把雷达处理结果实时传到 PC 上做可视化。Modbus 协议则可能是为了对接工业现场的设备或者上位机软件。整体设计思路可以概括成一句话FPGA 负责从 ADC 到处理结果的完整流水线STM32 负责把这套流水线管起来并且把结果送出去。这个架构在相控阵雷达、超声成像、激光测距等领域都是通用的PLFM_RADAR 只是把它具体化到了脉冲压缩雷达这个场景。提示如果你打算复现类似项目先别急着画板子。把数据流画清楚——从 ADC 出来多少路、每路多快、要做什么运算、运算结果多大、送给谁——这张图决定了你后面所有的选型和接口设计。2. 核心硬件选型与信号链拆解2.1 FPGA 选型为什么不是随便挑一颗热搜词里出现了黑金 FPGA、安路 FPGA 仿真、FPGA EP4CE10 制作 FFT IP 核的信号频谱仪说明这个项目的 FPGA 选型大概率落在中低端到中端区间。EP4CE10 是 Altera Cyclone IV 系列的经典款逻辑资源大约 10K LE做简单的 FFT 和滤波够用但如果要做多通道脉冲压缩资源会非常紧张。我的经验是雷达信号处理项目选 FPGA 要看三个硬指标DSP 切片数量、Block RAM 容量、高速收发器如果要做高速 ADC 接口。以脉冲压缩为例一个 1024 点的复数 FFT 大概需要消耗 20 到 30 个 DSP 切片取决于架构如果要做 8 通道并行那就是 160 到 240 个 DSP。Cyclone IV 的 DSP 数量通常在几十到一百多明显不够。所以如果通道数多至少要上 Cyclone V 或者 Xilinx 的 Artix-7 系列。Block RAM 同样关键。雷达的脉冲压缩需要存储匹配滤波器系数1024 点的复数系数就是 1024 × 2 × 16 bit 32 Kbit看起来不大但如果你要做多普勒处理还需要存储多个脉冲的 FFT 结果这时候 BRAM 消耗会急剧上升。高速 ADC 接口方面热搜词里有FPGA 的 LVDS 接收和FPGA 实现 MIPI这两个都是高速差分接口。LVDS 在雷达 ADC 里非常常见因为很多高速 ADC比如 AD9253、ADS62P49都提供 LVDS 或者 DDR LVDS 输出模式。FPGA 这边需要用 IDDR 原语或者 SelectIO 的 SERDES 来接收时序约束要写得很仔细否则采样窗口对不上数据全是错的。2.2 STM32 的角色定位与型号选择STM32 在这个项目里不是打杂的它承担的是系统控制中枢的角色。具体来说包括配置 FPGA 的工作模式比如脉冲重复频率、脉宽、调频斜率、监控 FPGA 的状态寄存器、读取处理结果并通过 USB 或者串口上传、驱动显示屏做本地显示、处理按键输入等。热搜词里出现了STM32 芯片包安装、VSCode 配置 STM32 开发环境、Keil5 兼容 C51 和 STM32 安装说明这个项目的开发环境可能是 Keil 和 VSCode 混用。我个人的习惯是底层驱动和中断用 Keil 调试上层逻辑和协议用 VSCode Cortex-Debug这样既能用 Keil 成熟的仿真器支持又能享受 VSCode 的编辑体验。型号选择上如果只是做控制和 USB 通信STM32F103 或者 F407 就够了。但如果要跑 Modbus 协议栈、USB 设备栈、还要驱动 LCD建议上 F429 或者 H743RAM 和 Flash 都更宽裕。热搜词里还有STM32 禁用 JTAG这个操作通常是为了释放 PA15、PB3、PB4 这几个引脚做普通 IO 用在引脚紧张的时候很常见。2.3 时钟与同步雷达系统的命门雷达系统对时钟的要求极其苛刻。PLFM 信号的质量直接取决于发射和接收链路的时钟纯度。如果 ADC 采样时钟有抖动脉冲压缩后的旁瓣电平会恶化目标检测性能直线下降。典型的做法是用一颗低相噪的晶振或者时钟芯片比如 LMK04828、AD9517产生多路同步时钟一路给 ADC一路给 FPGA一路给 DAC如果有发射波形生成并且保证这些时钟之间的相位关系确定。FPGA 内部再用 MMCM 或者 PLL 生成各种衍生时钟。STM32 这边对时钟的要求没那么高但也要注意如果 STM32 和 FPGA 之间有高速并行接口接口时钟的相位关系要调好否则建立保持时间容易出问题。我一般会在 PCB 上预留时钟延迟调节的电阻位调试的时候用示波器看眼图不行就换电阻值。3. FPGA 端核心处理链路实现3.1 高速 ADC 数据接收与 DDR 缓存ADC 数据进 FPGA 的第一步是串并转换和位对齐。以 LVDS 接口为例ADC 输出的是一对差分时钟和若干对差分数据线FPGA 需要用 IBUFDS 接收差分信号然后用 IDDR 或者 ISERDES 做双沿采样。这里最容易踩的坑是字对齐——ADC 输出的数据流里通常有一个同步字或者训练序列FPGA 需要先找到这个同步字才能确定字节边界。我通常的做法是写一个状态机先进入搜索状态逐位滑动比较同步字匹配成功后进入锁定状态输出对齐后的数据如果连续多个周期没有检测到同步字就回到搜索状态。这个逻辑不复杂但一定要做否则数据错位了你还以为是算法问题。数据对齐之后下一步是写入 DDR 做缓存。热搜词里的基于 FPGA 的多端口 DDR 读写程序在这里就派上用场了。雷达系统通常需要缓存多个脉冲周期的数据因为多普勒处理需要跨脉冲做 FFT。DDR 的容量大、带宽高适合做这种大块数据的缓存。多端口 DDR 读写的核心是仲裁器。假设你有 4 个通道的 ADC 数据要写入 DDR同时有 1 个处理单元要从 DDR 读数据那么仲裁器需要决定每个时钟周期谁占用 DDR 总线。常见的策略是轮询仲裁或者优先级仲裁。轮询仲裁公平但延迟不确定优先级仲裁可以保证关键路径的实时性但可能导致低优先级通道饿死。我的经验是写入通道用轮询读取通道用优先级。因为写入是流式的偶尔延迟几个周期没关系数据先存到 FIFO 里等着就行读取是处理单元发起的如果读不到数据整个流水线就停了所以读取请求要给高优先级。// 简化的多端口 DDR 仲裁器核心逻辑 // 假设 4 个写端口1 个读端口 always (posedge clk) begin if (read_req) begin ddr_addr read_addr; ddr_wr_en 0; ddr_rd_en 1; read_grant 1; end else begin // 轮询写端口 case (wr_ptr) 2d0: if (wr_req[0]) begin ddr_addr wr_addr[0]; ddr_wr_en 1; wr_ptr 1; end 2d1: if (wr_req[1]) begin ddr_addr wr_addr[1]; ddr_wr_en 1; wr_ptr 2; end 2d2: if (wr_req[2]) begin ddr_addr wr_addr[2]; ddr_wr_en 1; wr_ptr 3; end 2d3: if (wr_req[3]) begin ddr_addr wr_addr[3]; ddr_wr_en 1; wr_ptr 0; end endcase end end注意DDR 控制器的用户接口时序一定要仔细看手册。特别是写数据时的 wren 和 wdata 的相位关系不同厂商的 IP 核要求不一样。我见过有人因为 wdata 比 wren 晚了一个周期导致写入的数据整体偏移调了两天才发现。3.2 脉冲压缩的 FPGA 实现脉冲压缩是 PLFM 雷达的核心算法本质上是接收信号与发射波形的共轭做卷积。在频域实现就是接收信号 FFT → 与匹配滤波器系数相乘 → IFFT。FPGA 实现时通常用 FFT IP 核加复数乘法器加 IFFT IP 核的流水线结构。这里有几个关键参数需要计算。假设你的 LFM 信号带宽是 10 MHz脉宽是 10 us那么调频斜率 K 10 MHz / 10 us 1e12 Hz/s。距离分辨率是 c / (2 × B) 3e8 / (2 × 10e6) 15 米。脉冲压缩比是 B × T 10e6 × 10e-6 100也就是说压缩后的脉冲宽度是原来的 1/100信噪比增益大约是 10log10(100) 20 dB。FPGA 做 FFT 的时候点数选择很关键。如果采样率是 20 MSPS脉宽 10 us那么一个脉冲周期内的采样点数是 200 个。为了做 FFT通常要补零到 256 或者 512 点。点数越多频率分辨率越高但资源消耗和延迟也越大。我一般会先用 MATLAB 或者 Python 做定点仿真确定 FFT 点数、位宽、缩放因子这些参数然后再写 Verilog。定点仿真的重点是位宽增长——FFT 每级蝶形运算都会让数据位宽增加如果不做缩放很快就会溢出。Xilinx 的 FFT IP 核提供了几种缩放模式unscaled、scaled、block floating point。雷达信号动态范围大我推荐用 block floating point兼顾动态范围和资源消耗。3.3 与 STM32 的接口设计FPGA 和 STM32 之间的接口常见的有三种SPI、FSMC或者叫 FMC、自定义并行总线。SPI 简单但速度慢适合传控制命令和状态寄存器。FSMC 速度快STM32 可以直接把 FPGA 当成外部 SRAM 来读写适合传中等数据量的处理结果。自定义并行总线最灵活但需要自己写时序。我的建议是控制通道用 SPI数据通道用 FSMC。SPI 占引脚少STM32 的硬件 SPI 加上 DMA传几十个字节的命令毫无压力。FSMC 可以做到 16 bit 或者 32 bit 位宽STM32F4 系列在 100 MHz HCLK 下FSMC 的读写速度可以到几十 MB/s传雷达处理结果足够了。接口设计里有一个容易被忽略的点跨时钟域同步。FPGA 和 STM32 的时钟是独立的如果 STM32 直接读 FPGA 内部寄存器必须做同步处理否则会读到亚稳态数据。我通常会在 FPGA 侧加一个双口 RAM 或者异步 FIFOSTM32 写命令进 FIFOFPGA 读出来执行FPGA 把状态写进另一个 FIFOSTM32 读走。这样两边完全解耦时序也好约束。4. STM32 端控制与通信实现4.1 USB 设备模式配置热搜词里STM32 如何做 USB 设备是个高频问题。在雷达项目里USB 的主要用途是把处理结果实时上传到 PC或者接收 PC 下发的配置参数。STM32 的 USB 外设支持设备模式可以枚举成 CDC虚拟串口、HID、MSC 等设备类。CDC 类是最容易上手的因为 PC 端不需要装驱动直接当串口用。配置步骤大致是使能 USB 时钟和 GPIO配置 USB 中断优先级初始化 USB 设备栈用 ST 的 HAL 库或者 CubeMX 生成然后在 CDC_Receive_FS 回调里处理接收数据用 CDC_Transmit_FS 发送数据。但 CDC 的带宽有限全速 USB 的理论上限是 12 Mbps实际能跑到 1 MB/s 左右。如果雷达数据率很高CDC 就不够用了。这时候可以考虑自定义设备类或者USB 批量传输把带宽榨到极限。不过自定义设备类需要写 PC 端驱动开发成本高除非必要我一般还是用 CDC。提示STM32 的 USB 时钟必须精确到 48 MHz误差不能超过 0.25%。如果你用外部晶振一定要选质量好的负载电容要匹配。我见过因为晶振偏差导致 USB 枚举失败的案例换了晶振就好了。4.2 Modbus 协议栈的移植与使用热搜词里的agile_modbus stm32指向一个轻量级的 Modbus 协议栈。在雷达项目里Modbus 通常用于对接工业上位机或者 PLC。比如雷达检测到目标后把距离、速度、角度这些信息通过 Modbus 寄存器暴露出去上位机轮询读取。agile_modbus 的移植比较简单它不依赖特定硬件只需要提供发送和接收的回调函数。我一般会把它和串口 DMA 配合使用串口接收中断里把数据塞进环形缓冲区主循环里调用 agile_modbus 的轮询函数解析到完整帧后就处理然后通过串口发送响应。需要注意的是Modbus 的帧间隔。RTU 模式下帧与帧之间需要至少 3.5 个字符时间的静默。如果雷达数据更新率高STM32 可能来不及处理这时候要么降低轮询频率要么改用 TCP 模式如果有以太网接口。4.3 定时器与延时函数的坑热搜词里STM32 延时函数 delay 卡死是个经典问题。很多人用 SysTick 做延时但在中断里调用 delay 就会卡死因为 SysTick 中断优先级低于当前中断永远等不到。正确的做法是延时函数不要依赖中断用 DWT 的周期计数器或者简单的循环计数。DWTData Watchpoint and Trace是 Cortex-M 内核自带的一个调试单元里面有一个 32 位的周期计数器。使能之后可以用它做微秒级延时而且不依赖任何中断。代码大概是这样// 使能 DWT 周期计数器 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; // 微秒级延时 void delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); }这个延时函数在中断里也能用而且精度比 SysTick 高。唯一需要注意的是 DWT 计数器是 32 位的在 168 MHz 下大约 25 秒就会溢出所以延时时间不能太长。5. 调试与问题排查实录5.1 FPGA 时序不收敛的排查思路FPGA 项目最头疼的问题就是时序不收敛。雷达项目里高速 ADC 接口和 DDR 接口都是时序敏感路径稍微不注意就报 setup 或者 hold 违例。我的排查顺序是先看时钟约束对不对再看跨时钟域处理最后看逻辑级数。时钟约束是最容易出错的比如 ADC 随路时钟的频率和相位如果约束写错了工具根本不知道要按什么标准去优化。跨时钟域如果没有做同步工具会报亚稳态警告这个必须解决。逻辑级数太高的话可以插入流水线寄存器把长路径打断。热搜词里FPGA 如何正确写 testbench也是个关键点。很多人写完 Verilog 就直接上板出了问题再回头仿真效率很低。我的习惯是每个模块都要有独立的 testbench用仿真验证功能正确性然后再上板调时序。testbench 里要覆盖边界情况比如 FIFO 满、空、同时读写这些场景。5.2 STM32 与 FPGA 通信失败的常见原因通信失败通常有三类原因硬件连接问题、时序配置问题、协议实现问题。硬件连接问题包括引脚接错、电平不匹配、上拉电阻缺失。STM32 是 3.3V 电平FPGA 的 IO 标准如果配成 1.8V 或者 2.5V直接对接就会出问题。我一般会在 PCB 上预留电平转换芯片的位置或者至少串一个 22 欧姆的电阻做限流。时序配置问题主要是 FSMC 的等待周期设置。STM32 读 FPGA 的时候如果 FPGA 的响应速度慢STM32 需要插入等待周期。这个参数在 FSMC 的初始化结构体里配置设小了读不到数据设大了速度慢。我的经验是先用保守值比如等待 5 个周期调通之后再逐步减小。协议实现问题最常见的是字节序。STM32 是小端模式FPGA 如果按大端发送数据就全反了。这个在调试的时候用示波器或者逻辑分析仪抓一下就能看出来。5.3 常见问题速查表现象可能原因排查方法解决方案ADC 数据全零时钟未输出示波器测 ADC 时钟引脚检查 FPGA 时钟配置和引脚约束ADC 数据错位字对齐失败逻辑分析仪抓数据流检查同步字搜索状态机DDR 写入失败仲裁器死锁仿真查看仲裁状态增加超时机制重新仲裁FFT 结果溢出位宽不足对比 MATLAB 定点仿真调整缩放因子或增加位宽USB 枚举失败时钟偏差测 USB 时钟频率更换晶振或调整负载电容Modbus 无响应帧间隔不足示波器测串口波形增加帧间延时或降低轮询频率STM32 读 FPGA 数据错误跨时钟域未同步检查同步逻辑加双口 RAM 或异步 FIFO延时函数卡死中断优先级冲突查看调用位置改用 DWT 延时注意逻辑分析仪是调试 FPGA 和 STM32 通信的利器。我推荐至少买一个 8 通道、100 MSPS 以上的抓 SPI、FSMC、串口这些信号绰绰有余。如果预算够上 16 通道的可以同时抓控制总线和数据总线。6. 实操心得与经验沉淀6.1 关于开发环境的选择热搜词里同时出现了 Keil、VSCode、STM32CubeMX说明大家在这几个工具之间纠结。我的建议是CubeMX 做初始化代码生成Keil 做编译和调试VSCode 做代码编辑。CubeMX 可以图形化配置时钟树、外设、中断生成 HAL 库代码省去大量查手册的时间。Keil 的调试器支持最完善特别是 ST-Link 和 J-Link 的配合。VSCode 的编辑体验最好配合 Cortex-Debug 插件也能做调试但偶尔会有连接不稳定的情况。FPGA 这边如果是 Xilinx 的芯片Vivado 是唯一选择Altera 的话Quartus 是标配。仿真工具我推荐 ModelSim 或者 Vivado 自带的 Simulator前者功能更强后者集成度更高。6.2 关于 PCB 设计的几点提醒雷达项目的 PCB 设计比普通嵌入式项目要求高得多。高速差分线要等长阻抗要控制参考平面要完整。ADC 的模拟输入和数字输出要隔离电源要分域滤波。FPGA 的 DDR 走线要按 Fly-by 或者 T 型拓扑做长度匹配误差控制在 5 mil 以内。STM32 这边相对简单但也要注意晶振尽量靠近芯片负载电容按手册选USB 差分线要走 90 欧姆阻抗。如果板子上有开关电源电感要选屏蔽式的否则辐射会干扰 ADC。6.3 关于项目管理的建议雷达项目周期长、环节多很容易做着做着就乱了。我的习惯是先做信号链仿真再做单板调试最后做系统联调。信号链仿真用 MATLAB 或者 Python把发射波形、信道、接收、脉冲压缩、CFAR 全部跑一遍确定参数。单板调试先调电源和时钟再调 ADC 和 DAC最后调 FPGA 和 STM32 的接口。系统联调的时候用信号源模拟雷达回波从天线口注入看整个链路能不能正确检测到目标。每个阶段都要有明确的验收标准。比如 ADC 调试的验收标准是输入单音信号FPGA 抓到的数据做 FFTSNR 大于 60 dB。FPGA 和 STM32 接口的验收标准是STM32 能正确读写 FPGA 的所有寄存器误码率为零。最后再分享一个小技巧在 FPGA 里埋一个逻辑分析仪 IP 核比如 Xilinx 的 ILA 或者 Altera 的 SignalTap把关键信号引出来。调试的时候不用外接逻辑分析仪直接在 Vivado 或者 Quartus 里看波形效率高很多。这个 IP 核会消耗一些 BRAM 资源但比起调试时抓瞎的痛苦这点代价完全值得。