FPGA学习专题-ADC的使用

ADC,即模拟转数字。可以将连续的模拟信号采样后,输出离散的数字信号,所谓数字信号,就是用“0”或者“1”这两个单元来表示一个值,这也是MCU所能识别的信号。所以,模拟转数字是非常有必要的。

此次需求的来源是采集正弦波信号,经ADC采样后把数据传给FFT模块计算,从而进行频谱分析。

首先介绍一下本次使用的ADC芯片:ADC128S022。我们好好来分析分析这个芯片,其他的芯片基本上是万变不离其宗。

1.功能引脚
先看下芯片的引脚图,这个是可以一眼看出芯片的基本功能的
FPGA学习专题-ADC的使用_第1张图片
除去电源部分的引脚,IN0-IN7,是模拟信号输入的通道,可以看出,这个芯片有8个输入通道,也就是你可以向这8个引脚任意一个输入模拟信号进行计算;这8个通道具体选通哪一个是可以通过寄存器来配置的;然后是一个SPI接口,很标准的SPI接口,包括片选引脚,数据输入引脚,数据输出引脚,时钟线。SPI接口是用来向ADC芯片发送配置寄存器命令的,同时把模拟转成数字的信号输出到MCU。

2.配置寄存器
看下这个ADC都需要配置些什么内容呢?
FPGA学习专题-ADC的使用_第2张图片
看到这个,需要配置的也就是通道了,ADD0~ADD2就是用来配置IN0-IN7通道的,默认不配置的话使用的是IN0。

3.时序图
分析芯片到了最重要的环节,就是时序分析,这是保证正常通信的基础。
FPGA学习专题-ADC的使用_第3张图片
4线SPI,CS是片选信号,当SCLK为高的时候拉低CS,表示通信的开始,CS拉高表示通信结束。
DIN信号:IN是相对于ADC芯片来说的,就是对于ADC来说是输入,对于MCU来说是输出。可以看到,在每个SCLK的上升沿,DIN上的信号要保持稳定,此时ADC芯片会对DIN上的信号进行采样,那么从MCU端来看呢,DIN是输出,就是要在SCLK的下降沿就把数据放到DIN上,以便ADC芯片能在SCLK的上升沿采集到信号。
DOUT信号:同上,这个是ADC芯片的输出引脚,对于MCU来说就是输入引脚。参考时序图,可以看出,MCU想要采样DOUT上的数据,需要保证该数据是在稳定状态,对应SCLK信号就是上升沿采样,ADC就是要在SCLK的下降沿把数据放到DOUT线上。
需要关注的是,我们在编写时序代码的时候,是站在FPGA的角度来看的,即:对应于一个已知的SCLK,我需要在SCLK的下降沿把数据放到DIN上,在SCLK的上升沿采样DOUT上的数据

看下具体的数据传输细节吧
FPGA学习专题-ADC的使用_第4张图片
CS拉低通信开始,通过DIN向ADC发送控制寄存器数据,可以看到,前两个bit的数据我们不用关心,都是DONTC,从ADD2-ADD0是模拟转换通道选择,接着3个bit也不用关心,发送完这个控制寄存器后,后面就不用再关心这个了,等着接收数据了,以下是针对整个的数据传输过程,将每一个时刻的引脚信号详细列出来,方便分析:

FPGA学习专题-ADC的使用_第5张图片

了解了通信的基本时序,还需要关注的就是这个SCLK我们给多少的问题。从上面的引脚功能表格中可以看出,SCLK的时钟频率在0.8M-3.2MHz。我么就是要通过FPGA产生一个这个频率范围内的时钟信号。
SCLK无非是时钟的分频,我们使用的FPGA的时钟是50M,所以就是将50M的时钟分频到SCLK支持的频率范围内即可。

4.FPGA控制代码
需要了解的是,上表格中列出的通信过程,是使用的线性序列机,就是用最原始的方法将一帧数据的传输过程完全拆分,这里的状态也就是我们使用的SCLK的时钟频率。结合代码分析:



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

代码仅供参考

你可能感兴趣的:(Quartus,fpga开发,硬件工程)