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 核 模式:

  1. PL 端实现高速接口时序(ADC/DAC SPI 协议)
  2. 通过 AXI4-Lite 总线映射为 ARM 可访问的寄存器
  3. 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  │
                                                           │数据处理  │
                                                           │波长计算  │
                                                           └─────────┘

工作原理

  1. 光源扫描:宽带光源入射 FBG 阵列,各 FBG 反射特定波长
  2. 边缘滤波:OTF (Optical Tunable Filter) 作为边缘滤波器,将波长变化转为光强变化
  3. 光电转换:8 路光电探测器 (PD) 将光强转为电压 (0~5V)
  4. 同步采样:AD7768 以 256 kSPS 同步采样 8 路 PD 信号
  5. 数据处理: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) 核:

  1. 在 Block Design 中添加 ILA IP,连接到待观测信号 (CS, SCLK, DIN, DOUT)
  2. 设置触发条件(如 CS 下降沿)
  3. 综合实现后,通过 JTAG 连接 Hardware Manager
  4. 实时捕获波形,验证 SCLK 频率、数据对齐、CS 时序裕量
  5. 补充手段:用示波器对外部 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

posted @ 2026-09-10 22:11  mo686  阅读(14)  评论(0)    收藏  举报