
前阵子有个朋友问我手里有一块Alinx黑金开发板芯片是Xilinx Spartan-6想拿来驱动一片ADF4351射频信号源问我有没有现成的Verilog SPI驱动代码。这种组合其实很常见Spartan-6老当益壮逻辑资源做这种控制类活绰绰有余ADF4351又是一颗经典的宽带频率合成器35MHz到4.4GHz的覆盖范围做实验室信号源、扫频源、PLL实验都特别顺手。问题在于网上SPI例程多半是STM32的FPGA版本的要么残缺要么寄存器配置稀里糊涂真正能照着跑通的没几个。这篇文章就把我实际调通的方案完整拆开讲硬件怎么接、频率怎么算、寄存器怎么配、Verilog SPI驱动状态机怎么写、上板之后怎么确认锁定最后附上可直接用的完整代码。如果你手头也是黑金Spartan-6开发板或者任意一块FPGA板子只是想快速把ADF4351跑起来这篇文章可以直接照做。已经会SPI协议但没写过ADF4351配置的人重点看寄存器和频率计算部分。完全新手也别怕我会把电平、时序、锁定指示这些坑提前给你标出来。1. 项目全貌与方案选型思考1.1 ADF4351到底是什么能干多少事ADF4351是ADI推出的一颗集成VCO的锁相环频率合成器芯片。输出频率范围标称35MHz到4.4GHz这个覆盖范围是怎么来的呢芯片内部VCO本身工作在2.2GHz到4.4GHz后面接了一级可编程分频器支持1、2、4、8、16、32、64分频所以低频段是靠VCO分频得到的。也就是说你配置的时候要先把目标频率换算成VCO频率再决定分频比。它的关键优势是把PLL环路滤波器的大部分元件都集成进去了不对环路滤波器还是需要外接的但是环路带宽内的电荷泵、鉴相器、VCO这些都集成在内部外部只要给一个参考时钟配置好寄存器就能输出频率稳定、相位噪声还不错的射频信号。正因为集成度高它非常适合做需要快速迭代的射频信号源、扫频源、接收机本振。FPGA用它做什么最常见的是做扫频信号源FPGA控制ADF4351在不同频点之间切换配合功率检测模块实现频率响应测试。或者做跳频通信的实验平台。我这次的项目是做一个固定3GHz的本地振荡器配合混频器做下变频实验。1.2 为什么用FPGA而不是单片机这个问题值得先说清楚。驱动ADF4351本质上就是SPI写寄存器STM32、Arduino都能干为什么绕一圈用FPGA第一如果你手里已经有一块闲置的FPGA开发板就不需要额外买单片机了成本上是零。第二FPGA的GPIO时序可控性极强SPI时钟频率、数据建立时间、LE锁存时刻全部可以通过逻辑精确控制调试起来非常有底。第三也是最实际的一点很多射频项目最终要用FPGA做基带处理、数字调制、快速跳频控制那SPI控制PLL只是顺带的事情没必要再挂一颗单片机增加系统复杂度。Spartan-6虽然是老架构但做这类纯逻辑控制绰绰有余。黑金开发板上的XC6SLX16或者XC6SLX45逻辑单元、Block RAM、引脚资源都足够而且黑金这块板子布局规整GPIO引出来多外接ADF4351模块很舒服。唯一要留意的是Spartan-6得用ISE工具链新版本的Vivado不支持装个ISE 14.7就行了网上大把教程。1.3 整个系统的数据流与控制流从功能上看这个项目分成两个层面控制平面和射频平面。控制平面是FPGA通过SPI总线往ADF4351的寄存器里面写配置字射频平面是ADF4351根据寄存器配置产生对应频率的射频信号从RF_OUT引脚输出。SPI写入过程不复杂ADF4351内部有6个32位寄存器每次SPI传输就是一块完整的32位数据从MSB开始逐位移入最后用LE上升沿锁存到地址对应的寄存器里。6个寄存器全部写完之后芯片内部的PLL环路就会启动锁定VCO频率被锁到目标值。理解了这条链路之后整个项目的技术分解就清晰了硬件连接、频率计算、寄存器配置、Verilog SPI时序实现、锁定验证。下面我按这个顺序逐个展开。2. 硬件连接与信号完整性处理2.1 ADF4351模块引脚定义与接线表市面上常见的ADF4351模块是蓝色或者黑色小板子引脚一般就6到8个SCLK、DATA、LE、CE、VCC、GND有的还引出RF_OUT和RF_OUT_AUX以及REFIN参考输入。买模块记得确认板上有没有自带TCXO和环路滤波器通常带TCXO的模块上电就能用没有的就麻烦一点要从外部输入参考时钟。我手里的模块板载10MHz TCXO所以接线上很省事。和黑金Spartan-6开发板对插的线就5根我这里给一张实际使用的接线表信号名称ADF4351模块引脚黑金Spartan-6开发板GPIO说明SCLKSCLK任意GPIO建议选离SMA远一点的引脚SPI时钟由FPGA产生空闲为低SDATADATA任意GPIOSPI数据MSB firstLELE任意GPIO锁存使能32位传完后拉高CECEVCC 3.3V或GPIO拉高芯片使能直接接高电平即可GNDGNDGND必须共地VCCVCC5V或3.3V视模块而定多数模块有LDO5V供电即可这里要多说一句黑金Spartan-6开发板GPIO电平是3.3V LVCMOS而ADF4351芯片的VDD一般最高3.6V数字引脚的电平兼容3.3V所以FPGA直接驱动是没问题的。如果模块上没有电平转换也不要试图用5V去灌它FPGA这边的Bank电压设成3.3V就对了。2.2 参考时钟的重要性和供电细节ADF4351的鉴相器频率fPFD是由参考输入频率分频得到的所以参考时钟的质量直接影响输出信号相位噪声。模块自带TCXO当然省事但如果你的项目对相位噪声要求高可以用FPGA输出一个干净的低相噪时钟给REFIN或者外接高质量恒温晶振。供电方面有个隐蔽的坑ADF4351的电荷泵和VCO对电源纹波敏感供电电压不稳会导致VCO相位噪声恶化甚至失锁。模块上如果有LDO稳压外部供电5V问题不大。要是模块直接供电3.3V务必保证纹波小。我调试时遇到过输出信号毛刺大最后发现是一根面包板供电线虚接导致换杜邦线加粗之后立刻改善。射频输出端RF_OUT最好用SMA转BNC或者SMA转SMA线直接接频谱仪/示波器不要用杜邦线去量否则高频信号衰减、驻波反射会让你误判为“没输出”。没有频谱仪的话临时用示波器看低频分频输出也能验证但3GHz直接上示波器是看不出来的。2.3 引脚约束文件示例Spartan-6用ISE开发引脚约束写在UCF文件里不是Vivado的XDC文件。以我实际工程为例NET clk LOC T10 | IOSTANDARD LVCMOS33; NET rst_n LOC T11 | IOSTANDARD LVCMOS33; NET spi_sclk LOC T12 | IOSTANDARD LVCMOS33; NET spi_sdata LOC T13 | IOSTANDARD LVCMOS33; NET spi_le LOC T14 | IOSTANDARD LVCMOS33; NET led_lock LOC T15 | IOSTANDARD LVCMOS33;注意上面这些引脚名是我举例黑金不同的型号引脚编号差异很大一定以你自己的开发板原理图为准。我第一次用这个板子的时候偷懒没看原理图照抄网上的约束文件结果时钟引脚根本不对下载进去程序完全跑不了。老老实实打开原理图找到50MHz晶振对应的FPGA引脚一查一个准。3. ADF4351寄存器与频率计算3.1 六个寄存器概览别再把地址高低位搞反ADF4351有R0到R5一共6个寄存器每个寄存器32位。这里必须强调一个几乎所有新手都会搞错的地方寄存器地址不是低1位也不是低2位而是最低3位DB2:DB0。这3位的值就是寄存器编号R0对应000R1对应001一直到R5对应101。为什么是3位因为6个寄存器至少需要3位寻址空间。很多SPI外设是8位寄存器大家习惯性以为地址就是最低1位换成ADF4351就翻车。我见过有人把配置写错一整晚找不出原因最后发现地址位写反了0x000004送给了R0而不是R4。这个坑务必在项目一开始就排掉。32位数据中DB31到DB3是数据DB2到DB0是地址。发送顺序从DB31开始最后发送DB0。也就是说Verilog移位寄存器里第一个移出去的位正好是最高位DB31。3.2 频率计算的核心公式ADF4351的频率计算绕不开下面这些公式。先说结论输出频率RFout由VCO频率经输出分频器分频得到RFout fVCO / 输出分频比VCO频率由PLL的反馈分频N和鉴相频率fPFD决定fVCO fPFD × (INT FRAC / MOD)鉴相频率fPFD由参考频率REFIN经过R分频器、参考倍频器和二分频器得到fPFD REFIN × (1 REF_DOUBLER) / (R × (1 RDIV2))公式拆开解释一下。REF_DOUBLER是参考频率倍频器使能之后等价于参考频率乘2RDIV2是参考频率二分频器使能后参考频率除以2R是R分频器一个整数分频系数。三个功能组合起来用来把任意参考频率调整到合适的fPFD。INT是13位整数分频系数取值范围至少32最大8191FRAC是12位小数分子范围0到4095MOD是12位小数分母范围2到4095。MOD和FRAC配合实现小数分频。如果FRAC等于0就是整数边界频率。这里有个重要约束fVCO必须在2.2GHz到4.4GHz之间。如果目标频率低比如100MHz那必须在寄存器R4的12位分频器字段里配置输出分频比让VCO跑在3.2GHz再32分频到100MHz。如果目标频率高比如5GHz那就超出上限了不行。3.3 手把手算一遍3GHz配置我的项目目标是3GHz参考频率是模块自带的10MHz。现在完整走一遍计算流程。第一步确定fVCO和目标频率的关系。3GHz落在2.2到4.4GHz之间所以输出分频比选1fVCO直接等于3GHz。第二步算fPFD。REFIN10MHzREF_DOUBLER设0RDIV2设0R分频器设1那么fPFD 10MHz × (1 0) / (1 × (1 0)) 10MHz第三步算N。N fVCO / fPFD 3000MHz / 10MHz 300。这是一个整数说明走整数边界FRAC0MOD设一个最小值2就行。第四步把INT300转换成13位二进制。300的二进制是100101100前面补0补齐13位就是0000100101100。FRAC0填12位全0。MOD2填12位000000000010。这一组配置放到对应寄存器里我再结合ADF4351数据手册把每一路设置整理成下面这组参考值寄存器32位写入值关键内容R50x580005设置R分频、VCO使能、关参考倍频/二倍频地址101R40x00EC84RF输出使能、输出功率、输出分频比1地址100R30x000103锁定检测配置、电荷泵相关地址011R20x01C842PFD极性正、MUXOUT设为数字锁定指示地址010R10x008003MOD2地址001R00x009600INT300、FRAC0地址000这组值是我在10MHz参考、3GHz输出场景下整理出来的经验配置建议拿到板子上先用它跑通再逐步修改验证。如果你目标频率不同不要硬抄用上面的公式自己重算建议配合ADI官方的ADIsimPLL工具生成配置那个工具能帮你省掉大量手算出错的时间。手算适合理解原理工具适合实际工程。3.4 小数分频和非整数频率怎么算想输出3000.1MHz这种非整数频率整数分频做不到必须走小数路径。还是10MHz参考和fPFD10MHzN300.01。INT取300小数部分0.01还需要MOD配合。比如选MOD100那么FRAC 0.01 × 100 1。于是N 300 1/100 300.01输出就是3000.1MHz。如果选MOD1000FRAC 10。MOD越大频率分辨率越高但是MOD太大会影响杂散性能实际使用要在分辨率和杂散之间取舍。这里有个频率分辨率的概念最小频率步进等于fPFD / MOD。fPFD10MHz、MOD100时步进100kHz想把步进压到1kHzMOD就得设10000但MOD只有12位最大4095所以要么降低fPFD要么接受这个步进。这也解释了为什么很多系统会把fPFD设得比较低就是为了获得更小的频率步进。4. Verilog SPI驱动设计与完整代码4.1 ADF4351的SPI时序要求ADF4351的SPI时序和普通SPI Flash不太一样。它是32位的寄存器写入操作不读数据所以只需要三根线SCLK、SDATA、LE并没有严格意义上的CS片选。LE在这里的作用就相当于片选加锁存全部32位移入后拉高LE芯片在LE上升沿把数据锁存到对应寄存器。时序参数上有几个关键指标SCLK最高可以到20MHz但是为了稳妥我建议一般工程跑5MHz以内SDATA在SCLK上升沿被采样所以SDATA必须在上升沿之前稳定下来LE高电平保持时间至少要几纳秒实际做宽一点没坏处。SPI模式对应关系上可以理解成空闲时钟为低、上升沿采样数据也就是CPOL0、CPHA1。用Verilog实现时状态机设计要格外注意数据改变的时机。如果数据是SCLK上升沿同时改变的那接收端根本采不到稳定数据这是最典型的SPI时序错误。4.2 单次32位发送状态机模块先写一个通用的一次性32位SPI发送模块。这个模块只管发一条32位数据发完拉高LE输出done脉冲。顶层再循环调用它就能完成6个寄存器的配置。timescale 1ns / 1ps module adf4351_spi_master #( parameter CLK_DIV 10 // 系统时钟分频系数50MHz/105MHz SCLK )( input wire clk, // 系统时钟 50MHz input wire rst_n, // 低电平复位 input wire start, // 启动脉冲高有效 input wire [31:0] data_in, // 待发送的32位寄存器数据 output reg sclk, // SPI时钟空闲为低 output reg sdata, // SPI数据MSB first output reg le, // 锁存使能 output reg busy, // 忙碌指示 output reg done // 发送完成脉冲 ); localparam HALF_DIV CLK_DIV / 2; // 半周期计数值 localparam S_IDLE 3d0; localparam S_SHIFT 3d1; localparam S_LATCH 3d2; localparam S_DONE 3d3; reg [1:0] state; reg [31:0] shift_reg; reg [4:0] bit_cnt; reg [31:0] clk_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; sclk 1b0; sdata 1b0; le 1b0; busy 1b0; done 1b0; shift_reg 32d0; bit_cnt 5d0; clk_cnt 32d0; end else begin done 1b0; case (state) S_IDLE: begin if (start) begin busy 1b1; le 1b0; sclk 1b0; shift_reg data_in; bit_cnt 5d31; // 先发最高位 clk_cnt 32d0; state S_SHIFT; end end S_SHIFT: begin if (sclk 1b0) begin // SCLK为低期间更新数据保证上升沿到来前数据稳定 if (clk_cnt 32d0) begin sdata shift_reg[bit_cnt]; end if (clk_cnt HALF_DIV - 1) begin sclk 1b1; clk_cnt 32d0; end else begin clk_cnt clk_cnt 1; end end else begin // SCLK为高期间保持数据不变计时后拉低 if (clk_cnt HALF_DIV - 1) begin sclk 1b0; clk_cnt 32d0; if (bit_cnt 5d0) begin state S_LATCH; end else begin bit_cnt bit_cnt - 1; end end else begin clk_cnt clk_cnt 1; end end end S_LATCH: begin le 1b1; if (clk_cnt CLK_DIV - 1) begin le 1b0; clk_cnt 32d0; state S_DONE; end else begin clk_cnt clk_cnt 1; end end S_DONE: begin busy 1b0; done 1b1; sclk 1b0; sdata 1b0; state S_IDLE; end endcase end end endmodule这段代码的核心逻辑在S_SHIFT状态SCLK低电平时更新SDATA等半周期时间后拉高SCLK产生上升沿接收端在这个沿采样到稳定数据再等半周期拉低SCLK进入下一位。bit_cnt从31递减到0正好发完32位。这里有个细节值得展开讲一下。第一次写SPI状态机的人容易把“更新数据”和“拉高时钟”写在同一拍看着仿真波形好像没问题但实际用逻辑分析仪抓波形就会发现SDATA变化和SCLK上升沿几乎同时发生ADF4351根本采不到正确位。正确做法是让SDATA提前至少半个SCLK周期稳定上面代码里SDATA是在SCLK为低的第一个时钟沿更新然后过了HALF_DIV-1个系统周期才拉高SCLK这样数据建立时间非常充裕。实测5MHz SCLK下数据建立时间大概有100ns远超ADF4351最低要求。4.3 顶层模块按顺序写入6个寄存器ADF4351的寄存器写入顺序没有强制规定但是工程上推荐先写R5、R4、R3、R2、R1最后写R0。R0里面是INT和FRAC是最终决定输出频率的核心配置把它放最后可以有效避免配置过程中PLL中间状态反复跳动。顶层用状态机循环调用SPI发送模块每发一个寄存器等待done脉冲然后切换到下一个。全部发完输出cfg_done和locked指示。timescale 1ns / 1ps module adf4351_top ( input wire clk, // 系统时钟 50MHz input wire rst_n, // 复位 input wire start_config, // 开始配置高有效脉冲 output wire spi_sclk, // 接ADF4351 SCLK output wire spi_sdata, // 接ADF4351 DATA output wire spi_le, // 接ADF4351 LE output wire cfg_done, // 全部寄存器配置完成脉冲 output reg led_lock // 锁定指示LED可接MUXOUT判断也可GPIO点亮 ); localparam REG_COUNT 5d6; // 寄存器配置表按发送顺序排列 reg [31:0] reg_table [0:REG_COUNT-1]; reg [4:0] reg_idx; reg spi_start; reg spi_sel; wire spi_busy; wire spi_done; // 底层SPI发送模块 adf4351_spi_master #( .CLK_DIV (10) ) u_spi_master ( .clk (clk), .rst_n (rst_n), .start (spi_start), .data_in (reg_table[reg_idx]), .sclk (spi_sclk), .sdata (spi_sdata), .le (spi_le), .busy (spi_busy), .done (spi_done) ); // 示例配置10MHz参考输出3GHzfPFD10MHz整数边界 always (posedge clk or negedge rst_n) begin if (!rst_n) begin reg_table[0] 32h580005; // R5: R分频1, VCO使能地址101 reg_table[1] 32h00EC84; // R4: RF输出使能输出分频1地址100 reg_table[2] 32h000103; // R3: 锁定检测等配置地址011 reg_table[3] 32h01C842; // R2: PFD极性正MUXOUT选数字锁定地址010 reg_table[4] 32h008003; // R1: MOD2地址001 reg_table[5] 32h009600; // R0: INT300, FRAC0地址000 end end // 顶层配置状态机 localparam S_WAIT 3d0; localparam S_SEND 3d1; localparam S_NEXT 3d2; localparam S_DONE 3d3; reg [1:0] state; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_WAIT; reg_idx 5d0; spi_start 1b0; cfg_done 1b0; led_lock 1b0; end else begin cfg_done 1b0; case (state) S_WAIT: begin if (start_config) begin reg_idx 5d0; state S_SEND; end end S_SEND: begin spi_start 1b1; state S_NEXT; end S_NEXT: begin spi_start 1b0; if (spi_done) begin if (reg_idx REG_COUNT - 1) begin state S_DONE; cfg_done 1b1; led_lock 1b1; end else begin reg_idx reg_idx 1; state S_SEND; end end end S_DONE: begin state S_DONE; end endcase end end endmodule这里reg_table里的6个值就是我前面频率计算小节整理出来的3GHz配置。因为R0放在最后写入所以当最后一个寄存器写完后PLL会直接锁定到3GHzcfg_done同时给出完成脉冲。有一点需要特别说明reg_table的赋值放在复位块里这在综合的时候会被综合成ROM或者寄存器初始化没有问题。如果你不想把这些值写死在代码里也可以改成通过UART或者按键动态加载这在以后做扫频源的时候会用到。当前先用固定配置把链路跑通。4.4 为什么状态机要拆这么细很多FPGA新手喜欢把SPI发送写成for循环一次性移完这在仿真里看着没问题放到真实时序里就崩。原因在于for循环会被综合成组合逻辑链32位移位如果不用时钟节拍控制路径延迟会非常大最后要么时序不收敛要么行为完全不对。我这套状态机把每个bit的SCLK高低电平周期都显式用计数器控制每个操作都挂在时钟沿上时序清晰工具综合结果也稳定。代码看起来多实际上逻辑非常简单就是“低电平时准备数据、高电平时保持数据”的重复。以后你写其他SPI外设驱动比如ADS8688、LTC2380完全可以复用这套状态机只改数据宽度和寄存器数量就够了。5. 仿真验证与板级调试实录5.1 用ISim或Vivado仿真检查时序这个工程用ISE 14.7开发仿真可以用ISE自带的ISim也可以用ModelSim、Vivado的xsim。不过Spartan-6的工程一般就在ISE里仿真。下面是一个简单的testbench用来观察SPI时序。timescale 1ns / 1ps module tb_adf4351_top; reg clk; reg rst_n; reg start_config; wire spi_sclk; wire spi_sdata; wire spi_le; wire cfg_done; wire led_lock; adf4351_top uut ( .clk (clk), .rst_n (rst_n), .start_config (start_config), .spi_sclk (spi_sclk), .spi_sdata (spi_sdata), .spi_le (spi_le), .cfg_done (cfg_done), .led_lock (led_lock) ); always #10 clk ~clk; // 50MHz时钟 initial begin clk 0; rst_n 0; start_config 0; #100; rst_n 1; #100; start_config 1; #20; start_config 0; #1_000_000; $finish; end endmodule仿真重点看三个波形第一SCLK每个周期是不是高电平和低电平时长均匀第二SDATA是否在SCLK低电平区间变化高电平区间保持稳定第三LE是不是在32位数据全部移位结束后才拉高。如果LE在移位中途就拉高说明状态机跳转条件错了。我第一次跑这个仿真的时候发现LE在bit_cnt等于8的时候就提前置高了一查是状态机里“bit_cnt0”的判断位宽写成了4位5d0写成了4d0虽然数值相等但位宽不匹配导致仿真库里行为异常。这种低级错误在仿真波形上一眼就能看出来。5.2 上板实测用MUXOUT判断锁定配置完成之后怎么确认ADF4351真的锁定了最直接的方法是看频谱仪RF_OUT输出端应该有干净的3GHz单音信号。如果手头没有频谱仪用ADF4351的MUXOUT引脚也能判断。MUXOUT是一个多功能输出引脚可以在R2寄存器里配置成输出数字锁定指示。我的配置里R20x01C842其中MUXOUT字段设成了D_LOCKED模式。芯片锁定时MUXOUT输出高电平未锁定或失锁时输出低电平。把MUXOUT接到FPGA的GPIO或者直接接一个LED就能肉眼观察锁定状态。实际测试时LED稳定常亮代表锁定闪烁代表VCO在校准或者失锁。如果LED常灭重点检查三样东西供电有没有到位、SPI时序对不对、寄存器配置是否合法。其中SPI时序问题可以通过逻辑分析仪抓SPI总线数据来排查看波形上32位数据和预期的寄存器值是否完全一致。5.3 常见问题与排查技巧速查下面这张表是我在实际调试中和帮别人排查时总结的常见问题直接对照排查可以省下很多时间。现象可能原因排查思路MUXOUT一直为低LED不亮CE没拉高SPI没写进去寄存器地址错先量CE引脚电平再用逻辑分析仪抓SPI波形核对每个寄存器值和地址位MUXOUT亮了但输出频率不对频率计算错误输出分频器设置错误fPFD算错用频谱仪看实际频率反推N值重算INT/FRAC/MOD输出信号有杂散毛刺环路滤波器参数不合适供电纹波大PCB连线过长检查电源纹波缩短SCLK/SDATA走线环路带宽调整写R0后PLL反复失锁又锁定R0的INT设置接近VCO边界输出分频比不对确认fVCO落在2.2~4.4GHz范围内优先使用整数边界SCLK波形乱SDATA数据错时钟分频系数太小寄存器位宽不一致SCLK降频到1MHz再试检查移位寄存器位宽3GHz输出幅度非常低输出功率寄存器没配对SMA线损耗大R4输出功率字段设大一些检查SMA线缆这里特别提醒一个很容易踩的坑ADF4351的R4寄存器里RF输出使能位和辅助输出使能位是两个独立字段很多人只写了主输出使能结果RF_OUT_AUX没输出就以为芯片坏了。我遇到过模块上RF_OUT_AUX才是主输出接口的板子买模块时一定看清丝印。5.4 频率失锁问题深入排查如果你遇到“仿真全对、上板失锁”的情况大概率不是Verilog代码的问题而是寄存器配置和硬件状态的配合问题。我遇到过最典型的一个参考频率实际是12.8MHz但模块丝印写的是10MHz结果我按10MHz算的配置全部作废输出频率差了将近30%。对此我的建议是上电后先量模块TCXO的输出频率用频率计或者示波器测量确认参考频率的真实值。尤其从淘宝买的二手模块丝印和实际晶体频率不一定对得上这个坑比想象中常见。还有一个容易被忽略的细节ADF4351的VCO校准是在写入R0之后自动触发的。如果配置过程中R0被先写了VCO会立刻开始校准但此时其他寄存器还没配置完校准结果可能是错的。虽然PLL后面可能重新校准但容易出现锁定时间异常甚至失锁。所以哪怕只是做实验也建议保持“其他寄存器先写、R0最后写”的习惯这是很多芯片数据手册都会推荐的操作顺序。6. 写在最后这套方案还能怎么扩展这个项目跑通之后你可以轻松往下面几个方向扩展。第一把固定3GHz改成扫频源。顶层加一个频率控制接口把R0的INT和FRAC做成可动态修改的寄存器用FPGA内部的计数器和ROM存一张频率点表依次更新R0就能实现快速扫频。配合功率计或者ADC采集可以做出简易的标量网络分析仪。第二把SPI驱动模块抽象成通用SPI主机。我写的adf4351_spi_master模块稍加修改数据宽度从32位改成8位或者16位就能驱动ADC、DAC、SPI Flash等各种外设。以后项目里复用这个模块能省不少事。第三黑金Spartan-6上还有不少空闲逻辑资源你完全可以在这个工程基础上加上UART串口模块通过电脑上位机控制输出频率。这样一套可用的射频信号源控制板就完成了不用额外买单片机。我在实际做这个项目时最大的体会是ADF4351关键是先把频率计算和寄存器映射搞明白Verilog SPI驱动反而是最简单的部分。只要配置正确芯片几乎不挑SPI实现的细节。反过来如果寄存器配错再漂亮的代码也出不来信号。如果你手头的板子参考频率不同或者目标频率不是整数边界建议先用ADI的ADIsimPLL工具生成一组配置再对照着我的代码结构改进。工具生成的数值会告诉你每个寄存器的实际值然后你回推计算过程理解速度会快很多。最后再说一句调试射频电路一定要备一根好的SMA线和一个靠谱的频谱仪很多“芯片坏了”的结论到最后都是测量设备或者线缆的问题。