FPGA之双路DAC的使用和多路ADC的使用

发布于:2022-12-14 ⋅ 阅读:(1062) ⋅ 点赞:(0)

一 、双路DAC       

         这次实验使用的双路 DA 模块是正点原子推出的一款双路高速数模转换模块(ATK_DUAL_HS_DA), 高速 DA 转换芯片是由思瑞浦公司生产的 3PD5651E 芯片。

                3PD5651E 芯片输出的是一对差分电流信号,为了防止受到噪声干扰,电路中接入了低通滤 波器,然后通过高性能和高带宽的运放电路,实现差分变单端以及幅度调节等功能,使整个电路性能得到 了最大限度的提升,最终输出的模拟电压范围是-5V~+5V .

芯片特点:

        3PD5651E 的数模转换位数为 10 位,最大转换速度为 125MSPS(每秒采样百万次,Million Samples per Second)

        两个电流输出端 IOUTA 和 IOUTB 为一 对差分电流,当输入数据为 0(DB9~DB0=10’h000)时,IOUTA 的输出电流为 0,而 IOUTB 的输出电流达 到最大,最大值的大小跟参考电压有关;当输入数据全为高点平(DB9~DB0=10’h3ff)时,IOUTA 的输出 电流达到最大,最大值的大小跟参考电压有关,而 IOUTB 的输出电流为 0

由图 可知,数据在时钟的上升沿锁存,因此我们可以在时钟的下降 沿发送数据,这样使 DA 芯片在数据的中央采样,保证数据采样的准确性。值得注意的是采样时钟越快,数模转换越快 

 其实就使用来说是很简单的。我们只需要给芯片提供驱动时钟和DATA就可以。然后数据由ROM提供。于是我们只需要设计1时钟模块提供da_clk;2rom读模块,从rom中读出数据给DAC用;3DAC驱动模块,驱动DAC工作。双端口输出我们只需要定义两个端口输出就可以,如果是多端口可能需要配置寄存器。

这次使用BMG块来当ROM用。

下面是主要的DAC驱动模块。由PLL提供时钟,ROM提供数据。

module da_wave_send(
    input clk , //时钟
    input rst_n , //复位信号,低电平有效

    input [9:0] rd_data, //ROM 读出的数据
    output reg [9:0] rd_addr, //读 ROM 地址
    //DA 芯片接口
    output da_clk , //DA(AD9708)驱动时钟,最大支持 125Mhz 时钟
    output [9:0] da_data //输出给 DA 的数据 
);

 //parameter
 //频率调节控制
  parameter FREQ_ADJ = 10'd5; //频率调节,FREQ_ADJ 的越大,最终输出的频率越低,范围 0~255

 //reg define
  reg [9:0] freq_cnt ; //频率调节计数器

//数据 rd_data 是在 clk 的上升沿更新的,所以 DA 芯片在 clk 的下降沿锁存数据是稳定的时刻
//而 DA 实际上在 da_clk 的上升沿锁存数据,所以时钟取反,这样 clk 的下降沿相当于 da_clk 的上升沿
    assign da_clk = ~clk; 
    assign da_data = rd_data; //将读到的 ROM 数据赋值给 DA 数据端口

//频率调节计数器
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        freq_cnt <= 10'd0;
    else if(freq_cnt == FREQ_ADJ) 
        freq_cnt <= 10'd0;
    else 
        freq_cnt <= freq_cnt + 10'd1;
    end

//读 ROM 地址
 always @(posedge clk or negedge rst_n) begin
     if(rst_n == 1'b0)
         rd_addr <= 10'd0;
     else begin
         if(freq_cnt == FREQ_ADJ) begin
             rd_addr <= rd_addr + 10'd1;
         end 
     end 
 end

 endmodule

这里两路数据接一块给DAC两个输入端口,所以DAC输出是一样的。DAC输出一般都是一对差分电流信号。这个实验用的DAC芯片,将其稳定滤波并转换成单端的电压信号。

 上面代码是读模块,读出ROM数据输出给DAC并输出驱动时钟给DAC。clk来自PLL,rd_data来自ROM。这些ip设置这里不做详细介绍。

二、多路ADC

        (21条消息) FPGA学习专题-ADC的使用_南山维拉的博客-CSDN博客_adc fpga

        这里以某芯片为例,其他的八九不离十。先看芯片引脚图和SPI时序

在这里插入图片描述

 IN0toIN7就是我们的模拟输入,多通道。而我们的输出只有一个串行输出。所以我们要配置寄存器来指明是要哪个通道的的转换输出。还有就是SPI时序了。直接上图

在这里插入图片描述

 在这里插入图片描述

 也就是说我们还有四个引脚来完成我们的时序。首先是在SCLK高电平时,CS片选端由高拉低就是数据处理开始(即输入输出有效),结束反之。一个是DIN一个是DOUT,这两个一个是来自FPGA的控制寄存器DIN一个是ADC的输出DOUT。

需要关注的是,我们在编写时序代码的时候,是站在FPGA的角度来看的,即:对应于一个已知的SCLK,我需要在SCLK的下降沿把数据放到DIN上,在SCLK的上升沿采样DOUT上的数据

所以这里的时序就是ADC在SCLK上升沿采样并输出DOUT,同时FPGA也采样DOUT。FPGA在下降沿输出DIN,ADC就能在SCLK上升沿接受到DIN。

这个芯片的SCLK要求是0.8M-3.2MHz我们就需要分频到这么多。

接下来就分析代码

这个模块最终就是要输出我们ADC得到的数据,方便后续处理。除了与ADC之间的接口外我们还需要设置,

Channel,通道选择一共三位在实际使用中我们填入我们需要的通道。或者灵活设置。
Data,        是FPGA采集后输出给其他模块的数字信号,方便后续处理比如FFT。
En_Conv,    
Conv_Done, 完成标志
 ADC_State,        状态
DIV_PARAM,        分频系数。

注意里面有个2倍SCLK的信号,这个时钟是用来驱动状态机并产生SCLK的。

输出是寄存型。要用移位方法表示过程,最后完成一次转换后将当前寄存的值赋值给DATA。



module adc128s022(
			Clk,
			Rst_n,
			
			Channel,
			Data,
			
			En_Conv,
			Conv_Done,
			ADC_State,
			DIV_PARAM,
			
			ADC_SCLK,
			ADC_DOUT,
			ADC_DIN,
			ADC_CS_N	
		);

	input Clk;	//输入时钟
	input Rst_n; //复位输入,低电平复位
	input [2:0]Channel;	//ADC转换通道选择
	output reg [11:0]Data;	//ADC转换结果
	
	input En_Conv;	//使能单次转换,该信号为单周期有效,高脉冲使能一次转换
	output reg Conv_Done;	//转换完成信号,完成转换后产生一个时钟周期的高脉冲
	output ADC_State;	//ADC工作状态,ADC处于转换时为低电平,空闲时为高电平
	input [7:0]DIV_PARAM;	//时钟分频设置,实际SCLK时钟 频率 = fclk / (DIV_PARAM * 2)
	
	output reg ADC_SCLK;	//ADC 串行数据接口时钟信号
	output reg ADC_CS_N;  //ADC 串行数据接口使能信号
	input  ADC_DOUT;		//ADC转换结果,由ADC输给FPGA
	output reg ADC_DIN;	//ADC控制信号输出,由FPGA发送通道控制字给ADC
	
	reg [2:0]r_Channel; //通道选择内部寄存器
	reg [11:0]r_data;	//转换结果读取内部寄存器
	
	reg [7:0]DIV_CNT;//分频计数器
	reg SCLK2X;//2倍SCLK的采样时钟
	
	reg [5:0]SCLK_GEN_CNT;//SCLK生成暨序列机计数器

	
	reg en;//转换使能信号
	
	//在每个使能转换的时候,寄存Channel的值,防止在转换过程中该值发生变化
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		r_Channel <= 3'd0;
	else if(En_Conv)
		r_Channel <= Channel;
	else
		r_Channel <= r_Channel;

	//产生使能转换信号
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		en  <= 1'b0;
	else if(En_Conv&&(Conv_Done==1'b0))
		en  <= 1'b1;
	else if(Conv_Done)
		en  <= 1'b0;
	else
		en  <= en;
		
	//生成2倍SCLK使能时钟计数器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		DIV_CNT  <= 8'd0;
	else if(en)begin
		if(DIV_CNT == (DIV_PARAM - 1'b1))
			DIV_CNT  <= 8'd0;
		else 
			DIV_CNT  <= DIV_CNT + 1'b1;
	end else	
		DIV_CNT  <= 8'd0;

	//生成2倍SCLK使能时钟
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		SCLK2X  <= 1'b0;
	else if(en && (DIV_CNT == (DIV_PARAM - 1'b1)))
		SCLK2X  <= 1'b1;
	else
		SCLK2X  <= 1'b0;
		
	//生成序列计数器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		SCLK_GEN_CNT  <= 6'd0;
	else if(SCLK2X && en)begin
		if(SCLK_GEN_CNT == 6'd33)
			SCLK_GEN_CNT  <= 6'd0;
		else
			SCLK_GEN_CNT  <= SCLK_GEN_CNT + 1'd1;
	end else
		SCLK_GEN_CNT  <= SCLK_GEN_CNT;
	
	//序列机实现ADC串行数据接口的数据发送和接收	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		ADC_SCLK <= 1'b1;
		ADC_CS_N <= 1'b1;
		ADC_DIN  <= 1'b1;
	end 
	else if(en) begin//2
		if(SCLK2X)begin//1
			case(SCLK_GEN_CNT)
				6'd0:begin ADC_CS_N <= 1'b0; end
				6'd1:begin ADC_SCLK <= 1'b0; ADC_DIN  <= 1'b0; end
				6'd2:begin ADC_SCLK <= 1'b1; end
				6'd3:begin ADC_SCLK <= 1'b0; end
				6'd4:begin ADC_SCLK <= 1'b1; end
				6'd5:begin ADC_SCLK <= 1'b0; ADC_DIN  <= r_Channel[2];end	//addr[2]
				6'd6:begin ADC_SCLK <= 1'b1; end
				6'd7:begin ADC_SCLK <= 1'b0; ADC_DIN  <= r_Channel[1];end	//addr[1]
				6'd8:begin ADC_SCLK <= 1'b1; end
				6'd9:begin ADC_SCLK <= 1'b0; ADC_DIN  <= r_Channel[0];end	//addr[0]

				//每个上升沿,寄存ADC串行数据输出线上的转换结果
				6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32:
					begin ADC_SCLK <= 1'b1; r_data <= {r_data[10:0], ADC_DOUT}; end	//循环移位寄存DOUT上的12个数据
				
				6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,6'd27,6'd29,6'd31:
					begin ADC_SCLK <= 1'b0; end
				
				6'd33:begin ADC_CS_N <= 1'b1; end
				default:begin ADC_CS_N <= 1'b1; end //将转换结果输出
			endcase
		end//1
		else ;
	end//2 
	else begin
		ADC_CS_N <= 1'b1;
	end
	
	//转换完成时,将转换结果输出到Data端口,同时产生一个时钟周期的高脉冲信号
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		Data <= 12'd0; 
		Conv_Done <= 1'b0;
	end else if(en && SCLK2X && (SCLK_GEN_CNT == 6'd33))begin
		Data <= r_data; 
		Conv_Done <= 1'b1;
	end else begin
		Data <= Data; 
		Conv_Done <= 1'b0;
	end
	
	//产生ADC工作状态指示信号
	assign ADC_State = ADC_CS_N;

endmodule

三、双路ADC并行输出

        

         这个就比较简单了,我们的接口只有驱动时钟、使能,输入就是数据和量程标志。程序设计也简单。我们只需要用PLL生成对应CLK就可以,然后和上面一样的时序要求,取反接到ADC。使能信号倒是我们需要考虑的。比如完成一次转换后还需不需要什么处理。

//adc采集模块
 module hs_dual_ad(
     input                 sys_clk      ,   //系统时钟
	 input 				   rst_n		,
     //AD0
     input     [9:0]       ad0_data     ,   //AD0数据
     input                 ad0_otr      ,   //输入电压超过量程标志
     output                ad0_clk      ,   //AD0采样时钟
     output                ad0_oe       ,   //AD0输出使能
     //AD1
     input     [9:0]       ad1_data     ,   //AD1数据
     input                 ad1_otr      ,   //输入电压超过量程标志
     output                ad1_clk      ,   //AD1采样时钟  
     output                ad1_oe           //AD1输出使能
	 //输出数据
	 output    [9:0]		data0       ,
	 output    [9:0]		data1       ,
     );
      
 //wire define
 wire clk_out1;
 wire clk_out2;
 //*****************************************************
 //**                    main code
 //*****************************************************  
 assign  ad0_oe =  1'b0;
 assign  ad1_oe =  1'b0;
 assign  ad0_clk = ~clk_out1;
 assign  ad1_clk = ~clk_out1;
 assign  data0   = ad0_data;
 assign  data1	 = ad1_data;
 //并行ADC,一个驱动时钟周期采样一次,在上升沿采样
 
/*always@(posedge	clk_out1 or negedge rst_n)begin
	if(!rst_n)begin
		data0<=10'b00000_00000;
		data1<=10'b00000_00000;
	end
	else begin
		data0<=ad0_data;
		data1<=ad1_data;
	end
end */
 
 
 //时钟ip核例化
 clk_wiz_0 u_clk_wiz_0
    (
     // Clock out ports
     .clk_out1(clk_out1),    // output clk_out1
     // Status and control signals
     .reset(1'b0), // input reset
     .locked(locked),        // output locked
    // Clock in ports
     .clk_in1(sys_clk));     // input clk_in1
     

 
 endmodule

这个ADC是并行的,也就是一个时钟采样一次然后输出一个10位数据,ADC对应输出有10个输出接口。我们在FPGA中用10位输入来接上,然后再输出给下一个模块。

 


网站公告

今日签到

点亮在社区的每一天
去签到