zynq
FPGA 项目详解 — AD7768 光谱解调采集 & LTC2688 微环热调谐
一、项目总览
1.1 项目背景
本文档涵盖两个基于 Xilinx Zynq-7020 SoC 平台的 FPGA 子项目,均服务于光纤布拉格光栅 (FBG) 波长解调系统的不同功能模块:
| 子项目 | 核心芯片 | 功能定位 | 应用场景 |
|---|---|---|---|
| AD7768 采集模块 | AD7768 (8通道24位ΔΣ ADC) | 高速光电信号同步采样 | FBG 波长解调中 PD 信号采集 |
| LTC2688 控制模块 | LTC2688 (16通道16位 DAC) | 微环谐振器热调谐驱动 | 硅基微环 WDM 系统波长锁定 |
1.2 平台架构
Xilinx Zynq-7020 SoC 集成了双核 ARM Cortex-A9 处理器 (PS) 和 Artix-7 等级 FPGA 逻辑 (PL):
┌─────────────────────────────────────────────────────────────────┐
│ Zynq-7020 SoC │
│ ┌─────────────────────┐ AXI ┌────────────────────────┐ │
│ │ PS (ARM Cortex-A9)│◄──────────►│ PL (Artix-7 Fabric) │ │
│ │ • 双核 667MHz │ Interconn │ • 53,200 LUTs │ │
│ │ • DDR3 控制器 │ │ • 106,400 FFs │ │
│ │ • UART/SPI/I2C │ │ • 140 BRAM (36Kb) │ │
│ │ • GigE/USB │ │ • 220 DSP48E1 │ │
│ └─────────────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
1.3 开发工具链
- Vivado 2018.3:RTL 设计、IP 封装、Block Design、综合实现、Bitstream 生成
- Xilinx SDK:ARM 裸机/FreeRTOS 应用开发
- 开发语言:Verilog HDL (PL)、C (PS)
1.4 设计理念
两个项目均采用 自定义 AXI4-Lite IP 核 模式:
- PL 端实现高速接口时序(ADC/DAC SPI 协议)
- 通过 AXI4-Lite 总线映射为 ARM 可访问的寄存器
- PS 端 ARM 负责上层算法(数据处理、PID 控制)
二、AD7768 高速光电信号采集模块
2.1 AD7768 芯片介绍
AD7768 是 Analog Devices 推出的 8 通道同步采样 24 位 ΔΣ (Sigma-Delta) 模数转换器,专为高精度数据采集系统设计。
核心技术参数
| 参数 | 规格 |
|---|---|
| 通道数 | 8 通道,真正同步采样 (Simultaneous Sampling) |
| 分辨率 | 24 位 (无丢失码) |
| 最大采样率 | 256 kSPS/通道 (Fast Mode) |
| 动态范围 (DR) | 108 dB (Fast Mode) |
| 信噪比 (SNR) | 108 dB |
| THD | -120 dB |
| 输入电压范围 | ±VREF (差分),典型 ±4.096V |
| 数字滤波器 | 内置 sinc5 数字滤波器,可配置抽取率 |
| 功耗模式 | Fast (256kSPS)、Median (128kSPS)、Low Power (32kSPS) |
| 数据接口 | TDM 并行串行输出 (8×DOUT + DCLK + DRDY) |
| 封装 | LQFP-64 |
接口时序特征
DRDY ────┐ ┌────
└──────────────────────────────────────────┘
DCLK ____/‾‾‾\___/‾‾‾\___/‾‾‾\___ ... ___/‾‾‾\___/‾‾‾\____
│ │ │ │ │
DOUT[0] ──X───B31──X───B30──X───B29── ... ──X───B1───X───B0───
DOUT[1] ──X───B31──X───B30──X───B29── ... ──X───B1───X───B0───
...
DOUT[7] ──X───B31──X───B30──X───B29── ... ──X───B1───X───B0───
每帧数据 32 位 = Header (4bit) + 24-bit ADC Data + Status (4bit)
为何选择 AD7768
- 同步采样:8 通道无通道间相位误差,消除了 LTC2408 顺序采样带来的时间偏移
- 高采样率:256 kSPS 远超 LTC2408 的 7.5 SPS,支持动态应变/振动传感
- 高动态范围:108 dB 保证微弱光电信号不被噪声淹没
- 宽带宽:支持 FFT 频域分析,适用于振动频率提取
2.2 在 FBG 解调系统中的角色
信号链路
┌─────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐
│ FBG │───►│ OTF Edge │───►│ 8路 PD │───►│ 跨阻放大 │───►│ AD7768 │
│反射光│ │ Filter │ │光电探测器│ │ 0~5V │ │ 8通道ADC│
└─────┘ └──────────┘ └──────────┘ └──────────┘ └────┬────┘
│ 8×DOUT
│ DCLK+DRDY
┌────▼────┐
│ FPGA │
│(Zynq PL)│
└────┬────┘
│ AXI4-Lite
┌────▼────┐
│ ARM PS │
│数据处理 │
│波长计算 │
└─────────┘
工作原理
- 光源扫描:宽带光源入射 FBG 阵列,各 FBG 反射特定波长
- 边缘滤波:OTF (Optical Tunable Filter) 作为边缘滤波器,将波长变化转为光强变化
- 光电转换:8 路光电探测器 (PD) 将光强转为电压 (0~5V)
- 同步采样:AD7768 以 256 kSPS 同步采样 8 路 PD 信号
- 数据处理:ARM 处理器计算各通道比值,反演 FBG 波长位移
相比 STM32+LTC2408 的升级意义
- LTC2408 方案问题:7.5 SPS 顺序采样,8 通道采完需 ~1s,无法捕捉动态信号
- AD7768 优势:256 kSPS × 8 通道同步,可进行 FFT 频谱分析
- 新应用场景:结构健康监测中的振动频率提取 (10Hz~100kHz)
2.3 FPGA 接口设计详解
以下为项目中 ad7768.v 模块的完整代码分析:
模块端口定义
module ad7768 (
// 设备接口 —— 直连 AD7768 芯片引脚
input clk_in, // DCLK: AD7768 输出的数据时钟
input ready_in, // DRDY: 数据就绪信号 (帧同步)
input [7:0] data_in, // DOUT[7:0]: 8通道并行数据线
// 数据输出接口 —— 连接 AXI 寄存器
output [31:0] adc_data_0, // 通道0 处理后数据 (μV值)
output [31:0] adc_data_1, // 通道1
output [31:0] adc_data_2, // 通道2
output [31:0] adc_data_3, // 通道3
output [31:0] adc_data_4, // 通道4
output [31:0] adc_data_5, // 通道5
output [31:0] adc_data_6, // 通道6
output [31:0] adc_data_7 // 通道7
);
关键内部寄存器
localparam FRAME_CNT = 'd31; // 每帧32位 (计数0~31)
reg [31:0] adc_bit_cnt = FRAME_CNT; // 位计数器 (倒计数)
reg [31:0] adc_out_cnt = 'd0; // 输出抽取计数器
// 每个通道两组寄存器:
// _a 后缀:移位接收缓冲 (正在接收的帧)
// _s 后缀:稳定输出寄存器 (已完成转换的数据)
reg [31:0] adc_data_0_a = 'd0; // CH0 接收缓冲
reg [31:0] adc_data_0_s = 'd0; // CH0 输出锁存
// ... 同理 CH1~CH7
核心逻辑:数据接收状态机
// 在 DCLK 下降沿采样数据 (AD7768 在上升沿更新数据,下降沿采样最稳定)
always @(negedge adc_clk) begin
adc_ready_s <= adc_ready;
if (adc_ready_s == 1'b1) begin
// ═══ 状态A:DRDY有效 → 一帧接收完成 ═══
adc_out_cnt <= adc_out_cnt + 'd1;
// 抽取控制:每N个DRDY输出一次 (此处N=1,即每帧都输出)
if ((adc_out_cnt % 'd1) == 'd0) begin
// ★ 原始数据转μV值的定点运算 ★
// 公式:raw_24bit × 4,096,000 / 16,777,216
// 等效:(raw & 0x00FFFFFF) × 100 >> 6 × 100 >> 6
adc_data_0_s <= (((((adc_data_0_a & 32'h00ffffff) * 'd100) >> 'd6) * 'd100) >> 'd6);
adc_data_1_s <= (((((adc_data_1_a & 32'h00ffffff) * 'd100) >> 'd6) * 'd100) >> 'd6);
// ... 同理 CH2~CH7
end
adc_bit_cnt <= FRAME_CNT; // 重置位计数器,准备接收下一帧
end
else begin
// ═══ 状态B:DRDY无效 → 逐位移入数据 ═══
if (adc_bit_cnt >= 'd1) begin
// 8通道并行移位接收,每个时钟周期同时移入8个通道各1bit
adc_data_0_a <= ((adc_data_0_a | data_in[0]) << 1'b1);
adc_data_1_a <= ((adc_data_1_a | data_in[1]) << 1'b1);
adc_data_2_a <= ((adc_data_2_a | data_in[2]) << 1'b1);
// ... 同理 CH3~CH7
adc_bit_cnt <= adc_bit_cnt - 1'b1;
end
end
end
原始数据到 μV 的转换推导
AD7768 满量程电压 VREF = 4.096V = 4,096,000 μV
24位分辨率:2^24 = 16,777,216
每个 LSB 对应:4,096,000 / 16,777,216 ≈ 0.244 μV
定点计算(避免浮点):
raw × 4,096,000 / 16,777,216
= raw × (100 × 100 × 410) / (64 × 64 × 4096)
≈ raw × 100 >> 6 × 100 >> 6 (近似,误差 <0.1%)
代码实现:
step1: (raw & 0x00FFFFFF) → 取低24位有效数据
step2: × 100 >> 6 → 第一次缩放
step3: × 100 >> 6 → 第二次缩放
结果单位:μV (微伏)
2.4 BRAM 数据缓冲
bram_rd_wr 模块实现 PL 侧的 BRAM 读写控制:
module bram_rd_wr(
input clk, // 系统时钟
input rst_n, // 复位 (低有效)
input [3:0] we, // 写使能 (字节选通)
input start, // 启动信号 (上升沿触发)
input [31:0] start_addr, // 起始地址
input [31:0] len, // 数据长度 (字节)
// BRAM 端口
output ram_clk, // BRAM 时钟
input [31:0] ram_rd_data, // 读数据
output reg ram_en, // BRAM 使能
output reg [31:0] ram_addr, // 地址
output [3:0] ram_we, // 写使能
output reg [31:0] ram_wr_data, // 写数据
output ram_rst // BRAM 复位
);
工作流程
┌───────┐ pos_start ┌───────┐ addr==len-4 ┌───────┐
│ IDLE │────────────────────►│ WRITE │──────────────────►│ DONE │
│flow=0 │ │flow=1 │ │flow=2 │
└───────┘ └───────┘ └───┬───┘
▲ │
└────────────────────────────────────────────────────────┘
自动返回 IDLE
关键设计要点:
- 上升沿检测:
pos_start = ~start_d1 & start_d0,双级寄存器消除亚稳态 - 地址自增 4:32位数据宽度,字节地址每次 +4 保证字对齐
- 双端口 BRAM:PL 侧写入 ADC 数据,PS 侧通过 AXI 总线读取
2.5 AXI IP 核封装
IP 核层次结构
ad7768_v1_0 (顶层封装)
└── ad7768_v1_0_S00_AXI (AXI4-Lite 从接口)
├── 8个 32-bit 从寄存器 (slv_reg0 ~ slv_reg7)
├── 写通道握手逻辑 (AWVALID/AWREADY, WVALID/WREADY)
├── 读通道握手逻辑 (ARVALID/ARREADY, RVALID/RREADY)
└── 用户逻辑区域 (连接 ad7768.v 模块)
寄存器映射表
| 偏移地址 | 寄存器 | 方向 | 功能 |
|---|---|---|---|
| 0x00 | slv_reg0 | R | ADC 通道0 数据 (μV) |
| 0x04 | slv_reg1 | R | ADC 通道1 数据 (μV) |
| 0x08 | slv_reg2 | R | ADC 通道2 数据 (μV) |
| 0x0C | slv_reg3 | R | ADC 通道3 数据 (μV) |
| 0x10 | slv_reg4 | R | ADC 通道4 数据 (μV) |
| 0x14 | slv_reg5 | R | ADC 通道5 数据 (μV) |
| 0x18 | slv_reg6 | R | ADC 通道6 数据 (μV) |
| 0x1C | slv_reg7 | R | ADC 通道7 数据 (μV) |
AXI 地址解码机制
// 地址位宽 5-bit,ADDR_LSB=2 (32位数据 → 字节地址右移2位)
// OPT_MEM_ADDR_BITS=2 → 支持 2^3=8 个寄存器
localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1; // =2
localparam integer OPT_MEM_ADDR_BITS = 2;
// 读地址解码:取地址 [4:2] 位作为寄存器选择
case (axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS : ADDR_LSB])
3'h0 : reg_data_out <= slv_reg0; // addr[4:2]=000 → CH0
3'h1 : reg_data_out <= slv_reg1; // addr[4:2]=001 → CH1
// ...
3'h7 : reg_data_out <= slv_reg7; // addr[4:2]=111 → CH7
endcase
ARM PS 端读取示例 (SDK C 代码)
#include "xil_io.h"
#define AD7768_BASEADDR 0x43C00000 // AXI IP 映射基地址
// 读取8通道ADC数据
uint32_t adc_ch0 = Xil_In32(AD7768_BASEADDR + 0x00); // 通道0 (μV)
uint32_t adc_ch1 = Xil_In32(AD7768_BASEADDR + 0x04); // 通道1 (μV)
// ...
uint32_t adc_ch7 = Xil_In32(AD7768_BASEADDR + 0x1C); // 通道7 (μV)
// 计算比值用于波长解调
float ratio = (float)adc_ch0 / (float)adc_ch1;
float wavelength = calibration_func(ratio);
2.6 与 STM32+LTC2408 方案对比
| 对比项目 | STM32+LTC2408 方案 | Zynq+AD7768 方案 |
|---|---|---|
| 采样率 | 7.5 SPS/通道 | 256,000 SPS/通道 |
| 通道同步 | 顺序采样(通道间有时差) | 真正同步采样(零时差) |
| ADC分辨率 | 24 位 (有效位 ~21) | 24 位 (有效位 ~18 @ 256kSPS) |
| 动态范围 | 110 dB (但仅限直流) | 108 dB (支持宽带信号) |
| 接口 | SPI (单通道逐个读取) | 并行 TDM (8通道同时传输) |
| 数据带宽 | ~240 bit/s | ~49 Mbit/s |
| 处理器 | STM32F103 (72MHz Cortex-M3) | Zynq ARM A9 (667MHz 双核) |
| FFT能力 | 无(采样率太低) | 支持 (Nyquist 128kHz) |
| 适用场景 | 静态应变、温度监测 | 动态振动、冲击检测 |
| 开发难度 | 低 (HAL库+简单SPI) | 高 (Vivado+AXI IP+SDK) |
| 单位成本 | ~¥50 (MCU+ADC) | ~¥200 (Zynq SoM) |
| 功耗 | ~200 mW | ~2 W |
三、LTC2688 DAC 微环谐振器热调谐模块
3.1 LTC2688 芯片介绍
LTC2688 是 Analog Devices (原 Linear Technology) 的 16 通道 16 位电压输出 DAC,适用于需要多路精密模拟输出的工业/科研系统。
核心技术参数
| 参数 | 规格 |
|---|---|
| 通道数 | 16 通道独立输出 |
| 分辨率 | 16 位 (1 LSB = 76.3 μV @ ±2.5V span) |
| 输出范围 | 可配置:0~5V, 0~10V, ±2.5V, ±5V, ±10V, ±16.5V |
| INL | ±1 LSB (max) |
| DNL | ±1 LSB (max) |
| 建立时间 | 7 μs (典型,满量程阶跃至0.5LSB) |
| 毛刺脉冲 | 2 nV·s |
| 接口 | SPI, SCLK 最高 50 MHz |
| 命令字 | 32 位 (Command + Address + Data) |
| 特殊功能 | 每通道独立 span 配置、Toggle 模式、CRC 保护 |
| 电源 | VCC = 4.75~5.25V, V+ 最高 16.5V, V- 最低 -16.5V |
| 封装 | LQFP-52 |
| 工作温度 | -40°C ~ +125°C (工业级) |
SPI 接口特性
- 全双工:DIN (MOSI) 写入命令,DOUT (MISO) 回读状态
- 32位帧:CS 拉低后传输 32 个 SCLK 周期
- CPOL=0, CPHA=0:空闲时 SCLK 低,上升沿采样
- MSB First:最高位先传输
3.2 硅基微环谐振器 (MRR) 热调谐原理
微环谐振器基础
硅基微环谐振器 (Silicon Micro-Ring Resonator, MRR) 是集成光子学中的核心器件,由一个环形波导与一条或两条直波导耦合而成:
直通端 (Through)
─────────────────────────►
│ │
│ 耦合区 │
│ │
┌────┘ └────┐
│ │
│ ○ 环形波导 │
│ R = 5~50 μm │
│ │
└────┐ ┌────┘
│ 耦合区 │
│ │
─────────────────────────►
下载端 (Drop)
谐振条件
微环的谐振波长由以下条件决定:
λ_res = n_eff × 2πR / m
其中:
λ_res : 谐振波长 (典型 1550 nm 附近)
n_eff : 有效折射率 (硅波导 ~2.4 @1550nm)
R : 环半径 (典型 5~50 μm)
m : 谐振阶数 (整数)
热光效应 (Thermo-Optic Effect)
硅材料具有显著的热光系数:
dn/dT ≈ 1.86 × 10⁻⁴ /°C (硅 @1550nm)
温度变化 → 折射率变化 → 谐振波长移动:
dλ/dT ≈ 80 pm/°C (标准 SOI 微环,R=10μm)
热调谐实现
在微环上方集成加热电阻(通常为 TiN 薄膜或掺杂硅),通过电流加热改变局部温度:
| 参数 | 典型值 |
|---|---|
| 加热电阻材料 | TiN (氮化钛) 或掺杂多晶硅 |
| 电阻值 | ~1 kΩ |
| 调谐效率 | ~2.5 mW/nm (每偏移1nm所需功耗) |
| 热时间常数 | 10~100 μs |
| 最大调谐范围 | ~20 nm (需 ~50 mW) |
| FSR (自由光谱范围) | ~10 nm (R=10μm) |
多通道应用:WDM 系统
在波分复用 (WDM) 系统中,多个微环分别对应不同波长通道:
λ1 λ2 λ3 λ16
Input ──┬─────┬─────┬── ... ──┬──── Through
│ │ │ │
○R1 ○R2 ○R3 ○R16
│ │ │ │
Drop1 Drop2 Drop3 Drop16
│ │ │ │
┌┴─────┴─────┴── ... ──┴──┐
│ LTC2688 16通道DAC │
│ 独立驱动16个加热电阻 │
└──────────┬───────────────┘
│ SPI
┌──────┴──────┐
│ Zynq FPGA │
└─────────────┘
应用场景:WDM MUX/DEMUX、光开关、可重构上下路复用器 (ROADM)
3.3 系统架构 (Zynq PS+PL)
┌──────────────────────────────────────────────────────────────────────┐
│ Zynq-7020 SoC │
│ │
│ ┌──────────────────────┐ ┌───────────────────────────────┐ │
│ │ PS (ARM A9) │ │ PL (FPGA) │ │
│ │ │ AXI4 │ │ │
│ │ ┌────────────────┐ │ Lite │ ┌─────────────────────────┐ │ │
│ │ │ 波长锁定算法 │ │◄────────►│ │ dac_controller_v1_0 │ │ │
│ │ │ (PID 控制) │ │ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ │ │ │ │ AXI4-Lite Slave │ │ │ │
│ │ │ Xil_Out32() │ │ │ │ │ slv_reg0 (32bit) │ │ │ │
│ │ └────────────────┘ │ │ │ └─────────┬─────────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ ┌────────────────┐ │ │ │ ┌─────────▼─────────┐ │ │ │
│ │ │ ADC 读取 │ │ │ │ │ ltc2688.v (顶层) │ │ │ │
│ │ │ 监测环输出功率 │ │ │ │ │ ┌──────────────┐ │ │ │ │
│ │ └────────────────┘ │ │ │ │ │ltc2688_driver│ │ │ │ │
│ └──────────────────────┘ │ │ │ │ (SPI 驱动) │ │ │ │ │
│ │ │ │ └──────┬───────┘ │ │ │ │
│ │ │ └─────────┼─────────┘ │ │ │
│ │ └────────────┼────────────┘ │ │
│ └───────────────┼──────────────┘ │
└────────────────────────────────────────────────────┼────────────────┘
│ SPI (CS/DIN/SCLK/DOUT)
┌──────▼──────┐
│ LTC2688 │
│ 16通道 DAC │
└──┬──┬──┬──┬─┘
│ │ │ │ 16路模拟输出
┌──▼──▼──▼──▼─────────────┐
│ 16个加热电阻 (TiN) │
│ 硅光芯片上的微环阵列 │
└─────────────────────────┘
闭环控制流程
1. ARM 读取 PD 监测值 (通过 ADC)
2. 比较当前功率与目标值 → 计算误差
3. PID 算法输出新的 DAC 码值
4. 写入 AXI 寄存器 → 触发 SPI 传输
5. LTC2688 更新加热电压 → 微环温度变化 → 波长移动
6. 重复步骤 1 (闭环)
3.4 FPGA SPI 驱动详解
ltc2688_driver.v 模块架构
module ltc2688_driver #(
parameter DIV_PARAM = 8 // SCLK = 50MHz / (2 × 8) = 3.125 MHz
)(
input Clk, // 系统时钟 50MHz
input Rst_n, // 异步复位 (低有效)
input [31:0] DAC_DATA, // 32位 SPI 命令字 (来自顶层)
input DAC_DOUT, // SPI MISO (LTC2688 回读)
input Start, // 传输启动信号
output reg Set_Done, // 传输完成标志 (单周期脉冲)
output reg DAC_CS_N, // SPI 片选 (低有效)
output reg DAC_DIN, // SPI MOSI (数据输出到 LTC2688)
output reg DAC_SCLK, // SPI 时钟
output reg [31:0] DAC_DATA_O, // 回读数据 (DOUT 移入)
output DAC_State // CS状态输出 (高=空闲)
);
时钟分频机制
// 分频计数器:系统时钟 50MHz → SCLK2X 脉冲 (6.25MHz)
// SCLK2X 的每个脉冲驱动状态机前进一步
// 实际 SCLK 频率 = 50MHz / (2 × DIV_PARAM) = 3.125MHz
always @(posedge Clk or negedge Rst_n)
if (!Rst_n)
DIV_CNT <= 4'd0;
else if (en) begin
if (DIV_CNT == (DIV_PARAM - 1))
DIV_CNT <= 4'd0; // 计满归零
else
DIV_CNT <= DIV_CNT + 1;
end else
DIV_CNT <= 4'd0; // 未使能时保持0
// 产生 SCLK2X 使能脉冲
always @(posedge Clk or negedge Rst_n)
if (!Rst_n)
SCLK2X <= 1'b0;
else if (en && (DIV_CNT == (DIV_PARAM - 1)))
SCLK2X <= 1'b1; // 每8个系统时钟产生一次脉冲
else
SCLK2X <= 1'b0;
SPI 状态机 (66 步)
SCLK_GEN_CNT: 0 1 2 3 4 ... 63 64 65
│ │ │ │ │ │ │ │
动作: CS↓ ↑ ↓ ↑ ↓ ↑ ↓ CS↑
D31 D30 D29 D0
状态0: CS拉低,输出 DAC_DATA[31] (MSB)
状态1: SCLK↑,采样 DOUT (读回 bit31)
状态2: SCLK↓,输出 DAC_DATA[30]
状态3: SCLK↑,采样 DOUT (读回 bit30)
...
状态62: SCLK↓,输出 DAC_DATA[0] (LSB)
状态63: SCLK↑,采样 DOUT (读回 bit0)
状态64: SCLK↓ (最后一个下降沿)
状态65: CS拉高 (传输结束)
核心状态机代码详解
always @(posedge Clk or negedge Rst_n)
if (!Rst_n) begin
DAC_DIN <= 1'b1;
DAC_SCLK <= 1'b0;
DAC_CS_N <= 1'b1; // 复位:CS高(空闲), SCLK低, DIN高
DAC_DATA_O <= 32'b0;
end
else if (!Set_Done && SCLK2X) begin
case (SCLK_GEN_CNT)
// 状态0:拉低CS,输出最高位
0: begin
DAC_CS_N <= 1'b0; // 片选有效
DAC_DIN <= r_DAC_DATA[31]; // 输出 MSB
DAC_SCLK <= 1'b0;
end
// 奇数状态(1,3,5,...,63):SCLK上升沿 + 采样DOUT
1,3,5,...,63: begin
DAC_SCLK <= 1'b1; // 时钟上升沿
// 采样回读数据:位置 = 32 - (CNT+1)/2
DAC_DATA_O[32-(SCLK_GEN_CNT+1)/2] <= DAC_DOUT;
end
// 偶数状态(2,4,6,...,62):SCLK下降沿 + 输出下一位
2,4,6,...,62: begin
DAC_DIN <= r_DAC_DATA[32 - SCLK_GEN_CNT/2 - 1]; // 下一位数据
DAC_SCLK <= 1'b0; // 时钟下降沿
end
64: DAC_SCLK <= 1'b0; // 最终下降沿
65: DAC_CS_N <= 1'b1; // 释放片选
endcase
end
ltc2688.v 顶层模块:边沿检测触发
module ltc2688 #(
parameter DIV_PARAM = 8
)(
input Clk, // 50MHz
input Rst_n,
input [31:0] DAC_DATA, // 来自 AXI 寄存器的命令字
input DAC_DOUT, // SPI MISO
output DAC_CS_N,
output DAC_DIN,
output [31:0] DAC_DATA_O,
output DAC_SCLK
);
// ★ 关键设计:仅当数据变化时才触发传输 ★
always @(posedge Clk or negedge Rst_n)
if (!Rst_n)
Start <= 1'd0;
else if (r_DAC_DATA != DAC_DATA) // 新数据 ≠ 旧数据 → 触发
Start <= 1'b1;
else
Start <= 1'd0;
// 在 CS 空闲时锁存新数据
always @(posedge Clk or negedge Rst_n)
if (!Rst_n)
r_DAC_DATA <= 32'd0;
else if (DAC_State) // DAC_State=CS_N=1 → 空闲
r_DAC_DATA <= DAC_DATA;
设计亮点:
- 变化检测机制避免重复发送相同数据,降低 SPI 总线负载
- 仅在 CS 空闲时接受新数据,防止传输中途数据被篡改
- 单次传输耗时 = 66 × 2 × DIV_PARAM / 50MHz = 66 × 16 / 50M ≈ 21.1 μs
3.5 LTC2688 32-bit SPI 命令字格式
命令字结构
┌────────┬──────────┬───────────┬──────────┬───────────┐
│ Bit 31~28 │ Bit 27~24 │ Bit 23~20 │ Bit 19~4 │ Bit 3~0 │
│ Command │ Reserved │ CH Address │ 16-bit Data │ Feature │
└────────┴──────────┴───────────┴──────────┴───────────┘
命令码 (Bit[31:28])
| 命令码 | 功能 | 说明 |
|---|---|---|
| 0x0 | No Operation | 空操作,可用于读回 |
| 0x2 | Write to Input Register | 写入但不更新输出 |
| 0x3 | Write and Update | 写入并立即更新 DAC 输出 |
| 0x4 | Update (Power Up) | 使用已写入的值更新输出 |
| 0x5 | Power Down | 通道掉电 |
| 0x6 | Write Span | 配置通道输出范围 |
| 0x7 | Read Back | 回读寄存器值 |
通道地址 (Bit[23:20])
0x0 = CH0, 0x1 = CH1, ... 0xF = CH15
常用命令示例
// 设置通道0输出为中间值 (0x8000),Write and Update
uint32_t cmd = 0x30008000;
// │ │ │
// │ │ └── 16-bit data = 0x8000 (中间码)
// │ └──── Channel 0
// └──────── Command 0x3 (Write & Update)
// 配置通道5输出范围为 0~5V
uint32_t span_cmd = 0x60050000 | (0x0003 << 4);
// │ │ │
// │ │ └── Span code 0x3 = 0~5V
// │ └──────────── Channel 5
// └────────────────── Command 0x6 (Write Span)
Span 配置码
| Span Code | 输出范围 | LSB 大小 |
|---|---|---|
| 0x0000 | 0 to +5V | 76.3 μV |
| 0x0001 | 0 to +10V | 152.6 μV |
| 0x0002 | ±5V | 152.6 μV |
| 0x0003 | ±10V | 305.2 μV |
| 0x0004 | ±2.5V | 76.3 μV |
| 0x000C | 0 to +2.5V | 38.1 μV |
3.6 CRC 校验模块
crc6_d1.v 模块分析
项目使用 CRC-32 (Ethernet) 多项式对 SPI 数据进行完整性校验:
// CRC-32 多项式: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10
// + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
// 十六进制表示: 0xEDB88320 (反转形式,LSB-first)
// 输入宽度: 1 bit (逐位计算,适配串行SPI)
module crc (
input [31:0] crcIn, // 当前 CRC 状态
input [0:0] data, // 输入数据位
output [31:0] crcOut // 下一个 CRC 状态
);
CRC 计算原理
逐位 (bit-serial) CRC 实现:每个时钟周期处理 1 位数据,32 个时钟周期完成一帧校验。
┌─────────────────────────────────┐
data[0] ───────►│ XOR 网络 (组合逻辑) │
│ │
crcIn[31:0] ──►│ 基于多项式 0xEDB88320 │──► crcOut[31:0]
│ 的 LFSR (线性反馈移位寄存器) │
└─────────────────────────────────┘
使用场景
ARM 计算 DAC 命令字 + CRC → SPI 发送 (32bit Data + CRC)
│
LTC2688 内部校验 CRC
│
┌────────────┼────────────┐
│ │ │
CRC 匹配 CRC 不匹配
│ │
更新 DAC 输出 保持上次值 (忽略错误帧)
为什么需要 CRC?
- 硅光芯片上的加热电阻由 DAC 直接驱动,错误电压可能导致:
- 过热损坏光波导 (~300°C 以上硅波导会发生永久性变形)
- 热应力导致芯片机械断裂
- WDM 系统串扰(错误波长偏移影响相邻通道)
- SPI 在 PCB 长走线 (>10cm) 或高速传输时易受 EMI 干扰
- CRC-32 可检测所有 1~3 位错误,以及大多数突发错误
3.7 SDK 软件端 (ARM Cortex-A9)
软件架构
┌─────────────────────────────────────────────┐
│ ARM Cortex-A9 软件 │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ PID 控制 │ │ 16通道波长管理器 │ │ │
│ │ └────┬─────┘ └────────┬─────────┘ │ │
│ └───────┼─────────────────┼────────────┘ │
│ ┌───────┼─────────────────┼────────────┐ │
│ │ │ HAL 驱动层 │ │ │
│ │ ┌────▼─────┐ ┌───────▼────────┐ │ │
│ │ │ ADC 读取 │ │ DAC 写入 │ │ │
│ │ │Xil_In32()│ │Xil_Out32() │ │ │
│ │ └──────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
DAC 控制代码示例
#include "xil_io.h"
#include "xparameters.h"
#define DAC_BASEADDR XPAR_DAC_CONTROLLER_0_S00_AXI_BASEADDR
// 构建 LTC2688 命令字:Write & Update 指定通道
uint32_t build_dac_cmd(uint8_t channel, uint16_t code) {
// Command=0x3 (Write&Update), Channel, 16-bit data
return (0x3 << 28) | ((uint32_t)channel << 20) | ((uint32_t)code << 4);
}
// 设置通道电压
void set_heater_voltage(uint8_t ch, float voltage_mV) {
// 假设 span = 0~5V, 16-bit
uint16_t code = (uint16_t)(voltage_mV / 5000.0f * 65535.0f);
uint32_t cmd = build_dac_cmd(ch, code);
Xil_Out32(DAC_BASEADDR, cmd); // 写入 AXI 寄存器 → 触发 SPI
}
// PID 控制循环
void wavelength_lock_loop(void) {
float setpoint[16]; // 16个通道的目标功率值
float kp = 0.5, ki = 0.01, kd = 0.1;
float integral[16] = {0};
float prev_error[16] = {0};
while (1) {
for (int ch = 0; ch < 16; ch++) {
float current_power = read_monitor_pd(ch); // 读取监测PD
float error = setpoint[ch] - current_power;
integral[ch] += error;
float derivative = error - prev_error[ch];
float output = kp * error + ki * integral[ch] + kd * derivative;
set_heater_voltage(ch, output);
prev_error[ch] = error;
}
usleep(100); // 10kHz 控制带宽
}
}
四、Zynq SoC 架构知识补充
4.1 PS + PL 分工
PS (Processing System) 主要资源
| 资源 | 规格 |
|---|---|
| CPU | 双核 ARM Cortex-A9 @ 667MHz |
| 缓存 | 32KB I/D L1 + 512KB L2 |
| DDR 控制器 | DDR3/DDR3L, 最高 1066Mbps |
| DMA | 8 通道 PL330 DMA 控制器 |
| 外设 | 2×UART, 2×SPI, 2×I2C, 2×CAN, GigE, USB2.0 |
| 中断控制器 | GIC (支持 PL 中断) |
| 片上 RAM | 256KB OCM |
PL (Programmable Logic) 主要资源 (7020)
| 资源 | 数量 |
|---|---|
| Logic Cells | 85,000 |
| LUT | 53,200 |
| Flip-Flop | 106,400 |
| BRAM (36Kb) | 140 块 (4.9 Mb 总量) |
| DSP48E1 | 220 片 |
| Clock Tiles | 4 MMCM + 4 PLL |
| I/O Bank | 5 (200+ I/O pins) |
PS-PL 互联通道
| 通道 | 位宽 | 方向 | 用途 |
|---|---|---|---|
| M_AXI_GP0/1 | 32-bit | PS→PL | PS 作主设备访问 PL 从设备 |
| S_AXI_GP0/1 | 32-bit | PL→PS | PL 作主设备访问 PS 从设备 |
| S_AXI_HP0~3 | 64-bit | PL→PS | PL 高带宽访问 DDR |
| S_AXI_ACP | 64-bit | PL→PS | PL 通过缓存一致性端口访问 |
| EMIO | 1-bit | 双向 | GPIO/SPI/UART 等扩展到 PL |
4.2 AXI4-Lite 协议简介
AXI4-Lite 是 ARM AMBA 4.0 规范中的轻量级总线协议,用于低速外设寄存器访问。
五组通道
┌─────────────┐ ┌─────────────┐
│ │ ──── AW (写地址) ────► │ │
│ │ ──── W (写数据) ────► │ │
│ Master │ ◄─── B (写响应) ──── │ Slave │
│ (ARM PS) │ ──── AR (读地址) ────► │ (PL IP) │
│ │ ◄─── R (读数据) ──── │ │
└─────────────┘ └─────────────┘
握手机制 (VALID/READY)
┌───┐ ┌───┐ ┌───┐ ┌───┐
CLK ───┘ └───┘ └───┘ └───┘ └───
┌───────────────────────┐
VALID ───┘ └───────── (发送方拉高表示数据有效)
┌───────────┐
READY ───────────────┘ └───────── (接收方拉高表示可接收)
↑
传输发生 (VALID & READY 同时为高)
规则:
- VALID 一旦拉高,在 READY 响应前不得撤回
- READY 可以先于 VALID 拉高(表示一直准备好)
- 实际传输在 VALID=1 且 READY=1 的时钟上升沿完成
写操作时序
CLK _/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_
AWVALID ────/‾‾‾‾‾‾‾‾‾‾‾\_______________
AWREADY ────────────/‾‾‾‾\_______________ (AW 握手)
AWADDR ────────────<ADDR>────────────────
WVALID ────/‾‾‾‾‾‾‾‾‾‾‾\_______________
WREADY ────────────/‾‾‾‾\_______________ (W 握手)
WDATA ────────────<DATA>────────────────
BVALID ──────────────────/‾‾‾‾‾\________ (B 响应)
BREADY ──────────────────/‾‾‾‾‾\________
BRESP ──────────────────< OK >──────────
4.3 Vivado IP 核开发流程
完整开发步骤
步骤1: 创建自定义 IP
┌──────────────────────────────────────────────┐
│ Vivado → Tools → Create and Package New IP │
│ → Create AXI4 Peripheral │
│ → 选择 AXI4-Lite Slave, 数据宽度32, 寄存器数8 │
└──────────────────────────────────────────────┘
│
▼
步骤2: 添加用户逻辑
┌──────────────────────────────────────────────┐
│ 修改 xxx_v1_0_S00_AXI.v: │
│ → 在 "// Add user logic here" 区域添加 │
│ → 实例化 ad7768.v 或 ltc2688.v │
│ → 连接用户端口到 slv_reg 寄存器 │
└──────────────────────────────────────────────┘
│
▼
步骤3: 封装 IP (Package IP)
┌──────────────────────────────────────────────┐
│ → 定义端口 (用户自定义外部引脚) │
│ → 设置地址空间 │
│ → 生成 component.xml │
└──────────────────────────────────────────────┘
│
▼
步骤4: Block Design 集成
┌──────────────────────────────────────────────┐
│ → 添加 Zynq PS IP │
│ → 添加自定义 IP │
│ → Run Connection Automation (自动连线) │
│ → AXI Interconnect 自动插入 │
└──────────────────────────────────────────────┘
│
▼
步骤5: 约束与综合
┌──────────────────────────────────────────────┐
│ → 添加 XDC 引脚约束文件 │
│ → Run Synthesis → Run Implementation │
│ → Generate Bitstream │
└──────────────────────────────────────────────┘
│
▼
步骤6: 导出到 SDK
┌──────────────────────────────────────────────┐
│ → File → Export → Export Hardware (含 bit) │
│ → File → Launch SDK │
│ → 创建 BSP + Application Project │
│ → 编写 C 代码调用 Xil_In32/Xil_Out32 │
└──────────────────────────────────────────────┘
五、面试问答 (FPGA 方向)
Q1: 为什么选 FPGA 而非 MCU 来驱动 AD7768?
答: AD7768 的并行 TDM 接口需要在 DCLK (最高 16MHz) 的每个下降沿同时采样 8 条 DOUT 线。MCU 的 GPIO 中断响应延迟 (~1μs) 无法满足 62.5ns 的时序要求。FPGA 可以用硬件逻辑在每个时钟边沿精确操作,且 8 通道并行处理不增加额外延迟。此外,FPGA 中可用 BRAM 做数据缓冲,实现零开销的 DMA-like 传输。
Q2: 请解释 AXI4-Lite 协议的握手机制
答: AXI4-Lite 使用 VALID/READY 双向握手:
- 发送方置起 VALID 表示数据/地址有效
- 接收方置起 READY 表示可以接收
- 传输在两者同时为高的时钟上升沿完成
- 关键规则:VALID 拉高后不能在 READY 前撤回(防止死锁)
- 与 AXI4-Full 不同,AXI4-Lite 没有 burst 传输,每次只传一个 32/64-bit 数据
Q3: 如何在 FPGA 上调试 SPI 时序?
答: 主要使用 Vivado 内置的 ILA (Integrated Logic Analyzer) 核:
- 在 Block Design 中添加 ILA IP,连接到待观测信号 (CS, SCLK, DIN, DOUT)
- 设置触发条件(如 CS 下降沿)
- 综合实现后,通过 JTAG 连接 Hardware Manager
- 实时捕获波形,验证 SCLK 频率、数据对齐、CS 时序裕量
- 补充手段:用示波器对外部 SPI 物理信号进行验证
Q4: AXI4-Lite、AXI4-Full、AXI4-Stream 的区别?
答:
| 特性 | AXI4-Lite | AXI4-Full | AXI4-Stream |
|---|---|---|---|
| 用途 | 低速寄存器访问 | 高带宽内存访问 | 高速数据流 |
| Burst | 不支持 | 支持 (1~256) | 无地址概念 |
| 数据宽度 | 32/64 bit | 32~1024 bit | 任意 |
| 地址 | 有 (单次) | 有 (burst地址) | 无 |
| 应用 | 控制寄存器、状态读取 | DDR 读写、DMA | 视频流、ADC 数据流 |
| 本项目 | ✓ (IP 控制接口) | × | × |
Q5: ltc2688_driver.v 的状态机如何工作?
答: 使用 66 步线性状态机 (SCLK_GEN_CNT = 0~65):
- 状态 0:拉低 CS,输出 MSB
- 奇数状态 (1,3,...,63):SCLK 上升沿,采样 DOUT 回读
- 偶数状态 (2,4,...,62):SCLK 下降沿,输出下一位数据
- 状态 64:最后下降沿
- 状态 65:释放 CS
总传输时间 = 66 × 2 × DIV_PARAM / f_sys = 66 × 16 / 50M ≈ 21.1μs。状态机由 SCLK2X 使能脉冲驱动,保证时序均匀。
Q6: 该设计中有哪些跨时钟域 (CDC) 问题?
答: AD7768 模块存在 CDC:
- AD7768 的 DCLK (外部时钟) ≠ Zynq 系统时钟 (100/200MHz)
ad7768.v直接使用negedge adc_clk驱动所有逻辑,属于独立时钟域- 输出数据 (
adc_data_x_s) 需要跨到 AXI 时钟域 - 解决方案:双级同步寄存器 + 握手信号,或使用异步 FIFO
- 当前设计简化处理:由于数据更新频率 (256kHz) 远低于 AXI 时钟 (100MHz),直接读取大概率不会出错,但严格设计应加同步器
Q7: 为什么用 BRAM 而非 DDR 做 ADC 数据缓冲?
答:
- 延迟:BRAM 读写延迟 1 个时钟周期;DDR 访问需经 AXI HP 端口,延迟 10~50 个周期
- 确定性:BRAM 延迟固定,适合实时采样;DDR 有仲裁和刷新导致的不确定延迟
- 带宽:8通道 × 32bit × 256kSPS = 65.5 Mbps,BRAM 本地带宽远超需求
- 容量:仅需存储几帧数据供 ARM 读取,几 KB 即可,无需 DDR 的 GB 级容量
- 功耗:BRAM 是 PL 内部资源,无需外部 DDR 接口功耗
Q8: 微环热调谐闭环控制带宽如何确定?
答:
- 加热电阻热时间常数 τ ≈ 10~100 μs → 3dB 带宽 ≈ 1/(2πτ) ≈ 1.6~16 kHz
- DAC 更新速率:单次 SPI 传输 ~21 μs → 最高更新率 ~47 kHz (单通道)
- 16通道轮询:47k/16 ≈ 3 kHz 更新率
- PID 控制带宽应 < 系统最慢环节 → 实际 ~1 kHz
- 如需更高带宽:可并行多个 SPI 接口,或使用 DAC 的 broadcast 模式
Q9: DAC 系统中 CRC 保护的必要性?
答: 在本应用中,LTC2688 直接驱动微环加热电阻:
- 错误码值可能产生过高电压 → 加热功率 P=V²/R 可达数百 mW
- 硅波导在 >300°C 会发生不可逆热损伤
- 单个通道错误可能导致 WDM 串扰(影响所有波长通道)
- CRC-32 可检测所有单比特和双比特错误,以及长度 ≤32 的突发错误
- LTC2688 硬件 CRC 校验失败时自动忽略该帧,输出保持上次有效值
Q10: Zynq 启动流程是什么?
答:
上电 → BootROM → FSBL → PL Bitstream → U-Boot/Application
详细步骤:
1. BootROM (固化在芯片内):从 QSPI/SD/JTAG 加载 FSBL
2. FSBL (First Stage Boot Loader):
- 初始化 DDR 控制器
- 加载 PL bitstream (配置 FPGA 逻辑)
- 加载用户应用 (或 U-Boot)
3. 用户应用:裸机 main() 或 FreeRTOS task
对于本项目,典型启动序列:QSPI Flash → FSBL → Bitstream (含 AD7768/DAC IP) → 裸机应用
六、简历项目描述 (FPGA 方向)
Version A: 强调 ADC 采集 (FBG 光纤传感解调)
项目名称:基于 Zynq FPGA 的 8 通道高速光电信号同步采集系统
- 基于 Xilinx Zynq-7020 SoC 平台,设计并实现了 AD7768 8 通道 24 位 ΔΣ ADC 的 FPGA 接口驱动,实现 256 kSPS/通道同步采样,相比原 STM32+LTC2408 方案采样率提升 34,000 倍
- 开发自定义 AXI4-Lite IP 核,在 PL 端实现并行 TDM 数据解析(8 路 DOUT 同时移位接收),通过 8 个 32 位内存映射寄存器向 PS 端 ARM 提供实时 ADC 数据
- 设计 BRAM 双端口缓冲机制,PL 侧以 DCLK 速率写入,PS 侧以 AXI 总线速率读取,实现跨时钟域无丢帧数据传输
- 在 ARM Cortex-A9 上实现定点数 ADC 原始码到 μV 电压值的实时转换算法,支持 8 通道光电探测器信号的在线波长解调
- 系统应用于 FBG 光纤布拉格光栅波长解调仪,支持 OTF 边缘滤波法,实现 ±1 pm 波长分辨率,满足结构健康监测中动态应变检测需求
Version B: 强调 DAC 控制 (硅光微环热调谐)
项目名称:基于 Zynq FPGA 的 16 通道硅基微环谐振器热调谐控制系统
- 基于 Xilinx Zynq-7020 设计了 LTC2688 16 通道 16 位 DAC 的 SPI 驱动 IP 核,实现 3.125 MHz SCLK、21 μs/帧的高速 DAC 更新,驱动 16 路硅光芯片加热电阻
- 设计 66 步线性状态机完成 32 位全双工 SPI 通信,支持 LTC2688 命令字写入和状态回读,集成 CRC-32 校验模块保障数据完整性
- 实现边沿检测触发机制:仅在 DAC 命令字发生变化时发起 SPI 传输,降低总线负载约 90%,避免无效传输引起的 EMI 干扰
- 在 ARM 端实现 PID 闭环控制算法,以 ~1 kHz 带宽锁定 16 通道微环谐振波长,单通道调谐精度达 ±0.1 pm (对应 DAC 分辨率 76 μV)
- 系统应用于 WDM 波分复用硅光芯片,通过热光效应实现谐振波长 ±10 nm 连续调谐,支持可重构光网络中的动态波长分配
附录:项目文件结构
FPGA/
├── ad7768/ # AD7768 ADC 采集项目
│ ├── ip_repo/
│ │ ├── ad7768_1.0/ # AXI IP 核
│ │ │ └── hdl/
│ │ │ ├── ad7768_v1_0.v # IP 顶层
│ │ │ ├── ad7768_v1_0_S00_AXI.v # AXI 从接口
│ │ │ └── ad7768.v # ADC 数据接收逻辑
│ │ └── pl_bram_rd_1.0/ # BRAM 读写 IP
│ │ └── hdl/
│ │ ├── ad7768.v # ADC 模块 (集成版)
│ │ └── bram_rd.v # BRAM 控制器
│ └── AD7768/ # Vivado 工程
│ └── AD7768.srcs/
│
└── DAC_CONTROLLER/ # LTC2688 DAC 控制项目
├── ip_repo/
│ └── dac_controller_1.0/ # AXI IP 核
│ ├── hdl/
│ │ ├── dac_controller_v1_0.v # IP 顶层
│ │ └── dac_controller_v1_0_S00_AXI.v # AXI 从接口
│ └── src/
│ ├── ltc2688.v # DAC 顶层 (变化检测)
│ └── ltc2688_driver.v # SPI 状态机驱动
├── CRC/
│ ├── crc6_d1.v # CRC-32 逐位计算模块
│ └── crc6_d16.v # CRC-32 16位并行模块
└── dac_controller_7020/ # Zynq-7020 Vivado 工程
文档版本:v1.0
作者:Jarven (东南大学)
创建日期:2024/09
适用平台:Xilinx Zynq-7020, Vivado 2018.3

浙公网安备 33010602011771号