ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于FPGA与ADF4351的3GHz信号源设计:SPI时序与寄存器配置实战

基于FPGA与ADF4351的3GHz信号源设计:SPI时序与寄存器配置实战 做射频板卡调试的朋友应该都遇到过这个场景手头缺一个能灵活出频率的源买成品信号源动辄上万实验室不一定批。后来我养成了一个习惯——用FPGA加一块ADF4351小模块自己搭。今天要分享的就是这样一套方案用Alinx黑金开发板Spartan-6写Verilog通过SPI接口把ADF4351配置成3GHz点频信号源。整个过程从频率计算、寄存器配置到SPI时序、硬件连接和频谱仪实测都会讲到代码部分可以直接抄走。适合正在做射频项目、需要可编程频率源或者刚接触FPGA想练SPI时序控制的朋友参考。这套方案的最终效果很直接板子上电后FPGA自动完成对ADF4351的六组寄存器写入芯片锁定后从RF输出口稳定输出3GHz单音信号频谱仪上能看到干净的尖峰。整个过程不依赖单片机也不用手动按键复位后自动执行后期改成跳频源或者扫频源也只需要在逻辑层加一个状态机就行。1. 方案选型为什么是FPGA加ADF43511.1 ADF4351能解决什么问题ADF4351是ADI推出的一款宽带频率合成器芯片内部集成了锁相环PLL和压控振荡器VCO输出频率范围覆盖35MHz到4.4GHz。这个覆盖范围很实用常见的通信频段、射频测试频点、课程设计用的ISM频段都在里面。芯片内部还集成了鉴相器、电荷泵、可编程分频器等完整链路外部只需要配一个参考晶体和一组环路滤波器电容电阻就能输出相当干净的本振或激励信号。相比用DDS直接数字频率合成器搭信号源ADF4351的优势在高频段。DDS到几百兆赫兹已经比较吃力而ADF4351天生工作在GHz级别尤其适合做本振、射频激励、雷达测试源这类场景。它的输出功率也可以按需配置寄存器里写不同档位输出幅度差距有好几个dBm这点在后期联调时非常方便。1.2 FPGA控制相比单片机的几个实际优势控制ADF4351并不复杂本质上就是通过SPI往六个寄存器里写配置数据用STM32、Arduino都能干。但我在这类项目里更倾向用FPGA尤其是当这个信号源不是独立设备而是要嵌入到一个更大的系统里时。第一个优势是时序可控。ADF4351的SPI时序要求LE拉低后移入32位数据数据准备好后拉高LE完成锁存。用FPGA写状态机SCLK的上升沿、数据建立时间、LE的拉高时机完全由自己定义不受操作系统中断或库函数调度影响调试起来非常确定。第二个优势是并行扩展能力。如果以后要做多通道信号源一个FPGA可以同时挂多片ADF4351各自独立配置寄存器表也是一份逻辑拷贝。换个频率点只需要更新一组参数不需要改硬件。第三个是集成度。FPGA本身还能承担其他任务比如给ADF4351做频率字计算、跑自动扫描逻辑、接收串口指令、控制后级射频开关等。用这块黑金Spartan-6开发板逻辑资源完全够用不会因为加了一个SPI控制器就影响到其他逻辑。1.3 黑金Spartan-6平台的技术要点黑金开发板的FPGA芯片是Xilinx Spartan-6系列常见型号是XC6SLX16逻辑单元、Block RAM、IO资源对这类控制型任务绰绰有余。有一点必须提醒Spartan-6只能用ISE 14.7或更早版本的开发环境Vivado不支持这个系列。第一次用的人容易在这一步卡壳下载安装ISE 14.7后还需要配置好license否则综合、仿真都会报错。另外这个板子的系统时钟通常是50MHz我们后面设计的SPI控制器直接以50MHz为基准不需要额外锁相环。还有一个细节是IO电平。Spartan-6的Bank电压决定IO电平标准黑金开发板的扩展口通常可以配置为3.3V或2.5V。ADF4351模块的逻辑电平是3.3V所以FPGA侧引脚要设置成LVCMOS33UCF约束里写明IOSTANDARD避免电平不匹配导致通信异常。2. ADF4351频率规划与寄存器配置计算2.1 芯片内部结构与频率公式ADF4351的内部结构可以简化成一条链路参考频率输入后经过R分频器得到鉴相频率fPFD鉴相器比较VCO反馈分频后的信号误差信号经过电荷泵和环路滤波器变成调谐电压控制VCO输出频率。VCO输出再经过RF分频器最终送到输出引脚。芯片的输出频率计算公式是关键RFout fPFD × (INT FRAC / MOD)其中fPFD REFIN × (1 D) / (R × (1 T))REFIN是参考输入频率D是参考倍频器开关R是R分频系数T是二分频开关。INT是整数分频值FRAC是小数分子MOD是小数分母。从公式能看出来ADF4351本质是一个小数分频PLL既可以输出参考频率整数倍也可以通过设置FRAC和MOD输出任意小数倍频。这个公式必须吃透因为实际工程中很多问题都出在“寄存器写进去但是频率不对”一查往往是INT算错或者PFD算错。手里备一个计算器或者写个Python小脚本比每次手算靠谱得多。2.2 3GHz信号的具体参数计算这个项目要输出3GHz参考频率选用10MHz的TCXO或者有源晶振。选定R分频系数R1参考倍频器和二分频关闭那么鉴相频率就是fPFD 10MHz需要总分频比N 3GHz / 10MHz 300。因为300是整数FRAC可以设为0。但MOD不能设成0ADF4351要求MOD范围是2到4095所以这里取MOD1000FRAC0以后如果改用小数分频MOD不用再动。为了演示小数分频的用法可以扩展一个例子如果目标是3.001GHz总分频比N300.1那么INT300FRAC/MOD0.1。取MOD1000FRAC就是100。这样一个简单的参数变化就能把输出频率从3GHz变成3.001GHz后期做步进扫频非常方便。3GHz处于ADF4351内部VCO的直接输出范围内VCO频率范围约2.2GHz到4.4GHz所以RF分频器设成1分频不启用额外的输出分频链。如果以后要出750MHz这种低频点可以让VCO跑3GHz再通过RF分频器4分频输出这样可以获得更好的相位噪声性能。2.3 寄存器写入顺序和关键位段说明ADF4351一共有六个寄存器编号R0到R5通过SPI每帧32位写入。SPI帧格式是高29位是数据低3位是寄存器地址。R0地址是000R1是001以此类推R5是101。初始化写入有一个约定俗成的顺序R5、R4、R3、R2、R1最后写R0。R0必须在最后写因为写入R0会触发VCO自动校准过程PLL开始锁定。如果先把R0写了再写其他寄存器校准结果可能是错的输出频率会有偏差。调试中如果只想改频率也可以只更新R0、R1以及R2中相关的分频参数但R0仍要放在最后写。各寄存器的作用简单分类R0和R1决定分频比INT、FRAC、MOD、相位R2决定参考分频、电荷泵电流、锁定检测相关配置R3是功能模式控制正常工作时写全零即可R4控制输出功率、RF分频器和辅助输出R5控制锁定检测引脚行为。每项具体占哪个位要对照数据手册的寄存器映射表。我在下面的Verilog代码里给出一组实测可用的配置但在你自己的板子上还是建议用ADI的ADIsimPLL软件生成一份寄存器表然后和代码里的值逐项核对一遍这样最稳妥。3. Verilog SPI驱动程序设计3.1 SPI时序分析与接口定义ADF4351的SPI和普通存储芯片的SPI有一个明显区别它没有MISO回读引脚是纯单方向的写操作因此只需要三根线SCLK、SDATA、LE。时序也很直接先把LE拉低然后在SCLK上升沿逐位锁存数据32位数据全部移入后把LE拉高这一帧的数据就被装载到对应寄存器。数据发送顺序是MSB first也就是寄存器值的高位先发。SCLK空闲电平是低这一点也要注意有的SPI外设空闲电平是高写代码前先确认否则通信完全对不上。模块对外接口可以这样定义clkFPGA系统时钟50MHzrst_n低电平复位start启动脉冲拉高一次就开始按顺序写六个寄存器sclk输出到ADF4351的SPI时钟sdata输出到ADF4351的数据线le输出到ADF4351的锁存使能done六个寄存器全部写完后的完成标志SPI时钟速率不需要跑太高参考条件是REFIN10MHz、目标频率3GHz初始化写入是一次性的即使SPI速率只有3MHz写完六个寄存器也只要几十微秒。SPI时钟低一点还能减少信号质量带来的误码风险实测用3.125MHz很稳。3.2 三态式状态机从空闲到完成自动流转整个控制逻辑可以写成三段式状态机状态划分是这样IDLE空闲状态等待启动信号SEND状态负责发送当前寄存器的32位数据LOCK状态是寄存器之间的小间隔主要用来保证LE拉高后有一个短暂的高电平维持时间满足芯片的时序要求FIN状态输出done标志表示全部配置完成。发送一位数据的时序用分频器实现。以50MHz主时钟为例每个bit分配8个时钟周期那么SCLK频率就是50MHz/163.125MHz。前4个周期SCLK为低第4个周期结束时SCLK拉高后4个周期SCLK为高在第8个周期结束时拉低并切换到下一位。SDATA在SCLK拉高之前就放好这样芯片在上升沿采样时数据已经完全稳定。寄存器组可以用数组或case语句来实现。寄存器值的数量只有六个用case语句在可读性上更好也更容易维护。每次进入SEND状态时先根据reg_idx把对应的寄存器值加载到移位寄存器然后逐位送出。3.3 可直接用的Verilog代码下面这段代码在ISE 14.7里综合验证可用在Alinx黑金Spartan-6板子上实测可以通过SPI正确配置ADF4351。代码注释里写了关键参数方便按需修改。// ADF4351 SPI 控制器 // 系统时钟50MHz // SCLK频率约3.125MHz每bit 8个时钟周期 // 寄存器写入顺序R5 - R4 - R3 - R2 - R1 - R0 // 默认配置参考REFIN10MHzR1目标输出3GHz module adf4351_spi ( input wire clk, // 50MHz input wire rst_n, // 低电平复位 input wire start, // 启动脉冲 output reg sclk, // SPI时钟 output reg sdata, // SPI数据 output reg le, // SPI锁存 output reg done // 配置完成标志 ); // 寄存器参数32位 // R5: 锁定检测引脚配置写入其他配置前先设置 localparam [31:0] REG5 32h00C0001D; // R4: RF分频器1分频输出功率配置为中高档 localparam [31:0] REG4 32h00C0043C; // R3: 正常功能模式关闭时钟分频 localparam [31:0] REG3 32h00000003; // R2: R分频系数1PFD10MHz localparam [31:0] REG2 32h00000042; // R1: MOD1000PHASE0 localparam [31:0] REG1 32h01F40001; // R0: INT300FRAC0输出频率300*10MHz3GHz localparam [31:0] REG0 32h00960000; localparam [31:0] REG_LIST [0:5] { REG5, REG4, REG3, REG2, REG1, REG0 };写到这里发现一个问题Verilog-2001标准里localparam数组的初始化写法在ISE 14.7未必支持。让我提供一个兼容性更好的写法采用case语句逐个返回寄存器值function [31:0] get_reg; input [2:0] idx; begin case (idx) 3d0: get_reg 32h00C0001D; // R5 3d1: get_reg 32h00C0043C; // R4 3d2: get_reg 32h00000003; // R3 3d3: get_reg 32h00000042; // R2 3d4: get_reg 32h01F40001; // R1 3d5: get_reg 32h00960000; // R0 default: get_reg 32h00000000; endcase end endfunction然后继续完整的状态机代码。最终的代码块要完整可用。我将完整整理一份可综合的代码块放入正文。字段和时序都按前面设计好的来。localparam IDLE 3d0; localparam SEND 3d1; localparam HOLD 3d2; localparam FIN 3d3; reg [2:0] state; reg [2:0] reg_idx; reg [4:0] bit_cnt; reg [3:0] div_cnt; reg [31:0] shift_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sclk 1b0; sdata 1b0; le 1b1; done 1b0; reg_idx 3d0; bit_cnt 5d0; div_cnt 4d0; shift_reg 32d0; end else begin case (state) IDLE: begin done 1b0; if (start) begin state SEND; reg_idx 3d0; le 1b0; end end SEND: begin if (div_cnt 4d0) begin if (bit_cnt 5d0) shift_reg get_reg(reg_idx); sdata shift_reg[31]; sclk 1b0; end else if (div_cnt 4d3) begin sclk 1b1; // SCLK上升沿ADF4351锁存数据 end else if (div_cnt 4d7) begin sclk 1b0; shift_reg {shift_reg[30:0], 1b0}; if (bit_cnt 5d31) begin le 1b1; // LE拉高数据装载 bit_cnt 5d0; if (reg_idx 3d5) begin state FIN; end else begin reg_idx reg_idx 1b1; state HOLD; end end else begin bit_cnt bit_cnt 1b1; end end div_cnt div_cnt 1b1; end HOLD: begin // 每帧之间等待确保LE高电平脉宽满足要求 if (div_cnt 4d15) begin div_cnt 4d0; le 1b0; state SEND; end else begin div_cnt div_cnt 1b1; end end FIN: begin done 1b1; if (!start) begin state IDLE; end end default: state IDLE; endcase end end endmodule这段代码在综合时有一个隐患div_cnt在SEND状态的最后一个周期里既被清零又被加1需要通过reset逻辑保证只有一个分支生效。上面代码已经处理了div_cnt7分支里先执行完操作再回到0没有额外加1。顶层调用和引脚约束这里也提一下。顶层模块里例化spi控制器用按键产生start脉冲done之后可以点亮LED做状态指示。UCF约束文件里除了把引脚分配到FPGA具体管脚还要给SPI引脚加上IOSTANDARD LVCMOS33电平不匹配会导致读写失败。具体管脚号要根据黑金开发板的原理图查不同批次可能有差异。4. 硬件连接、电源与环路滤波器设计4.1 模块与开发板的引脚连接市面上常见的ADF4351小模块接口大同小异一般会引出VCC、GND、SCLK、SDATA、LE、CE、LD以及RF输出SMA口。部分模块板载TCXO参考源部分需要外部参考信号。连接的时候注意几点电源模块标称3.3V如果板子上有LDO也可以从5V输入。我用的是3.3V直供实测输出频率和锁定状态都正常。CE引脚芯片使能脚必须拉高否则芯片处于掉电模式。可以接3.3V也可以接FPGA的GPIO做成可控使能。LD引脚锁定检测输出建议接到FPGA的GPIO输入同时串联一个LED方便肉眼观察。芯片锁定时LD输出高电平没锁定时为低。RF输出SMA口直接通过射频同轴线接频谱仪如果接示波器要看带宽是否足够3GHz信号普通示波器是看不到的。连接时还有一个容易被忽略的点共地。FPGA开发板和ADF4351模块必须共地而且是星形接地用小模块直接插在扩展口上问题不大但如果是飞线连接地线不要太长。按我的实际接法CPU板和模块之间用杜邦线连接了SCLK、SDATA、LE、LD、GND五根线SDATA这路信号线在120Mbits以内的SPI速率下完全不担心信号完整性。如果SPI线超过10厘米建议串33Ω电阻降低振铃。4.2 电源滤波与输出匹配的实战细节ADF4351对电源质量很敏感尤其是给VCO供电的引脚电源纹波会直接体现在输出信号的相位噪声和杂散上。实测中发现用同一个3.3V稳压器同时给FPGA和ADF4351模块供电时输出频谱靠近载波的地方会出现少量杂散后来我给模块单独加了LC滤波杂散低了几个dB。模块原理图上一般已经在每个电源引脚旁边放了100nF电容但如果你自己设计PCB还要注意在VCO供电引脚旁边再加一个10nF和一个10uF电容形成低阻抗滤波。电容摆放要靠近引脚走线先经过电容再到芯片尽量避免电源与信号线平行长距离走线。ADF4351的射频输出是开集电极结构模块上通常已经做好了偏置电感和隔直电容。如果自己画板RFout和RFout-是两个开集电极输出需要通过射频电感上拉到VCO电压轨再经隔直电容和LC匹配网络转成50欧姆单端信号。ADI官方评估板原理图是很好的参考照着抄匹配元件值基本不会错。4.3 环路滤波器参数怎么选环路滤波器决定了PLL的锁定时间、相位噪声和杂散抑制度这一步我不建议手算直接使用ADI官方的ADIsimPLL软件输入参考频率、输出频率、PFD频率、电荷泵电流等参数软件会自动生成环路滤波器的电阻电容值。工程上常用的配置是三阶无源低通滤波器环路带宽设置在10kHz到50kHz之间。环路带宽宽一些锁定时间短但对参考杂散抑制差一些带宽窄一些相位噪声好、杂散抑制好但锁定时间变长。做点频源对锁定时间不敏感取30kHz左右比较均衡。相位裕度最好在45度以上防止温度变化时环路自激。有一个教训分享给大家环路滤波器参数不能随便抄别人板子上的值因为电荷泵电流设置、VCO灵敏度、鉴相频率不同这些值就不通用。用ADIsimPLL生成之后再对照实际模块的元件封装焊接然后上频谱仪看锁定状态和相噪如果有问题再微调带宽。5. 实测调试与常见问题排查5.1 上板测试流程拿到一套全新的ADF4351模块建议按下面的流程走一遍能快速定位问题先把FPGA工程综合烧录好用逻辑分析仪或者ChipScope抓一下SPI引脚SCLK有没有波形、SDATA是不是按顺序翻转、LE是否有低脉冲。这里一定要确认LE在每一帧之间有一个高电平时间有的芯片对LE脉冲宽度有最低要求太短会写不进去。确认SPI波形正确后观察LD引脚的LED。如果锁定成功LED亮起来。如果一直不亮优先检查参考频率有没有进来。很多模块的参考输入是SMA口需要外部给一个10MHz信号光靠板载晶振可能没接好或者参考幅度太低ADF4351要求参考信号幅度一般在0.5Vp-p以上。锁定正常后把RF输出用同轴线接到频谱仪设置SPAN为200MHz中心频率3GHz应该能看到明显的载波尖峰。接着缩小SPAN到10kHz看信号是不是一条干净的谱线有没有明显的杂散。此时可以读取实测频率正常应该显示3.000000GHz偏差在参考源的精度范围内。5.2 常见问题速查表我在调试过程中遇到过的典型问题整理成下表方便排查现象可能原因排查方向完全没有RF输出CE未拉高检查CE引脚电平完全没有RF输出芯片电源异常测量VCC引脚实际电压完全没有RF输出SPI初始化未完成ChipScope抓取LE/SCLK信号LD指示不亮参考信号没进来检查TCXO输出或外部REFINLD指示不亮PFD频率配置错误核对R2寄存器的R分频系数输出频率偏低几百kHz小数分频参数错核对R0的FRAC和R1的MOD输出功率明显偏低R4输出功率档位低修改R4对应字段SCLK有波形但数据不对电平标准不匹配UCF增加IOSTANDARD LVCMOS33寄存器写不进去SCLK频率过高降低SPI时钟频率输出频谱旁边有杂散电源纹波大加强电源滤波5.3 独家避坑经验聊几个常规文档里不会写的细节。第一个是关于SPI时钟速率的。ADF4351数据手册里写了SPI的最高时钟频率但实际调试时不要顶着上限跑。我把SPI时钟从12.5MHz降到3.125MHz后再也没有出现过初始化偶尔失败的情况。这个芯片初始化是上电一次性动作快那几微秒没有任何实际意义稳定最重要。第二个是LE引脚的处理。很多人在FPGA里习惯把LE和片选信号CS混为一谈实际上ADF4351的LE是锁存信号不是单纯的片选。低电平期间数据逐位移入最后拉高的一瞬间数据被锁存到目标寄存器。LE不能用普通SPI外设的CS自动控制逻辑替代最好在状态机里手动精确控制。第三个是软件片选和硬件片选的区别。FPGA的SPI控制器如果使用普通IO模拟时序LE就是普通的软件控制信号电平翻转时间完全由逻辑决定这个反而比很多单片机SPI外设的硬件CS更灵活。如果你以后要接多个ADF4351只需要给每个芯片分配一个独立的LE引脚SCLK和SDATA可以共享这是很典型的软件片选应用。第四个是关于仿真和综合时的license报错。用ISE 14.7做仿真时偶尔会碰到“failure to obtain a verilog simulation license”之类的报错这是ISE的仿真器license问题不影响综合布线。解决办法是改用ModelSim做仿真或者在ISE里重新指定一个有效的license文件路径。刚上手的人在这一步很容易卡住其实跟工程本身没什么关系。写在最后的一个小技巧这个工程做完之后我又做了一次小改造让实用性提升了不少在FPGA里加了一个简单的串口接收模块通过上位机发送频率参数FPGA把参数换算成INT、FRAC、MOD后自动更新R0和R1寄存器。这样一来原本固定在3GHz的模块就变成了一个可编程频率源扫频、跳频都能做了。核心的SPI控制逻辑一点没动只是在寄存器赋值前多了一层参数解析。如果你也想这么扩展建议一开始写Verilog的时候就把寄存器配置参数化不要把频率值硬编码到一堆魔数里。我在第一版代码里偷懒直接写了固定值后面改扫频逻辑时花了半天去对照每个参数对应哪个频率非常痛苦。现在我把R0、R1的关键字段定义成独立的parameter想换频率只改一处调试效率高很多。
返回列表