信号发生器在电子实验中作为信号源,通常用得多的是正弦波、三角波、方波以及用作触发信号的脉冲波。在这里给大家分享下如何通过FPGA实现任意信号发生器,希望对相关课题的小伙伴有所帮助。
一、设计任务 通过FPGA驱动旋转编码器得到操作信息,通过逻辑控制波形和频率寄存器,设计DDS模块根据波形和频率寄存器控制波形数据的输出,波形数据通过串行DAC驱动模块传送到底板的DAC芯片进行转换,得到波形信号输出。
二、设计准备 硬件:小脚丫FPGA核心板、DAC芯片(DAC081S101)、旋转编码器 软件:Quartus Prime/Lattice Diamond
三、设计结构
四、设计原理 1.DAC模块 串行DAC(以DAC081S101为例)与数字电路接口为三根线(sync,clk,din),兼容三线SPI总线,sync为帧同步管脚,clk为芯片时钟管脚,din为芯片数据管脚,当DAC帧同步信号拉低后每个clk周期从din发送1bit的数据,但是根据DAC081S101的时序,需要16个clk完成一次DA转换,所以clk频率至少等于采样率的16倍。
DAC模块电路,其电路图如下:FPGA直接连接DAC081S101芯片的控制端,ADC有6个管脚,2脚VCC为VCC和Vref功能复用,即VCC =Vref。DAC后端是运放电路LMV721,运放模块为电压跟随电路,再往后端射频端子输出。
五、设计驱动 1. DAC模块驱动 根据DAC081S101的芯片手册以及通信时序,我们用Verilog设计一个计数器,当计数器值不同时完成不同操作,实现一次DAC转换,程序实现如下: - reg[7:0] cnt;
- always@(posedge clk ornegedge rst_n)
- if(!rst_n) cnt <=1'b0;
- elseif(cnt >=8'd34) cnt <=1'b0;
- else cnt <= cnt +1'b1;
-
- reg[7:0] data;
- always@(posedge clk ornegedge rst_n)
- if(!rst_n)begin
- dac_sync <= HIGH; dac_clk <= LOW; dac_dat <= LOW;
- endelsecase(cnt)
- 8'd0:begin dac_sync <= HIGH; dac_clk <= LOW; data <= dac_data;end
- 8'd1,8'd3,8'd5,8'd7,8'd9,8'd11,8'd13,8'd15,
- 8'd17,8'd19,8'd21,8'd23,8'd25,8'd27,8'd29,8'd31,
- 8'd33:begin dac_sync <= LOW; dac_clk <= LOW;end
- 8'd2:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= LOW;end//15
- 8'd4:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= LOW;end//14
- 8'd6:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= LOW;end//13
- 8'd8:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= LOW;end//12
- 8'd10:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[7];end//11
- 8'd12:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[6];end//10
- 8'd14:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[5];end//9
- 8'd16:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[4];end//8
- 8'd18:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[3];end//7
- 8'd20:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[2];end//6
- 8'd22:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[1];end//5
- 8'd24:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= data[0];end//4
- 8'd26:begin dac_sync <= LOW; dac_clk <= HIGH; dac_dat <= LOW;end//3
- 8'd28:begin dac_sync <= LOW; dac_clk <= HIGH; dac_done <= HIGH;end//2
- 8'd30:begin dac_sync <= LOW; dac_clk <= HIGH; dac_done <= LOW;end//1
- 8'd32:begin dac_sync <= LOW; dac_clk <= HIGH;end//0
- 8'd34:begin dac_sync <= HIGH; dac_clk <= LOW;end
- default:begin dac_sync <= HIGH; dac_clk <= LOW; end
- endcase
复制代码我们就完成了串行DAC芯片DAC081S101的驱动设计,整个采样周期用了35个系统时钟,如果我们采用12MHz时钟作为该模块系统时钟,转换率Fs = 12M / 35 = 342.86 Ksps,DAC主频Fsclk = 12 MHz / 2 = 6MHz,DAC081S101芯片手册Fsclk最高频率为30MHz,所以想要更高的转换率,可以将系统时钟的频率从12 MHz倍频到60 MHz。
模块接口如下:clk和rst_n为系统时钟及复位,dac_sync,dac_clk和dac_dat为DAC控制管脚,dac_data为DAC转换数据,dac_done脉冲对应一次DAC转换的完成 - input clk, //系统时钟
- input rst_n, //系统复位,低有效
- output reg dac_done, //DAC采样完成标志
- input [7:0] dac_data, //DAC采样数据
- output reg dac_sync, //SPI总线CS
- output reg dac_clk, //SPI总线SCLK
- output reg dac_dat //SPI总线MOSI
复制代码
2.DDS设计实现 如上图所示,以8位DAC为例,将一个周期的正弦波分割成256份,得到256个相位波形量化数据,设计一个存储器,宽度为8,深度为256,将256个相位波形量化数据放入存储器中,我们就得到了一个正弦波波表。我们可以通过例化rom的IP核实现波表的设计,具体的操作如下: 第一步我们需要有波表数据初始化文件(.mif文件),方便再例化rom核时配置需要的初始化数据,得到波表数据初始化文件的方法很多,简单介绍两种:
2. 安装正弦波数据生成器(网上搜索下载)工具软件,配置相应的参数,直接导出生成初始化.mif文件。
第二步例化rom核,打开Tools菜单下的IP Catalog工具,依次找到Libraty → Basic Functions → On Chip Memory → ROM:1-PORT,配置存储器宽度和深度,选择刚刚生成的初始化文件,其他选项默认即可,点击Finish完成rom的IP核例化。
第三步更改配置模式,因为例化了rom的IP核,需要初始化数据,所以需要更改配置模式。打开Assignments菜单下的Device选项,点击Device and Pin Opetions,找到Configuration → Configuration mode,下拉列表中选择最后一项,点击OK保存。
打开rom核中的Verilog文件,端口声明如下:
- input [7:0] address;
- input clock;
- output [7:0] q;
复制代码 其中clock为存储器时钟,address为存储器地址,q对应address地址中的数据输出,到这里我们的正弦波表就创建好了。
如果我们有上图电路的硬件,当FPGA按照时钟的节拍产生波形数据,就可以得到对应的模拟输出了,我们来看一个简单的DDS产生锯齿波例子:
- module SimpleDDS(clk, dac_dat);
- input clk;
- output[7:0] dac_dat;
- //24位相位累加器
- reg[23:0] phase_acc;
- always@(posedge clk) phase_acc <= phase_acc +24'b1;
- //相位地址
- wire[23:0] phase = phase_acc;
- assign dac_dat = phase[23:16]; //锯齿波
- endmodule
复制代码程序中可以看到DDS模块在每个clk周期都会产生一个8位数据,并行DAC每个clk进行一次DAC转换,主频等于转换率,程序是成立的。对串行DAC(以DAC081S101为例),需要16个clk才能将DDS模块1次产生的8位数据转换,所以逻辑上,DDS模块中的clk端口信号应该跟DAC驱动模块的转换率同步,而不是和系统时钟或主频同步。 波形选择 上面程序中累加器phase_acc随时钟clk自加1,传输给并行DAC的数据同样自加,最后得到锯齿波输出,当clk为100MHz时,锯齿波频率为100MHz/ 2^24 = 5.96Hz。 锯齿波输出: 想要输出三角波,只需要把DAC数据输出赋值的语句改为: - assign dac_dat = phase[23]?~phase[22:15]:phase[22:15];//三角波
复制代码三角波输出: 想要输出方波,只需要把DAC数据输出赋值的语句改为: - assign dac_dat = phase[23]?8'hff:8'h00; //方波
复制代码方波输出: 想要输出正弦波,只需要把DAC数据输出赋值的语句改为波表存储器的例化: - rom u1(
- .clock (clk ),
- .address (phase[23:16] ),
- .q (dac_dat )
- );
复制代码我们知道了各种波形的产生方法,我们可以同时产生多种波形信号,然后根据波形选择端口变量wave的值选择输出的波形数据,程序实现如下: - wire[7:0] sin_dat;//正弦波
- wire[7:0] saw_dat = phase[23:16]; //锯齿波
- wire[7:0] tri_dat = phase[23]?(~phase[22:15]): phase[22:15];//三角波
- wire[7:0] squ_dat = phase[23]?8'hff:8'h00; //方波
-
- always@(*)begin
- case(wave)
- 2'b00: dac_dat = sin_dat; //正弦波
- 2'b01: dac_dat = saw_dat; //锯齿波
- 2'b10: dac_dat = tri_dat; //三角波
- 2'b11: dac_dat = squ_dat; //方波
- default: dac_dat = sin_dat;//正弦波
- endcase
- end
- rom u1(.clock (clk ),
- .address (phase[23:16] ),
- .q (sin_dat )
- );
复制代码频率调节 上面简单的DDS设计中, 将相位累加器的控制改为: - always@(posedge clk) phase_acc <= phase_acc +24'd2;
复制代码
这样原来需要2^24个clk周期完成的相位增量,现在只需要2^23个clk周期,同样的相位增量花费时间减少一半,即模拟输出信号频率是原来的2倍,改变被加数就会改变频率,如果我们定义一个端口变量f_inc作为被加数,使用逻辑调节f_inc的值就可以调节频率,f_inc被称为频率控制字 - always@(posedge clk) phase_acc <= phase_acc + f_inc;
复制代码相位调节 上面简单的DDS设计中, 将相位地址的控制改为: - wire[23:0] phase = phase_acc +24'b1;
复制代码相位累加器每次加一个固定的偏移得到全新的相位地址,使波形数据整体上产生相位偏移,不改变输出的频率,如果我们定义一个端口变量p_inc作为被加数,使用逻辑调节p_inc的值就可以调节相位,p_inc被称为相位控制字 - wire[23:0] phase = phase_acc + p_inc;
复制代码底板上旋转编码器带有按键功能,我们使用按键消抖模块完成按键信号的消抖处理,前面章节我们还学习了旋转编码器旋转功能的驱动,刚刚讲到控制波形信号发生器的波形选择和频率输出其实就是控制两个端口参数wave和f_inc,所以从旋转编码器的驱动输出到DDS端口参数的控制,我们还需要一个逻辑模块。 按动旋转编码器的按键切换信号发生器的波形输出,依次为正弦波、锯齿波、三角波、方波,波形选择功能程序实现如下: - localparam SIN =2'b00, SAW =2'b01, TRI =2'b10, SQU =2'b11;
- //波形输出选择
- always@(posedge clk ornegedge rst_n)begin
- if(!rst_n) wave <= SIN;
- elseif(O_pulse)begin
- case(wave)
- SIN: wave <= SAW;
- SAW: wave <= TRI;
- TRI: wave <= SQU;
- SQU: wave <= SIN;
- default: wave <= SIN;
- endcase
- endelse wave <= wave;
- end
复制代码转动旋转编码器调节信号发生器的输出频率,左旋(逆时针)频率减小,左旋(逆时针)频率增加,频率控制功能程序实现如下: - //频率控制
- always@(posedge clk ornegedge rst_n)begin
- if(!rst_n) f_inc <=24'ha0000;
- elseif(L_pulse)begin
- if(f_inc <=24'h10000) f_inc <= f_inc;
- else f_inc <= f_inc -24'h10000;
- endelseif(R_pulse)begin
- if(f_inc >=24'h140000) f_inc <= f_inc;
- else f_inc <= f_inc +24'h10000;
- endelse f_inc <= f_inc;
- end
复制代码上电默认情况下,频率控制字f_inc的值为24’ha0000,那么在模拟信号周期内采样点n = 2^24 / 10 / 2^16= 25.6。我们设计的DAC驱动转换率为342.86 Ksps,那么对应模拟信号频率 f = 342.86K / 25.6 = 13393Hz = 13.4KHz左右。 旋转编码器调节频率控制字的步进及频率控制字最小值都为24’h10000,对应频率步进值及输出模拟信号最小频率都等于1.34KHz(运算方法同上)。 将设计中所有模块例化连线,完成整体设计,再次强调:DDS模块的时钟信号与DAC模块中的转换率信号同步,所以DDS模块中的clk要和DAC081S101_driver模块中的dac_done信号连接。
本文转载自 公众号 FPGA入门到精通
|