资讯动态

AD9914与HMC835扫频链路设计:SPI配置、FPGA状态机与锁定检测

发布时间:2026/10/17 14:33:04 来源:尧图企业网站定制
简介本资源面向FPGA数字信号处理方向的开发者与学习者提供基于FPGA的AD9914与HMC835芯片驱动调试程序。AD9914部分涵盖扫频、定频、调频等多种工作模式仅需简单修改寄存器值即可切换HMC835部分完成SPI配置与时钟管理两者配合可搭建完整的频率合成与信号发生链路。全部代码采用Verilog编写风格简明清晰便于理解与二次开发。压缩包共77个文件约1.31MB包含Verilog源码、bsf与bdf原理图符号、qip与qsf工程配置、sof与jic烧录文件、rpt与summary综合报告、cdb与hdb编译数据库以及readme说明等覆盖从源码到工程编译、下载验证的完整流程。目前已有898人学习下载适合需要快速上手AD9914与HMC835驱动、参考扫频与调频实现思路的读者可直接复用工程框架并在此基础上修改寄存器配置完成自己的设计。1. 从一份 AD9914 与 HMC835 的工程包说起扫频链路到底怎么搭如果你手头正好有一份名为 AD9914 和 HMC835 的压缩包打开后大概率会看到 FPGA 工程、SPI 配置脚本、寄存器初始化表以及一些零散的测试数据。这不是一份“点开就能跑”的演示而是一套典型的 DDS 加锁相环扫频链路参考设计。AD9914 负责高速直接数字合成输出几百 MHz 级别的中频信号HMC835 则是宽带锁相环频率合成器把参考时钟锁定后输出低相噪的本振。两者通过 SPI 总线受 FPGA 控制配合起来就能做一套覆盖几十 MHz 到几 GHz 的扫频源。适合谁做雷达、通信、测试仪器方向的 FPGA 工程师尤其是需要自己写寄存器时序、调扫频步进和驻留时间的人。这份资源的价值不在“开箱即用”而在它把 AD9914 的 profile 切换、HMC835 的 SPI 写时序、以及扫频触发逻辑都摊开了省去你从数据手册第一页翻起的功夫。2. AD9914 的 SPI 配置与 profile 切换寄存器到底怎么写2.1 先搞清楚 AD9914 的串行端口时序AD9914 支持 SPI 兼容的串行编程但它的时序和普通 SPI Flash 不太一样。SCLK 空闲高电平数据在 SCLK 下降沿被采样CS 拉低后第一个字节是地址紧接着是数据。很多新手直接拿 FPGA 的硬件 SPI 核去怼结果发现写进去的寄存器读出来全是 0血泪经验就是没注意 CPOL 和 CPHA 的组合。AD9914 要求 CPOL1、CPHA1也就是空闲高、第二个边沿采样。如果你用软件模拟 SPI翻转顺序必须严格对齐。下面是一段 Verilog 写的字节写任务我一般会把它放在一个独立的 spi_ctrl.v 里方便复用// AD9914 单字节写时序SCLK 空闲高下降沿采样 task ad9914_write_byte; input [7:0] data; integer i; begin for (i 7; i 0; i i - 1) begin sclk 1b0; // 先拉低准备数据 sdata data[i]; // 数据在 SCLK 低电平期间变化 #10; sclk 1b1; // 上升沿AD9914 在下降沿采样 #10; end sclk 1b0; // 最后一个下降沿完成采样 end endtask逻辑说明循环从高位到低位发送sclk 先低后高再低确保数据在低电平期间稳定AD9914 在 sclk 下降沿采样。参数 #10 是仿真用的延时实际综合时换成时钟分频计数。注意 CS 信号要在整个字节传输期间保持低电平字节之间可以拉高也可以保持低但连续写多个寄存器时建议每字节后拉高至少一个 SCLK 周期让芯片内部地址指针复位。2.2 profile 寄存器与扫频步进的映射关系AD9914 有 8 个 profile 寄存器每个 profile 可以独立设置频率调谐字、幅度和相位。扫频的本质就是按顺序切换 profile或者在一个 profile 内不断更新频率调谐字。前者切换速度快适合跳频后者连续性好适合线性扫频。工程包里通常会把 8 个 profile 预加载成 8 个频点然后用 PROFILE[2:0] 引脚快速切换。但如果你要扫 100 个点8 个 profile 就不够了这时候得走 SPI 动态写 FTW。频率调谐字 FTW 的计算公式是FTW round(f_out * 2^32 / f_sysclk)。假设系统时钟 3.5 GHz想输出 400 MHzFTW 400e6 * 2^32 / 3.5e9 ≈ 490,853,682。转成十六进制是 0x1D41_5C32。写寄存器时先写 FTW 低 32 位到 0x04再写高 8 位到 0x05。注意 AD9914 的寄存器地址是 8 位但很多资料只写低 7 位最高位是读写标志写操作时最高位为 0。提示profile 切换引脚 PROFILE[2:0] 的电平变化必须和 SYNC_CLK 同步否则会出现相位不连续。常见做法是用 FPGA 在 SYNC_CLK 上升沿打一拍再输出。2.3 用状态机实现自动扫频手动写寄存器只能做单点输出要自动扫频得让 FPGA 自己跑一个状态机。下面是一个简化的三段式状态机每 1 ms 切换一个频点频点表存在 ROM 里// 扫频状态机每 1ms 更新一次 FTW localparam IDLE 2d0, UPDATE 2d1, WAIT 2d2; reg [1:0] state; reg [15:0] dwell_cnt; reg [5:0] point_idx; always (posedge clk) begin case (state) IDLE: begin point_idx 0; dwell_cnt 0; state UPDATE; end UPDATE: begin ftw rom_ftw[point_idx]; // 从 ROM 取频点 start_write 1b1; // 触发 SPI 写 state WAIT; end WAIT: begin start_write 1b0; if (dwell_cnt DWELL_MAX) begin dwell_cnt 0; point_idx point_idx 1; state UPDATE; end else begin dwell_cnt dwell_cnt 1; end end endcase end逻辑说明IDLE 初始化索引UPDATE 把 ROM 里的 FTW 推到 SPI 写模块WAIT 计数驻留时间。DWELL_MAX 根据时钟频率算比如 100 MHz 时钟、1 ms 驻留就是 100_000。point_idx 位宽决定扫频点数6 位最多 64 点。注意 SPI 写模块要有 busy 信号状态机得等 busy 拉低才能进下一个 UPDATE否则会丢数据。3. HMC835 的 SPI 写时序与锁定检测别让 PLL 失锁3.1 HMC835 的寄存器映射与写入顺序HMC835 是整数分频 PLL内部有 16 个寄存器上电后必须按特定顺序写否则 charge pump 电流和分频比会乱。常见顺序是先写 R 分频器参考分频再写 N 分频器反馈分频然后写 charge pump 电流和锁定检测配置最后写使能位。工程包里一般会有一个 reg_init.txt里面按地址递增排列了初始化值。但注意HMC835 的 SPI 是 24 位数据加 1 位地址共 25 位和 AD9914 的 8 位地址加 8 位数据完全不同。别想着用同一个 SPI 模块复用时序参数得改。下面是一段 Python 脚本用来生成 HMC835 的寄存器写入序列方便你对照数据手册改分频比# HMC835 寄存器写入序列生成 def hmc835_write(addr, data): # addr: 4位寄存器地址, data: 24位数据 word ((addr 0x0F) 24) | (data 0xFFFFFF) # 返回 25 位二进制字符串高位在前 return format(word, 025b) # 示例设置 R1, N100, 参考 100MHz输出 10GHz regs { 0x0: 0x000001, # R 分频器低字节 0x1: 0x000000, # R 分频器高字节 0x2: 0x000064, # N 分频器低字节100 0x3: 0x000000, # N 分频器高字节 0x4: 0x00000F, # charge pump 电流 0x5: 0x000001, # 锁定检测使能 } for addr, data in regs.items(): print(fAddr 0x{addr:01X}: {hmc835_write(addr, data)})逻辑说明word 是 25 位高 4 位地址低 24 位数据。format 转成二进制字符串后FPGA 按位发送即可。参数 regs 里的值需要根据你的参考频率和输出频率重新算N f_out / f_refR 一般取 1 到 10 之间。注意 HMC835 的 SPI 是 MSB first且 CS 拉低后第一个时钟上升沿采样地址位。3.2 锁定检测与失锁排查HMC835 的锁定检测引脚 LD 输出高电平表示 PLL 锁定。但很多人在调试时发现 LD 一直低第一反应是芯片坏了其实八成是环路滤波器参数不对。环路带宽太宽会导致相位噪声恶化太窄则锁定时间变长甚至失锁。常见做法是先用数据手册推荐的环路滤波器值锁定后再用频谱仪看相噪逐步微调。如果 LD 抖动检查参考时钟是否干净以及 charge pump 电流是否和环路滤波器匹配。注意HMC835 的 LD 引脚是开漏输出必须外接上拉电阻典型值 10 kΩ 到 3.3 V。直接接 FPGA 引脚而不加上拉读到的永远是低电平。3.3 参考时钟与 SYNC 信号的处理AD9914 和 HMC835 通常共用同一个参考时钟比如 100 MHz 恒温晶振。但 AD9914 的系统时钟可以倍频到 3.5 GHz而 HMC835 的鉴相频率就是 100 MHz。两者对参考时钟的抖动要求不同HMC835 对参考相噪更敏感。如果扫频输出杂散大先查参考时钟的电源纹波。另外AD9914 的 SYNC_CLK 输出是系统时钟的 1/24可以用来同步 HMC835 的 LD 采样避免亚稳态。4. 避坑与排查扫频链路最常见的五个翻车点4.1 现象SPI 写完后读回全是 0xFF原因AD9914 的读操作需要先写一个读地址再给 SCLK 才能读出数据。很多人直接发读命令就收结果读到的是移位寄存器里的残留值。解决严格按照“写地址字节最高位为 1→ 给 8 个 SCLK → 读数据”的流程且 CS 在整个读周期保持低。4.2 现象扫频输出在频点切换处出现尖峰原因profile 切换时相位不连续或者 FTW 更新时没有等 SPI 写完就切了。解决用 SYNC_CLK 同步 profile 引脚或者在 FTW 更新后插入至少 10 个 SYNC_CLK 周期的等待。常见做法是在状态机里加一个 WAIT_SPI 状态等 busy 拉低再切。4.3 现象HMC835 锁定时间超过 10 ms原因环路滤波器带宽太窄或者 charge pump 电流设得太小。解决先按数据手册的推荐值设置如果还慢把环路带宽从 50 kHz 提到 100 kHz同时检查 VCO 的调谐电压范围是否在合理区间。注意调谐电压超出范围会导致失锁。4.4 现象输出频谱底噪偏高杂散多原因AD9914 的 DAC 满量程输出没有正确匹配或者 HMC835 的参考时钟泄漏。解决AD9914 的 DAC 输出要接 50 Ω 到地且用差分转单端变压器。HMC835 的参考输入端加一个窄带滤波器滤掉谐波。另外FPGA 的 SPI 时钟线要远离模拟电源必要时加屏蔽。4.5 现象上电后 AD9914 无输出原因复位时序不对。AD9914 的 RESET 引脚需要至少 1 个 SYNC_CLK 周期的高电平且复位后要等 1 ms 才能写寄存器。解决用 FPGA 控制复位上电后先拉高 RESET 保持 10 us再拉低然后延时 5 ms 再开始 SPI 初始化。别用 RC 复位电路一致性差。5. 进阶技巧用 Python 上位机做扫频参数在线整定5.1 为什么要在上位机做参数整定FPGA 里改一个频点表要重新综合动辄十几分钟。我一般会在 PC 上写一个 Python 脚本通过串口或以太网把频点表下发给 FPGAFPGA 只负责缓存和扫频。这样调驻留时间、步进间隔、起止频率都是秒级的事。下面是一个用 pyserial 下发频点表的例子import serial import struct # 打开串口波特率 115200 ser serial.Serial(COM3, 115200, timeout1) def send_sweep_params(start_freq, stop_freq, step, dwell_ms): # 帧头 0xAA55后跟 4 个 32 位参数 frame struct.pack(HIIII, 0xAA55, int(start_freq), int(stop_freq), int(step), int(dwell_ms)) ser.write(frame) # 等待 FPGA 回传确认字节 0x06 ack ser.read(1) if ack b\x06: print(参数下发成功) else: print(下发失败检查连接) # 示例从 100MHz 扫到 200MHz步进 1MHz驻留 0.5ms send_sweep_params(100_000_000, 200_000_000, 1_000_000, 0)逻辑说明struct.pack 按小端格式打包帧头 0xAA55 用于 FPGA 状态机同步。start_freq 和 stop_freq 单位是 Hzstep 也是 Hzdwell_ms 是驻留毫秒数0 表示用 FPGA 内部默认值。FPGA 收到后回 0x06 表示参数已写入 ROM。注意串口波特率要和 FPGA 一致且每次下发前清空接收缓冲区。5.2 用扫频数据反推环路带宽如果你有频谱仪或者功率计可以把扫频输出接上去记录每个频点的幅度。理想情况下幅度应该平坦如果两端跌落说明 HMC835 的环路带宽不够或者 AD9914 的 DAC 滚降没补偿。我一般会扫 100 个点用 Python 画一条曲线看 3 dB 点在哪里。下面是一个简单的数据处理片段import numpy as np import matplotlib.pyplot as plt freqs np.linspace(100e6, 200e6, 100) amps np.random.normal(-10, 0.5, 100) # 模拟实测幅度 # 找 3dB 带宽 max_amp np.max(amps) idx_3db np.where(amps max_amp - 3)[0] if len(idx_3db) 0: print(f3dB 带宽约 {freqs[idx_3db[0]]/1e6:.1f} MHz) plt.plot(freqs/1e6, amps) plt.xlabel(Frequency (MHz)) plt.ylabel(Amplitude (dBm)) plt.grid() plt.show()逻辑说明amps 是实测幅度数组max_amp 是峰值idx_3db 找到第一个低于峰值 3 dB 的索引对应的频率就是带宽边缘。注意实测时要用峰值检波且扫频速度不能太快否则滤波器响应跟不上。5.3 一个我常犯的错误早些年调这套链路时我总想着把扫频步进设得很小比如 10 kHz觉得分辨率高。结果发现 HMC835 的锁定时间根本跟不上每个频点还没锁稳就切走了输出全是杂散。后来学乖了步进和驻留时间必须匹配环路带宽一般驻留时间至少是锁定时间的 3 倍。从那以后我每次改扫频参数都强制先跑一遍单频点锁定测试确认 LD 稳定后再开扫频。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑