学习目标

在这个项目中,我们将通过FPGA串行DAC构成一个简单的任意波形发生器,从而学习信号合成的基本原理以及如何用FPGA内部的SRAM构成存储波表的ROM,通过产生能够访问这些波表的地址指针达到调整输出信号频率的目的。在本项目训练中需要掌握的知识点:

  • 按键输入及防抖处理
  • DDS的基本原理及FPGA实现
  • FPGA内部软核的调用及ROM构成
  • 信号的频谱变化
  • 串行DAC的工作原理及数据/控制接口
  • 信号流程中的时序及各级时钟产生
  • 通过示波器频谱仪观察信号的构成及质量

simpledds.png

项目要求

图1. 小脚丫FPGA同串行DAC构成的任意信号发生器

本项目采用Analog Devices公司的8位串行DAC芯片AD5601,小脚丫FPGA模块通过SPI总线与AD5601连接,AD5601输出模拟信号,通过一个RC低通滤波器进行抗混叠滤波,得到最高频率为200KHz以内的信号。具体要求如下:

  • 输入:5个按键,用于控制输出信号的波形以及频率选择
  • 时钟:25MHz晶振,FPGA内如需更高频率的时钟可以通过FPGA内置的PLL电路来实现
  • 输出波形可调:正弦波、三角波、阶梯波、方波、白噪声
  • 频率可调:可以输出频率从5Hz-200KHz的波形,调节精度达到1Hz
  • 输出信号的幅度为0-3.3V

硬件构成

小脚丫FPGA

5个按键

DAC - AD5601:

  • 2.7 V至5.5 V,在本系统中工作电压为3.3V,其DAC转换用参考电压也为3.3V
  • 工作电流小于100µA
  • 8位nanoDAC®,由于本项目只是掌握原理,不追求更高的性能,因此采用了8位的器件,如需更高的性能可以选用管脚兼容的其它器件
  • SPI接口
  • 采用LFCSP和SC70封装

图2 AD5601的内部功能框图

图3 AD5601数据接口时序

逻辑架构

DDS的构成框图

图4 DDS的构成框图

通过DDS产生的信号频谱

图5 通过DDS产生的信号频谱

DDS系统中的抗混叠滤波器的使用

图6 DDS系统中的抗混叠滤波器的使用

调试过程:由于这是一个相对复杂的系统,我们采用分段实现的方式进行系统实现和分阶段调试:

  1. DAC产生直流电压:通过SPI串行总线的低速数模变换器DAC得到需要的直流电压,按键更改数字参数能够改变输出直流电压的值
  2. DAC生成正弦波:通过FPGA内部寄存器或调用内部IP核将内嵌的块RAM配置成256深度,位宽为8位的ROM存储一个周期的正弦波波形信号,逻辑控制将波形表中的每个点通过DAC输出,生成模拟的正弦波信号
  3. DAC生成任意波形,并且可以调整频率

核心代码

  • top.v: 系统的顶层文件
  • keyin.v: 按键输入控制,选择波形以及调整频率,需要防抖处理
  • dds.v: 根据频率控制字能够改变的相位累加器
  • rom.v: Lattice Diamond系统自带软核构成的波表存储器,可以配置成宽度8位、深度256Byte的ROM,如果要存储多个波形方便切换,可以生成对应于不同波形的ROM文件
  • spidac.v: 通过SPI总线协议将ROM表中的数据串行发送給AD5601

代码实现

通过SPI控制DAC的Verilog代码

本参考代码是控制Max548 - 双通道8位串行DAC,其SPI接口模式和内部控制寄存器都不相同,但设计思路一致

module SPIDAC(CLOCK, DAC_SCLK, DAC_DIN, DAC_CS, DAC_LD, DATA_BYTE, RESET);
input 		CLOCK;                  // 96MHz system clock
input 		RESET;                  // system reset
input [7:0]     DATA_BYTE_A;             // Data Byte Register
 
output 		DAC_SCLK;               // 6MHz Serial Clock
output 		DAC_DIN;                // Serial Data output
output 		DAC_CS;                 // Serial Interface Select
output 		DAC_LD;                 // DAC Load output
 
// data_reg is used for buffer address and data
reg [15:0]	data_reg;
 
// Create 6MHz clock from system clock
reg [3:0] 	cnt6;
wire 		clk6mhz;
 
always @(posedge CLOCK or posedge RESET)
begin
   if (RESET)
      cnt6 <= 0;
   else
      cnt6 <= cnt6 + 1;
end 
 
assign clk6mhz = cnt6[3];
 
// Detect changing of control byte and data byte
reg [7:0] 	comp_reg;
reg 		data_chg;
 
always @ (negedge clk6mhz or posedge RESET)
begin
	if (RESET)	{comp_reg, data_chg} <= 0;
   	else	begin
		comp_reg <= DATA_BYTE;
      	if (comp_reg != DATA_BYTE)	data_chg <= 1;
      	else  data_chg <= 0;
   end
end
 
// Shift data_reg 
reg [4:0] 	cnt16;
reg 		DAC_CS;
 
always @(posedge clk6mhz or posedge RESET)
begin
	if (RESET)	begin
		DAC_CS <= 1;
		cnt16 <=  0;
		data_reg <={16'h0};
	end
	else if (data_chg) begin
		DAC_CS <=0;  
		cnt16 <= 0;
		data_reg <={8'h0a,DATA_BYTE};
	end
	else
   	if(cnt16 < 15)	begin
		cnt16 <= cnt16 + 1;
		data_reg <= data_reg <<1;
	end
	else
		DAC_CS <= 1;
end
 
wire 	clk12mhz;
assign 	clk12mhz = cnt6[2];
 
assign 	DAC_LD = 1'b1;
assign 	DAC_DIN = data_reg[15];
 
reg DAC_SCLK;
always @(negedge clk12mhz or posedge RESET)
begin
	if (RESET)		DAC_SCLK <= 0;
	else if (!DAC_CS)	DAC_SCLK <= !DAC_SCLK ;
end
endmodule

结果及资源报告

参考资料