☰
FPGA+STM32混合架构相位法激光测距雷达设计与实现
2026/10/4 1:23:42 网站建设 项目流程

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

PLFM_RADAR 这个名字拆开看就是PhaseLaserFrequencyModulationRADAR,直译过来是相位式激光调频测距雷达。但在我这个项目里,它其实是一个以 FPGA 做高速时序控制、STM32 做系统调度与人机交互的混合架构测距系统。核心原理并不复杂:发射一束经过正弦调幅的激光,接收端把回波光信号转换成电信号,通过测量发射调制信号与接收调制信号之间的相位差,反推出目标距离。相位差和距离之间是线性关系,测相精度直接决定测距精度,这也是为什么整个项目最吃功夫的地方在“相位测量”而不是“激光发射”。

我最初动这个念头,是因为市面上能买到的激光测距模块大多是脉冲飞行时间法(dTOF),量程大但毫米级精度很难做,而且模块内部算法封闭,想改点东西几乎不可能。相位法测距在短距离(几米到几十米)内可以做到毫米甚至亚毫米级分辨率,正好适合室内定位、工业形变监测、精密装配引导这类场景。问题是,公开的相位法测距完整工程极少,要么是纯理论推导,要么是拿现成评估板跑个 demo,真正把 FPGA 高速采样、数字鉴相、STM32 系统控制串起来的资料几乎找不到。所以我决定自己搭一套,把 PLFM_RADAR 做成一个可复现、可扩展的开源项目骨架。

这套东西适合谁看?如果你正在做FPGA 高速 ADC 采样、STM32 与 FPGA 协同、数字信号处理中的相位提取,或者你单纯想找一个能同时练 FPGA 和 STM32 的综合性项目,那这篇内容应该能帮你省下不少试错时间。我会把方案选型、参数计算、实操步骤、踩过的坑全部摊开讲,不藏私。

2. 整体架构设计与关键选型逻辑

2.1 为什么是 FPGA + STM32 的双核架构

先回答一个最常被问到的问题:为什么不用纯 FPGA 或者纯 STM32 搞定?纯 STM32 的问题在于采样率。相位法测距需要至少几十 MHz 的调制频率,根据奈奎斯特采样定理,ADC 采样率得是调制频率的两倍以上,实际工程中为了做数字正交解调,往往需要 4 倍甚至更高。STM32 内置 ADC 在 12 位分辨率下勉强能跑到几 MSPS,但一旦要同时处理多路信号、做实时数字滤波和相位解算,CPU 根本扛不住。纯 FPGA 倒是能解决采样和实时处理,但人机交互、参数存储、上位机通信、屏幕显示这些事用 FPGA 做性价比极低,开发周期也长。

所以最终方案是:FPGA 负责高速数据通路,STM32 负责控制通路和业务逻辑。具体分工如下表:

模块承担角色关键器件/接口理由
信号采集FPGA高速 ADC(如 AD9226,12 位 65 MSPS)需要严格时序和高速并行采样
数字鉴相FPGA内部 DSP 切片做乘法与累加实时性要求高,STM32 算不过来
调制信号生成FPGADDS IP 核或查找表频率和相位可精确控制
系统调度STM32主控 MCU任务管理、状态机、异常处理
人机交互STM32ILI9341 屏、按键、串口成熟生态,开发快
数据存储STM32SPI Flash / SD 卡参数与日志保存
上位机通信STM32UART / USB CDC方便调试和数据回传

这个分工的核心逻辑是:让擅长的人干擅长的事。FPGA 的强项是并行和确定性时序,STM32 的强项是流程控制和丰富外设。两者通过 SPI 或 FSMC 总线交换数据,STM32 发命令、读结果,FPGA 只管把相位差算出来放到寄存器里。

2.2 调制频率与量程的取舍计算

相位法测距有一个绕不开的矛盾:调制频率越高,理论分辨率越高,但无模糊量程越短。因为相位差只能在 0 到 2π 之间测量,超过一个周期就出现模糊。距离和相位差的关系是:

距离 D = (c × Δφ) / (4π × f_mod)

其中 c 是光速,Δφ 是相位差,f_mod 是调制频率。当 Δφ = 2π 时对应一个“无模糊距离”,即 D_max = c / (2 × f_mod)。

我选了几个典型频率算了一下:

调制频率 f_mod无模糊量程 D_max理论相位分辨率(假设 0.1°)对应距离
1 MHz150 m约 0.42 mm
5 MHz30 m约 0.083 mm
10 MHz15 m约 0.042 mm
20 MHz7.5 m约 0.021 mm

可以看到,10 MHz 调制下无模糊量程 15 米,对于室内和一般工业场景完全够用,同时理论分辨率已经进入亚毫米级。再高的话量程太短,实际使用中稍微远一点就模糊了。所以我最终把主调制频率定在10 MHz,同时预留一个1 MHz 的粗测频率用于解模糊,两个频率配合可以把量程扩展到 150 米同时保持高精度。这就是多频相位法测距的基本思路。

2.3 ADC 采样率与数字鉴相方案选择

10 MHz 调制信号,如果采用数字正交解调(I/Q 解调),采样率至少要是调制频率的 4 倍,即 40 MSPS 以上。我选的是AD9226,12 位、最高 65 MSPS,实际配置在 50 MSPS 工作。这样每个调制周期采 5 个点,虽然不算特别充裕,但配合后续的数字滤波和相干累加,信噪比可以接受。

数字鉴相我试过两种方案:

第一种是过零检测法,把发射和接收信号分别过比较器变成方波,然后用 FPGA 测两个方波上升沿的时间差。这个方法实现简单,但对噪声极其敏感,信号稍微有点毛刺就误触发,实测抖动很大,直接放弃。

第二种是正交解调法(I/Q 解调),也是我最终采用的方案。原理是把 ADC 采到的信号分别乘以同相分量 cos(ωt) 和正交分量 sin(ωt),然后各自做低通滤波,得到 I 和 Q 两路直流分量。相位差就是 atan2(Q, I)。这个方法的优势在于对噪声有天然的抑制能力,因为乘法加累加本质上是一个相干积分过程,噪声被平均掉了。FPGA 里用 DSP48 切片做乘加非常高效,一个 12 位乘 12 位的乘法器可以跑到几百 MHz,完全跟得上 50 MSPS 的采样率。

3. 核心细节解析与实操要点

3.1 FPGA 内部数字鉴相的具体实现

正交解调在 FPGA 里的实现分三步:混频、滤波、相位计算。

混频部分,我需要生成与调制信号同频的 cos 和 sin 参考序列。这里有个细节:参考信号的相位必须和发射调制信号严格对齐,否则测出来的相位差里会混入一个固定偏移。我的做法是用同一个 DDS 核同时产生发射调制信号和接收端解调用的参考信号,保证两者同源同相。DDS 的相位累加器位宽选 32 位,输出 12 位正弦查找表地址,频率控制字根据 10 MHz 和 50 MHz 采样时钟计算:

频率控制字 = (f_mod / f_clk) × 2^32 = (10e6 / 50e6) × 2^32 ≈ 858993459

这个值直接写进 DDS 的频率控制寄存器即可。

混频之后是低通滤波。因为调制频率是 10 MHz,采样率 50 MHz,混频后有用信号变成直流附近,而二倍频分量在 20 MHz 附近。我设计了一个32 阶的 FIR 低通滤波器,截止频率设在 1 MHz,用 FPGA 的乘法器阵列实现。滤波器的系数用 MATLAB 的 fir1 函数生成,量化成 12 位后写入 ROM。这里有个经验:滤波器阶数不是越高越好,阶数太高会消耗大量 DSP 资源,而且群延迟变大,对实时性有影响。32 阶在资源占用和滤波效果之间是一个比较平衡的点。

相位计算部分,I 和 Q 两路滤波后的值送到一个 CORDIC 核里算 atan2。CORDIC 是 FPGA 里算三角函数的经典方法,只需要移位和加法,不需要乘法器,非常适合硬件实现。我用的是一个 16 级迭代的流水线 CORDIC,延迟 16 个时钟周期,精度大约 0.01°。算出来的相位值是一个 16 位定点数,范围对应 -π 到 +π,STM32 读过去之后再根据公式换算成距离。

3.2 STM32 与 FPGA 的通信接口设计

STM32 和 FPGA 之间我选了SPI 接口,而不是 FSMC 并行总线。原因很简单:SPI 占用的引脚少,PCB 布线容易,而且我的数据量不大——每次测距只需要读回一个相位值和一个状态字,几十个字节而已。SPI 时钟跑到 10 MHz,传输这点数据连一毫秒都用不了。

通信协议我定义得很简单:STM32 作为主机,FPGA 作为从机。STM32 先拉低片选,然后发送一个 8 位命令字,FPGA 根据命令字决定返回什么数据。命令字定义如下:

命令字功能返回数据
0x01启动单次测距状态字节
0x02读取相位差2 字节相位值
0x03读取信号强度2 字节幅度值
0x04设置调制频率状态字节
0x05读取 FPGA 版本4 字节版本号

这里有个坑我踩过:SPI 的时钟极性和相位必须和 FPGA 端严格匹配。STM32 的 SPI 可以配置成四种模式,我一开始用了默认的 Mode 0,结果 FPGA 端采样数据错位,读回来的相位值全是乱的。后来改成 Mode 3(CPOL=1,CPHA=1)才正常。建议在 FPGA 端用示波器或者逻辑分析仪抓一下 SCK 和 MOSI 的波形,确认采样沿和数据变化沿的关系。

3.3 激光发射与接收模拟前端的关键参数

模拟前端是整个系统里最容易被忽视但最影响性能的部分。发射端我用的是一个10 mW 的 650 nm 激光二极管,驱动电路是一个恒流源加高速 MOSFET 调制。调制深度直接决定接收端信号的信噪比,我实测调制深度在 80% 以上时,接收信号幅度比较理想。

接收端用的是APD(雪崩光电二极管),因为普通 PIN 光电二极管在几米距离上回波信号太弱,APD 内部有增益,可以显著提高灵敏度。APD 的偏置电压需要精确控制,太高会击穿,太低增益不够。我用的是一个可调高压模块,通过 STM32 的 DAC 输出控制电压,再经过升压电路产生 APD 所需的偏压。调试时先用示波器观察接收信号的幅度,慢慢调偏压直到信号幅度最大且没有明显噪声。

跨阻放大器(TIA)的反馈电阻选的是100 kΩ,配合 APD 的结电容,带宽大约在 20 MHz 左右,刚好覆盖 10 MHz 调制信号。这里要注意:反馈电阻越大,增益越高,但带宽越低。如果发现接收信号幅度够但波形失真,很可能是带宽不够,需要减小反馈电阻或者换用更高带宽的运放。

4. 实操过程与核心环节实现

4.1 硬件搭建与上电调试顺序

硬件部分我分成三块板子:FPGA 核心板、ADC 采集板、STM32 控制板。三块板子通过排针连接,方便单独调试。上电顺序很重要,我建议按以下步骤来:

  1. 先单独给 STM32 上电,烧录一个最简单的 LED 闪烁程序,确认最小系统正常。这一步能排除电源、晶振、复位电路的问题。
  2. 再单独给 FPGA 上电,用 JTAG 下载一个 LED 闪烁的 bit 流,确认 FPGA 配置电路正常。如果是第一次用某款 FPGA,还要确认芯片包和下载器驱动装好了。
  3. 连接 ADC 板,给 ADC 提供独立的时钟源(我用的是 50 MHz 有源晶振),用逻辑分析仪抓 ADC 的输出数据线,确认有数据在跳变。
  4. 最后连接模拟前端,先不接激光器,用信号发生器给接收端一个 10 MHz 的正弦波,观察 ADC 采到的波形是否正常。

这个顺序的核心逻辑是从数字到模拟、从简单到复杂,每一步只引入一个新变量,出问题容易定位。我见过有人一上来就把所有板子插好上电,结果某个电源短路把 FPGA 烧了,损失惨重。

4.2 FPGA 工程的模块划分与关键代码

FPGA 工程我用的是 Verilog,顶层模块下面分了几个子模块:

  • clk_gen:时钟管理,产生 50 MHz 采样时钟和 100 MHz 系统时钟。
  • dds_mod:DDS 调制信号生成,输出 10 MHz 正弦波。
  • adc_capture:ADC 数据采集,把 12 位并行数据打拍同步到系统时钟域。
  • iq_demod:正交解调,包含混频器和 FIR 低通滤波器。
  • cordic_atan2:CORDIC 相位计算。
  • spi_slave:SPI 从机接口,与 STM32 通信。
  • reg_file:寄存器组,存放相位值、幅度值、状态字。

关键代码片段——DDS 频率控制字的计算和写入:

// 参数定义 parameter PHASE_ACC_WIDTH = 32; parameter FREQ_CTRL_WORD = 32'd858993459; // 10MHz @ 50MHz采样时钟 // 相位累加器 reg [PHASE_ACC_WIDTH-1:0] phase_acc; always @(posedge clk_50m) begin phase_acc <= phase_acc + FREQ_CTRL_WORD; end // 取高12位作为查找表地址 wire [11:0] lut_addr = phase_acc[PHASE_ACC_WIDTH-1:PHASE_ACC_WIDTH-12];

FIR 滤波器的实现我用的是转置型结构,每个时钟周期处理一个采样点,32 阶滤波器需要 32 个乘法器。如果 DSP 资源紧张,可以改成折叠结构,用 4 个乘法器分 8 个周期算完,但那样吞吐率会下降。我用的 FPGA 有 240 个 DSP 切片,32 个乘法器只占一小部分,所以直接用全并行结构。

4.3 STM32 端任务调度与屏幕显示

STM32 端我用的是FreeRTOS,建了三个任务:

  • task_measure:负责发起测距、读取相位、计算距离,优先级最高。
  • task_display:负责刷新 ILI9341 屏幕,显示距离值和信号强度,优先级中等。
  • task_comm:负责串口通信,把数据打包发给上位机,优先级最低。

任务之间通过队列传递数据,避免全局变量竞争。测距任务的伪代码如下:

void task_measure(void *pvParameters) { float distance; uint16_t phase_raw; while (1) { spi_send_cmd(0x01); // 启动测距 vTaskDelay(pdMS_TO_TICKS(10)); // 等待FPGA完成 phase_raw = spi_read_phase(); // 读相位 distance = phase_to_distance(phase_raw); xQueueSend(distance_queue, &distance, 0); vTaskDelay(pdMS_TO_TICKS(100)); // 10Hz刷新率 } }

ILI9341 屏幕的驱动我踩过一个坑:读 ID 返回 0xA1A1 并不一定代表屏幕正常。有些兼容芯片的 ID 就是 0xA1A1,但初始化序列和原厂 ILI9341 不同。我一开始按标准 ILI9341 的初始化序列写,屏幕一直白屏,后来换了卖家提供的初始化代码才点亮。所以买屏幕的时候一定要找卖家要初始化代码,不要迷信数据手册。

4.4 距离解算与校准流程

相位差转距离的公式前面已经给过,但实际使用中还需要校准。因为电路上存在固定的相位延迟(走线长度、运放延迟、滤波器群延迟等),这些延迟会叠加到测量结果里,导致零点偏移。校准的方法是:在已知距离上测量,反推固定偏移量。

我用了两个校准点:0.5 米和 5 米。先测 0.5 米时的相位差 Δφ1,再测 5 米时的相位差 Δφ2,然后解一个二元一次方程组:

D1 = k × Δφ1 + b D2 = k × Δφ2 + b

求出 k 和 b 之后,后续测量值代入即可。实测下来,校准后 0.5 米到 10 米范围内的误差可以控制在 ±2 毫米以内。如果要求更高,可以增加校准点数量,用最小二乘法拟合。

5. 常见问题与排查技巧实录

5.1 相位测量值跳动大怎么办

这是最常见的问题,表现为读回来的距离值一直在跳,波动范围可能有好几厘米。排查思路按以下顺序来:

可能原因排查方法解决方案
接收信号太弱用示波器看 TIA 输出幅度提高激光功率或 APD 偏压
电源噪声大用示波器看电源纹波增加 LC 滤波,模拟和数字电源分开
参考信号相位漂移检查 DDS 和 ADC 时钟是否同源确保同一时钟源驱动
FIR 滤波器截止频率太高观察滤波后 I/Q 波形降低截止频率到 500 kHz
相干累加次数不够增加累加周期数从 100 次增加到 1000 次

我遇到过一次特别诡异的跳动,最后发现是ADC 的时钟线和激光驱动线在 PCB 上走得太近,高速开关信号耦合到了时钟线上,导致采样时钟抖动。把这两根线分开走、中间加地线隔离之后,跳动从 ±5 厘米降到了 ±2 毫米。所以 PCB 布局在混合信号系统里怎么强调都不过分。

5.2 STM32 和 FPGA 通信失败怎么查

SPI 通信失败通常有三种表现:读回全 0、读回全 1、读回数据错位。对应的排查步骤:

  • 读回全 0:先检查片选信号有没有拉低,再检查 FPGA 端 SPI 从机模块有没有被正确综合进去。可以用 FPGA 的在线逻辑分析仪(如 SignalTap 或 ILA)抓一下 SPI 接口的信号。
  • 读回全 1:通常是 MISO 线没有正确驱动,检查 FPGA 端 MISO 有没有配置成输出,或者有没有上拉电阻冲突。
  • 数据错位:几乎都是时钟极性相位不匹配,参考前面说的改成 Mode 3 试试。

还有一个容易被忽略的点:STM32 的 SPI 引脚复用要配置正确。我有一次把 SPI1 的引脚配到了 SPI2 的复用功能上,结果怎么都不通,查了半天才发现是引脚映射搞错了。

5.3 量程不够或出现模糊怎么办

如果目标距离超过无模糊量程,相位差会绕圈,测出来的距离会突然跳到一个错误值。解决方法是多频解模糊。具体做法是:先用 1 MHz 粗测得到一个粗略距离(无模糊量程 150 米),再用 10 MHz 精测得到一个高精度但可能模糊的距离,然后用粗测值确定精测值的周期数。

举个例子:粗测得到 12.3 米,精测得到 2.3 米(因为 10 MHz 下 12.3 米对应的相位差超过了 2π,实际测出来是 2.3 米)。那么真实距离就是 12.3 米,精测值用来提供毫米级的小数部分。这个逻辑在 STM32 里实现,几行代码就能搞定。

5.4 FPGA 资源不够用的优化技巧

如果你的 FPGA 比较小(比如 EP4CE10 这类入门级芯片),可能会遇到资源不够的问题。我总结了几条优化经验:

  • FIR 滤波器改折叠结构:用 4 个乘法器分时复用,资源占用降到 1/8,代价是吞吐率下降,但 10 MHz 调制信号对吞吐率要求不高,完全够用。
  • CORDIC 改迭代结构:流水线 CORDIC 用 16 个寄存器,迭代 CORDIC 只用 1 个,但需要 16 个时钟周期算一次。因为相位计算不需要每个采样点都算,可以攒够一批数据再算,所以迭代结构完全可行。
  • DDS 查找表用块 RAM:不要用分布式 RAM 实现正弦查找表,块 RAM 更省逻辑资源。
  • SPI 从机用状态机实现:不要用现成的 IP 核,自己写一个简单的状态机,资源占用小很多。

6. 实测数据与性能评估

6.1 不同距离下的测量误差

我在实验室里用卷尺量了几个标准距离,每个点测 100 次取平均,结果如下:

实际距离测量均值误差标准差
0.5 m0.502 m+2 mm1.2 mm
1.0 m0.998 m-2 mm1.5 mm
2.0 m2.003 m+3 mm1.8 mm
5.0 m4.997 m-3 mm2.1 mm
10.0 m10.005 m+5 mm3.5 mm

可以看到,近距离误差在 ±3 毫米以内,10 米处误差稍微大一点,但也在 ±5 毫米以内。标准差随着距离增加而增大,这是因为回波信号强度随距离平方衰减,信噪比下降导致相位测量抖动增加。如果需要在更远距离保持精度,可以增加激光功率或者增加相干累加次数。

6.2 刷新率与响应时间

系统当前配置下,单次测距耗时约 10 毫秒(包括 FPGA 采集 1000 个周期做相干累加,以及 SPI 传输和 STM32 计算),实际刷新率约 100 Hz。如果降低累加次数到 100 次,刷新率可以提高到 500 Hz,但精度会下降。这是一个典型的精度与速度的权衡,根据具体应用场景调整即可。

对于静态测量场景(如形变监测),可以用 1000 次累加追求最高精度;对于动态跟踪场景(如机械臂末端定位),可以用 100 次累加保证实时性。

6.3 温度漂移与长期稳定性

连续运行 2 小时后,我发现测量值有大约 1-2 毫米的漂移。排查后发现主要来源是APD 的偏压随温度变化,以及激光二极管的波长随温度漂移。解决方法是加一个温度传感器(如 DS18B20),实时监测环境温度,然后在 STM32 里做温度补偿。补偿系数需要实验测定:在 20°C 和 40°C 下分别测量同一距离,算出漂移量,然后线性插值。

这个补偿做完之后,2 小时漂移降到了 0.5 毫米以内,基本满足工业级应用需求。

7. 后续扩展方向与个人体会

这套 PLFM_RADAR 骨架目前只实现了单点测距,但扩展空间很大。比如把单点激光换成振镜扫描,就可以做二维扫描测距,生成点云;把 FPGA 里的鉴相算法从正交解调换成 FFT,可以同时处理多个调制频率,实现更鲁棒的解模糊;把 STM32 的串口通信换成以太网,就可以接入工业网络做远程监测。

我在这个项目里最大的体会是:混合架构系统的难点从来不在单个模块,而在模块之间的接口和时序。FPGA 内部逻辑再复杂,只要仿真通过了基本不会出大问题;但 FPGA 和 STM32 之间的 SPI 通信、ADC 和 FPGA 之间的高速数据接口、模拟前端和数字后端之间的电源与地处理,这些“边界”才是最容易翻车的地方。所以我的建议是,每连接一个新模块,先用最简单的测试程序验证接口,确认无误后再往上叠功能。不要想着一次性把所有代码写完再调试,那样出了问题你根本不知道是哪一层的问题。

另外,仪器真的很重要。一台 200 MHz 带宽的示波器和一个逻辑分析仪,能帮你省下至少一半的调试时间。我一开始想用万用表和 LED 凑合,结果在 SPI 时序问题上卡了整整两天,后来借了一台逻辑分析仪,十分钟就定位到了问题。该花的钱还是要花。

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

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

立即咨询