FPGA控制AD9914与HMC835实现宽带快速扫频信号源设计

发布时间:2026/10/21 12:53:26
FPGA控制AD9914与HMC835实现宽带快速扫频信号源设计
简介AD9914与HMC835分别是高性能DDS和锁相环芯片广泛用于射频信号源、频率合成与通信测试设备。该压缩包提供基于FPGA的Verilog驱动调试程序面向FPGA开发者和射频调试工程师完整覆盖AD9914的扫频、定频、调频等模式并包含HMC835的SPI配置逻辑寄存器值按注释修改即可适配不同输出频率全部源码开放代码风格简明清晰适合直接学习与二次开发。资源共77个文件压缩包仅1.31MB其中.v源代码实现SPI时序与模式控制.bsf/.bdf提供图形化模块.qpf/.qsf搭建Quartus工程框架.jic/.sof用于下载配置.rpt/.summary为编译报告README说明操作流程另有时钟分频、LVDS接口等辅助模块目录结构便于在FPGA环境中快速恢复工程并上板验证。内容预览中可见定频与扫频对应的配置模块方便对照两种工作模式。目前已有898人学习对于正在做DDS/PLL控制、SPI通信或射频信号源项目的读者能有效缩短驱动编写与调试周期。1. 这个资源包是干什么的一块FPGA同时驱DDS和PLL的完整工程在实验室调一套宽带接收机前端时手头最缺的就是一台能快速扫频的信号源。通用仪器太贵于是某开发者把这块基于FPGA控制AD9914与HMC835的工程打包成了资源一块FPGA同时通过SPI驱动DDS和PLL既能做1.4 GHz以内的直接扫频也能把HMC835当作可编程本振把扫频范围往C波段推。做射频测试工装、雷达信号模拟、频谱监测前端的工程师拿到这份资源最直接的收益是SPI时序已经调通、寄存器初始化顺序已经排好不用再从数据手册的字缝里抠配置序列。资源里围绕AD9914和HMC835展开的FPGA工程、寄存器配置与扫频逻辑正好补上“从点频输出到快速扫频”这一整条链路。2. 架构设计AD9914 HMC835 FPGA的板级与总线分工2.1 系统级联与信号流向这块板子的核心关系可以简化为一条主线FPGA作为SPI主机通过两个片选分别控制AD9914和HMC835。AD9914是一颗3.5 GSPS的DDS内部自带12位DAC输出范围从直流到1.4 GHz左右HMC835则是一颗集成VCO的小数N分频锁相环RF输出覆盖几十MHz到6.8 GHz量级。两者分工明确需要快速扫频时用AD9914需要稳定点频本振时用HMC835。两条信号链可以独立工作也可以级联。独立模式下AD9914直接输出扫频中频HMC835输出本振两者在外部混频器里合路得到变频后的宽带信号。级联模式下HMC835锁定后的输出可以作为AD9914的高质量系统时钟把DDS的参考从普通晶振提升到GHz级这样AD9914既能输出更高频率又能改善近载波相噪。工程里两种接法都预留了配置入口改动的地方主要在时钟树不在SPI层。2.2 频率规划与指标边界用这套方案之前先要把两颗芯片的频率边界划清楚否则后面改参数容易迷茫。参数AD9914HMC835输出频率最高约1.4 GHz3.5 GSPS约25 MHz–6.8 GHz频率控制方式32位频率调谐字FTW小数分频比寄存器配置扫频方式数字斜坡 / RAM / profile切换逐点改写分频比重锁定接口SPI三线或四线SPI典型用途快速扫频源、调制源宽带本振、点频源AD9914最值得利用的特性是内部数字斜坡调制器它能在不占用FPGA资源的情况下自动完成线性扫频。HMC835则适合做“慢速扫频”或“步进点频”每次写入新的分频比后片内VCO要重新校准和锁定所以切换速度远不如DDS。常见做法是让AD9914负责几十MHz到1 GHz的快速扫描HMC835负责把本振固定在某个频点做上变频两头各干各的。频率字计算也很直接AD9914的频率调谐字为round(f_out / f_sysclk × 2^32)例如3.5 GSPS系统时钟输出700 MHzFTW约等于0x33333333HMC835则是根据鉴相频率和VCO输出频率反推分频比整数部分和小数部分分别写入对应寄存器。2.3 SPI总线拓扑与片选策略两颗芯片挂在同一条SPI总线上FPGA做主片选分开。连接方式如下FPGA(MOSI/MISO/SCLK) ── AD9914(SDI/SDO/SCLK, CS0) └── HMC835(SDI/SDO/SCLK, CS1)AD9914和HMC835都支持标准SPI但细节上有差异。AD9914的串行端口兼容三线制可以把SDO省掉HMC835的寄存器帧格式与AD9914不同地址和数据位宽都不一样。为了保证复用性工程里建议统一走四线SPIMOSI和MISO分开SCLK空闲低电平数据在上升沿采样也就是SPI模式0。SCLK频率控制在1 MHz左右比较稳AD9914虽然手册上标称可以更快但实际布线稍长就容易翻车1 MHz对两芯片都安全。上电顺序也是一个容易忽视的点。常见做法是先配置HMC835让它在后台完成VCO校准再去初始化AD9914。如果反过来AD9914先输出而此时HMC835还没锁定混频链路上会出现一段无本振的乱信号容易误导调试判断。3. FPGA控制逻辑拆解SPI主机的状态机与寄存器写入3.1 SPI主控制器从移位寄存器到状态机AD9914和HMC835虽然寄存器格式不同但底层都只需要一个能完成单字节传输的SPI主机。这里给一个可复用的Verilog实现模式0单字节传输带done脉冲方便上层模块按字节串起多字节配置序列。// spi_master_byte.v : 单字节SPI主机模式0CPOL0, CPHA0 // SCLK 系统时钟 / (2 * CLK_DIV) module spi_master_byte #( parameter integer CLK_DIV 50 // 100MHz系统时钟 - 1MHz SCLK )( input wire clk, input wire rst_n, input wire start, // 单周期高脉冲启动一次传输 input wire [7:0] tx_data, // 要发送的字节 output reg [7:0] rx_data, // 从机返回的字节 output wire cs_n, // 片选低有效 output reg sclk, // 串行时钟空闲为低 output reg mosi, // 主机输出 input wire miso, // 主机输入 output reg done // 传输完成单周期高脉冲 ); localparam IDLE 2d0, HALF_LOW 2d1, HALF_HIGH 2d2; reg [1:0] state; reg [7:0] tx_shf, rx_shf; reg [15:0] cnt; reg [2:0] bit_cnt; assign cs_n (state IDLE); always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sclk 1b0; mosi 1b0; done 1b0; rx_data 8h00; end else begin case (state) IDLE: begin sclk 1b0; done 1b0; if (start) begin tx_shf tx_data; rx_shf 8h00; cnt 16d0; bit_cnt 3d0; state HALF_LOW; end end HALF_LOW: begin sclk 1b0; mosi tx_shf[7]; // 先在低电平期准备好数据 if (cnt CLK_DIV - 1) begin cnt 16d0; sclk 1b1; // 上升沿从机采样 rx_shf {rx_shf[6:0], miso}; state HALF_HIGH; end else begin cnt cnt 16d1; end end HALF_HIGH: begin if (cnt CLK_DIV - 1) begin cnt 16d0; sclk 1b0; tx_shf {tx_shf[6:0], 1b0}; if (bit_cnt 3d7) begin state IDLE; done 1b1; rx_data rx_shf; end else begin bit_cnt bit_cnt 3d1; state HALF_LOW; end end else begin cnt cnt 16d1; end end endcase end end endmodule这个状态机把一次传输拆成“低半周期准备数据、高半周期让从机采样”的节奏。CLK_DIV参数算的是SCLK频率系统时钟100 MHz、CLK_DIV50时SCLK正好是1 MHz。cs_n在IDLE状态为高一旦进入HALF_LOW就自动拉低传输结束回到IDLE再释放。上层模块只需要在start上打一个脉冲等待done脉冲后再发起下一字节。实际工程里我会再包一层“多字节写”模块专门处理AD9914那种4字节的FTW写入。每发完一个字节done信号触发计数器加一按顺序把下一个字节送到tx_data最后一字节发完后整体结束。这样做的好处是上层只需要关心“从哪个地址开始、数据是什么”不用管SCLK的每个边沿细节。3.2 AD9914配置单频输出的关键寄存器AD9914的寄存器默认写入后不会立刻生效数据先落在缓冲寄存器里必须等IO_UPDATE引脚来一个脉冲才会整体进入活动寄存器。这个机制的好处是多字节配置可以“先写完、一次生效”坏处是如果中间少写一个寄存器IO_UPDATE一打输出就会按一个半成品配置工作调试时经常因为这个出现“看起来寄存器没错、输出却不对”的情况。单频输出配置流程通常是这样的以3.5 GSPS系统时钟输出固定频率为例// AD9914寄存器地址按手册映射填写工程头文件里有同名宏 #define AD9914_CFR1 0x00 #define AD9914_CFR2 0x01 #define AD9914_FTW0 0x04 void ad9914_set_frequency(uint32_t ftw) { // CFR1: 关闭RAM和数字斜坡使用FTW0作为频率字 spi_write_byte(AD9914_CFR1, 0x00); // CFR2: 关闭自动IO_UPDATE同步SYSCLK直接由外部提供 spi_write_byte(AD9914_CFR2, 0x00); // FTW0是4字节寄存器高位在前 spi_write_byte(AD9914_FTW0 0, (ftw 24) 0xFF); spi_write_byte(AD9914_FTW0 1, (ftw 16) 0xFF); spi_write_byte(AD9914_FTW0 2, (ftw 8) 0xFF); spi_write_byte(AD9914_FTW0 3, ftw 0xFF); io_update_pulse(); // 拉高IO_UPDATE至少一个周期 }这里spi_write_byte就是前面SPI主机模块的C模型封装。FTW0地址偏移加0到3字节序是高位在前也就是先发MSB。io_update_pulse在FPGA里实现为一个宽约一个系统时钟周期的脉冲接到AD9914的IO_UPDATE引脚。配置完后如果输出频率不对先回读FTW寄存器确认写入值没有被截断。AD9914的FTW是32位SPI写4字节时任何一字节丢失或错位输出频率都会差得离谱。回读的方法不能省这是排查这类问题最快的路径。3.3 HMC835配置锁定环路与VCO校准HMC835的寄存器帧格式与AD9914不同调用层通常封装成“写一个地址、跟两字节数据”的函数。它的配置顺序比AD9914更敏感参考分频、鉴相频率、电荷泵电流、VCO校准这些环节互相耦合跳着写很容易出现“寄存器看着对频率完全不对”的现象。// HMC835寄存器地址以手册为准工程里用宏隔离具体数值 void hmc835_write(uint8_t addr, uint16_t data); void hmc835_init(void) { // 1. 参考分频把外部10MHz参考分到目标鉴相频率 hmc835_write(HMC835_REG_REF_DIV, 10); // 2. 环路控制小数模式、电荷泵电流档位 hmc835_write(HMC835_REG_PLL_CTRL, pll_cfg_value); // 3. 频率字按目标频率和鉴相频率计算分频比 hmc835_write(HMC835_REG_FREQ_WORD, freq_frac_value); // 4. 触发VCO自动校准 hmc835_write(HMC835_REG_VCO_CAL, cal_trigger_value); delay_ms(10); // 5. 等待锁定而不是盲目等待 while (!hmc835_lock_detect()) { delay_ms(1); } }常见做法是把HMC835的初始化拆成“配置寄存器”和“触发校准”两步。第一次配置完成后片内VCO需要根据目标频率做一次自动校准校准期间RF输出频率是不准的只有校准结束且环路锁定后输出才落到目标频点。如果跳过校准直接查频率会看到输出停留在上一次或中间状态这也是很多人觉得PLL是“黑匣子”的原因。HMC835还应该在正式使用前做一次寄存器回读特别是VCO校准状态位和锁定状态位。工程里可以加一个调试接口用SPI读回这两个标志位烧到上位机或LED上省得每次都要拿频谱仪确认。4. 扫频模式实现数字斜坡、RAM与profile切换三选一4.1 三种扫频方案对比AD9914做扫频有三条路数字斜坡调制、RAM模式、profile切换。三种方案各有适用场景选错了会要么速度上不去要么FPGA被拖得很累。方案扫频速度频率精度FPGA负载适用场景数字斜坡DRM最快内部自动步进32位FTW极低线性扫频、锯齿波RAM模式受RAM深度限制32位FTW低任意频率曲线profile切换取决于切换时序32位FTW中离散频点跳变数字斜坡适合绝大多数“起点到终点线性扫”的场景一次配置后FPGA基本不用管扫频引擎在芯片内部自己走。RAM模式适合非线性曲线比如对数扫频、带预畸变的频率曲线把这些FTW提前算好写进RAM然后按地址循环播放。profile切换适合跳频比如雷达的频点序列每个profile装一个固定频率用外部引脚或SPI快速选择。4.2 数字斜坡模式配置流程数字斜坡的关键参数有四个上限FTW、下限FTW、步进FTW、步进速率。上限和下限决定扫频边界步进决定每次跳多少速率决定多久跳一次。配置完这些后再把CFR1里的数字斜坡使能位打开打一个IO_UPDATE脉冲扫频就自动开始了。// 从f_start扫到f_stopN步完成 uint32_t ftw_start freq_to_ftw(f_start); uint32_t ftw_stop freq_to_ftw(f_stop); uint32_t step_ftw (ftw_stop - ftw_start) / N; // 按FTW算步长 // 使能数字斜坡调制方向由边界自动决定 spi_write_byte(AD9914_CFR1, DRM_EN); // 写入扫频边界 spi_write_byte(AD9914_RAMP_LIMIT_HIGH 0, (ftw_stop 24) 0xFF); spi_write_byte(AD9914_RAMP_LIMIT_HIGH 1, (ftw_stop 16) 0xFF); spi_write_byte(AD9914_RAMP_LIMIT_HIGH 2, (ftw_stop 8) 0xFF); spi_write_byte(AD9914_RAMP_LIMIT_HIGH 3, ftw_stop 0xFF); // 下限类似不再重复 spi_write_byte(AD9914_RAMP_LIMIT_LOW 0, (ftw_start 24) 0xFF); // ... spi_write_byte(AD9914_RAMP_STEP 0, (step_ftw 24) 0xFF); // ... spi_write_byte(AD9914_RAMP_RATE, rate_value); // 每rate个SYSCLK步进一次 io_update_pulse(); // 一次生效扫频启动这里的freq_to_ftw就是前面的公式先算32位FTW再按字节拆开写入。step_ftw要注意取整问题如果(ftw_stop - ftw_start)不能被N整除最后一步会落在边界之外实际扫频终点和设定值会差一两个步长。常见做法是让N取2的幂或者最后一步直接把终点写成ftw_stop。RAMP_RATE的单位是系统时钟周期rate越大扫频越慢。这个值不能太小AD9914内部对步进间隔有时序要求太小会导致斜坡异常或步进丢失。具体最小值以数据手册为准我的经验是rate至少取8以上留出余量。4.3 扫频启动与结束判断数字斜坡启动后FPGA不需要逐点参与但要有一个机制判断“这次扫频是否结束”以及“要不要循环扫”。AD9914的斜坡在到达边界后默认停在终点也可以配置成方向翻转变成三角波扫频。判结束时可以读芯片内部的状态寄存器但SPI读取会占用总线时间另一个更简单的做法是在FPGA里自己计时斜坡总时长等于步进数乘以rate再除以系统时钟时间一到就认为扫频结束。// 每10ms触发一次正向扫频用IO_UPDATE启动 reg [31:0] timer; reg io_update_pulse; always (posedge clk or negedge rst_n) begin if (!rst_n) begin timer 32d0; io_update_pulse 1b0; end else if (timer TIMER_10MS) begin timer 32d0; io_update_pulse 1b1; // 启动一次斜坡 end else begin io_update_pulse 1b0; timer timer 32d1; end end这个代码实现的是周期性的扫频触发。注意io_update_pulse只在timer到期的那个周期拉高其它时间保持低电平避免反复触发斜坡配置复位。如果要做往返扫频需要读回“到达边界”的标志再反向设置方向或者直接配置CFR1里的自动翻转位让芯片自己往返。5. 避坑手记SPI时序、锁定失败与扫频非线性问题排查5.1 HMC835写完频率字后输出还是上一次的频率现象写入HMC835频率寄存器时没有任何报错SPI波形也对但频谱仪上输出频率纹丝不动。原因HMC835写完频率字后必须触发片内VCO自动校准校准不启动新频率字就不会参与环路计算另一个常见原因是写入顺序错乱频率字被后续的寄存器配置覆盖掉。解决把初始化固定为“参考分频 → 环路控制 → 频率字 → 校准触发”的顺序校准触发放最后一步。校准完成后轮询锁定状态寄存器锁定后再做下一步操作。从那以后我每次改完HMC835配置都强制走一遍回读确认写入值和读回值一致避免“写了个寂寞”。5.2 AD9914单频正常扫频时边界出现毛刺现象单频输出正常数字斜坡扫频时中间平顺但在起点和终点有突跳或短促的尖峰。原因斜坡边界FTW和初始FTW没有对齐扫频到边界后内部频率字瞬间跳变还有可能是IO_UPDATE与AD9914的SYNC_CLK没有同步斜坡参数部分生效、部分还在缓冲寄存器里造成一瞬间的错乱状态。解决配置斜坡参数时确保所有相关寄存器在一次IO_UPDATE窗口内写完不要写一半打脉冲。同步问题上打开CFR2里的IO_UPDATE同步位让IO_UPDATE的采样沿和SYNC_CLK对齐毛刺会明显改善。5.3 同一套配置换一块板子就锁定失败现象原先调好的工程重新打样后同样代码写进去HMC835要么锁不住要么锁定指示灯闪个不停。原因HMC835的环路滤波器性能与外部元件、板级寄生电容相关。新板子的地平面、走线分布电容变了等效环路带宽跟着变原来的电荷泵电流档位就不合适了。解决先降低鉴相频率试试把锁定难度降下来再调整电荷泵电流档位找到一个能让环路稳定锁定的组合。用示波器看环路滤波器控制电压锁定后这个电压应该稳定不动如果看到缓慢漂移说明环路带宽过低或电荷泵电流不对。5.4 扫频步进频率与设定不符现象设置100个步进从100 MHz扫到600 MHz实际数出来只有90多步或者每步间隔不均匀。原因FTW是按2^32比例量化的实际频率步进等于FTW步进乘以系统时钟再除以2^32。计算时如果把FTW步进取整每个步进的实际频率间隔和理想值会出现偏差多步累积后终点偏离目标。解决不要直接按“步数”配步进先按频率间隔算FTW间隔再让步数等于边界差除以FTW差。要求精确扫频点或者均匀步进时改用RAM模式把每个频点的FTW提前算好存进去完全绕开斜坡步进的量化问题。提示AD9914的FTW计算、HMC835的寄存器帧格式不同批次数据手册表述有差异工程里所有寄存器地址和bit位都建议用宏封装方便拿到手册后一次替换到位。6. 二次开发把固定扫频源改造成可程控信号源6.1 增加幅度控制AD9914除了频率字还有幅度字。幅度控制可以解决“扫频时需要同步调整输出功率”的问题比如做接收机增益平坦度测试时希望每个频点输出功率一致。把幅度字和频率字一起写在同一个IO_UPDATE窗口里更新频率的同时更新幅度实现“频率-幅度”联动。HMC835的输出功率调整能力有限常见做法是外接数控衰减器用FPGA的另一组SPI或GPIO控制。6.2 设计串口控制协议改成可程控信号源最直接的方式是加一个UART命令接口。用“帧头 命令字 参数 校验”的结构解析层用状态机实现避免阻塞式的字符串解析。命令字至少覆盖两类设置中心频率、设置扫频范围。帧结构建议做成定长方便FPGA里做状态机也方便上位机组帧。字节偏移内容说明00xAA帧头1命令字0x01设频点0x02设扫频2–5参数频率值或扫频上下限6校验和前6字节异或上层收到完整一帧后校验通过才执行配置。我之前在被串口调试助手反复打断的时候踩过坑数据处理到一半被新的字节覆盖后来强制规定“帧不完整不执行”这个习惯帮我稳定了后续所有调试流程。6.3 验证方法改动后先在频谱仪上用Max Hold看扫频包络确认边界没有尖峰再配合检波器和ADC回采检查扫频曲线线性度最后把所有配置过的寄存器回读一遍确认写入值与回读值一致。这套流程排掉了大半“写入但不生效”的暗亏从那以后我每次改完配置都强制走一遍回读比对再继续往下调。希望帮到你。本文还有配套的精品资源点击获取