基于AD9833的信号发生器

本文利用FPGA控制AD9833,实现信号发生器的功能。本文将对AD9833的手册进行详细的解读,并对其配置方法进行解析,最后在Verilog中进行编码,将代码烧录置FPGA中,FPGA通过外部引脚控制AD9833输出所需要的正弦波、方波和三角波。三种波形能够输出的频率范围为0~12.5Mhz。

文章目录

  • 前言
  • 一、AD9833数据手册分析
    • 1.1 AD9833特性分析
    • 1.2 AD9833时钟分析
    • 1.3 AD9833引脚分析
    • 1.4 AD9833操作原理分析
  • 二、FPGA控制代码
    • 2.1 主函数
    • 2.2 按键检测模块
    • 2.3 操作写入模块
    • 2.4 驱动操作模块
  • 三、结果展示
  • 总结

前言

AD9833是一种低功率可编程波形发生器,能够产生正弦、三角形和方波输出。在各种类型的传感、驱动和时域反射测量应用中,都需要产生波形。输出频率和相位是软件可编程的,允许易于调整。不需要任何外部组件。频率寄存器为28位;以25MHz的时钟速率,可以达到0.1 Hz的分辨率。类似地,使用1MHz的时钟速率,AD9833可以被调谐到0.004 Hz的分辨率。

AD9833是通过三线串行接口写入的。该串行接口以时钟速率高达40MHz,并与DSP和微控制器标准兼容。该设备的电源供电范围为2.3V至5.5V。

一、AD9833数据手册分析

博主将首先分析AD9833的数据手册,并列出博主认为手册中需要着重注意的点,以便于后续代码的提出。

1.1 AD9833特性分析

在AD9833的规格中,我们可以知道,其 基于AD9833的信号发生器 也就是最大输出电压可以达到0.65V,其具体的表达式如下:
基于AD9833的信号发生器

即:输出电压的大小跟参考电压和负载电阻密切相关,同时随着设置波形频率的变化而变化。

另外,AD9833的VDD的范围为2.3V-5.5V,也就是说你需要提供一定数值的电源电压。

1.2 AD9833时钟分析

在手册中,我们可以看到AD9833的时钟图如图1所示:
AD9833时钟

图1 AD9833时钟

在图1中,我们需要着重关注的是AD9833的MCLK周期最小为40ns,也就是说你需要提供25Mhz的激励时钟信号,用于驱动AD9833进行内部数据操作。

AD9833操作时序

图2 AD9833操作时序

观察图2,我们可知:如何需要写入AD9833的控制字,我们首先需要将FSYNC拉低,在SCLK操作时钟的周期性变化下,将SDATA也就是操作字逐一写入。需要注意的是,SCLK的操作时钟周期最小需要25ns,高低电平的维持时间最小需要10ns,在最后一个下降沿数据写入后需要在10ns-20ns的时间内将FSYNC的电平拉高,否则写入无效。

1.3 AD9833引脚分析

AD9833的引脚图如图3所示:

图3 AD9833引脚图

在AD9833的引脚中,MCLK为激励时钟,我们采用25Mhz;FSYNC在拉低后进行写入操作,随着SCLK的电平变化将SDATA的值读入。另外,VDD和AGND之间应连接一个0.1uF和一个10uF的解耦电容。

1.4 AD9833操作原理分析

要想讲清楚AD9833的控制方式,我们需要重点观察其操作字是如何定义的。

图4 AD9833操作字

在上述的16个控制位中,博主将讲解需要着重观察的控制位,其他控制位请读者自行查看手册。

  • B28
    当B28 = 1时允许连续两次写入将完整单词加载到频率寄存器中。第一次写包含频率单词的14个LSB,下一次写将包含14个MSB。每个16位字的前两位定义了该字被加载到的频率寄存器,因此,对于这两个连续的写操作,前两位应该是相同的。
    当B28 = 0时,28位频率寄存器作为两个14位寄存器运行,一个包含14个MSB,另一个包含14个LSB。这意味着频率词的14个MSB可以独立于14个LSB进行改变,反之亦然。为了改变14个MSB或14个LSB,需要对适当的频率地址进行一次写入。控制位D12(HLB)通知AD9833,要改变的位是14个MSB还是14个LSB。
    博主在后文中将B28 = 1,也就是连续写入两个16位字对频率寄存器进行更改。
  • RESET
    RESET = 1将内部寄存器重置为0,这对应于中度的模拟输出。
    RESET = 0将禁用重置。
    需要注意的是,RESET将不重置相位、频率和控制寄存器。另外,为了避免AD9833在初始化的时候出现虚假的DAC输出,需要在进行写入操作时也就是B28 = 1时将RESET位置1。
  • MODE
    MODE位与OPBITEN(D5)一起使用。这个位的功能是控制芯片上DAC连接到VOUT时VOUT引脚的输出。
    当MODE = 1时三角波将从DAC中输出。
    当MODE = 0时正弦波将输出。

接下来我们将利用代码来详细论述操作字的写入流程。

二、FPGA控制代码

我们将利用Verilog语言编写FPGA的代码,并用FPGA控制AD9833的SCLK、SDATA、FSYNC引脚,进行写入操作。

2.1 主函数

主函数中主要包含了各个模块,并将模块之间的变量联系起来。

module Signal_Generate(
	input sys_clk,
	input sys_rst_n,
	
	input key0,				// 按键检测切换波形状态
	input key1,
	
	output MCP_CS,			// MCP使能标志 		低电平有效
	output SCK,				// MCP输入信号状态	低电平信号变化 下降沿读信号
	output SI,				// MCP输入信号
	output AD_FSYNC			// AD使能标志
	
    );

// parameter define
parameter   CLK_FREQ   = 26'd50_000_000;	// MCP模块的驱动时钟频率
parameter	MCP_FREQ   = 18'd25_000_000;	// MCP的SCK时钟频率

// wire define
wire 		MCP_CLK;						// MCP时钟信号	
wire 		locked;						
wire [15:0] TxData;							// 写入AD的操作数
wire 		TxData_en;						// 开始写入标志位
wire        TxData_done;					// 写入完成标志位			
wire [ 7:0] counter_rem;					// 实时观察写入情况
wire [ 7:0] counter_rem_driver;				// 实时观察驱动情况

//***************************************************
//**** main code ****
//***************************************************

// 例化按键消抖模块
key_debounce u_key0_debounce(
	.clk		(sys_clk),
	.rst_n		(sys_rst_n),
	
	.key		(key0),
	.key_value	(key0_value),		// 按下后为低电平 	输出
	.key_flag	(key0_flag)			// 按下后标志位拉高	输出
	);
	
key_debounce u_key1_debounce(
	.clk		(sys_clk),
	.rst_n		(sys_rst_n),
	
	.key		(key1),
	.key_value	(key1_value),		// 按下后为低电平 	输出
	.key_flag	(key1_flag)			// 按下后标志位拉高	输出
	);	

// AD根据操作数写入16bits模块
AD_Write u_AD_Write(
	.clk		(sys_clk),
	.rst_n		(sys_rst_n),
	
	.TxData		(TxData),			// 写入16位二进制操作数
	.TxData_en	(TxData_en),		// 开始写入标志位 输入
	.TxData_done(TxData_done),		// 写入完成标志位 输出
	.counter_rem(counter_rem),		// 实时检查写入位置
	
	.AD_SCK		(SCK),				// AD操作时钟
	.AD_FSYNC	(AD_FSYNC),			// AD使能标志位
	.AD_SI		(SI)				// AD写入数据
	);
	
// AD驱动模块
AD_Driver u_AD_Driver(
	.clk		(sys_clk),
	.rst_n		(sys_rst_n),
	
	.key0_value	(key0_value),
	.key0_flag	(key0_flag),
	.key1_value	(key1_value),
	.key1_flag	(key1_flag),
	
	.counter_rem_driver	(counter_rem_driver),	// 实时检查驱动位置
	.TxData_done		(TxData_done),			// 写入完成标志位 输入	
	.TxData				(TxData),				// 写入操作数
	.TxData_en			(TxData_en)				// 开始写入标志位 输出
	);

// ILA检测模块
ila_0 u_ila_0(
	.clk	(sys_clk), 					// input wire clk

	.probe0	(AD_FSYNC), 				// input wire [0:0]  probe0  
	.probe1	(SCK), 						// input wire [0:0]  probe1 
	.probe2	(SI), 						// input wire [0:0]  probe2 
	.probe3	(TxData_en), 				// input wire [0:0]  probe3 
	.probe4	(TxData_done), 				// input wire [0:0]  probe4 
	.probe5	(TxData), 					// input wire [15:0] probe5
	.probe6	(counter_rem_driver) 		// input wire [7:0]  probe6
);   
	
endmodule

接下来,我将对函数的主要功能进行介绍。

  • key_debounce
    此函数的主要功能是对外部按键KEY0和KEY1是否按下进行判断,当KEY0按下后,将进行波形变换操作,当KEY1按下后,将进行频率变换操作。后文会有提及。
  • AD_Write
    此函数主要功能是将一个16位的操作字根据一定的时序写入到AD9833中,在写入完成后将TxData_done置位,AD_Driver接收到置位信息后,将后续需要写入的操作字赋值给TxData中,并将开始写入标志位TxData_en置位。
  • AD_Driver
    此函数的主要功能是根据当前状态变更状态字,以及控制AD9833需要输出的波形和频率。
  • ILA
    检测模块ILA用于实时检测。

2.2 按键检测模块

按键检测模块通过延时消抖来检测按键值变化是否有效。

module key_debounce(
	input clk,
	input rst_n,
	input key,				// 外部输入按键值
	output reg key_value,	// 消抖后的按键值
	output reg key_flag		// 消抖后的按键值有效标志
	);

reg [19:0] cnt;
reg key_reg;

// 按键值消抖
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		cnt 	<= 20'd0;
		key_reg <= 1'b1;
	end
	else begin
		key_reg <= key;
		if(key_reg != key) begin	// 检测到按键值变化
			cnt <= 20'd100_0000;	// 开启延时
		end
		else begin
			if(cnt > 20'd0)			// 延时过程
				cnt <= cnt - 1'b1;
			else
				cnt <= 20'd0;
		end
	end
end

// 输出最终值
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		key_value <= 1'b1;			// 没有按下时默认为高电平
		key_flag  <= 1'b0;			// 标志位为无效标志
	end
	// 计时器递减到1时输出按键值
	else if(cnt == 20'd1) begin	
		key_value <= key;			// 将消抖后的数值保存
		key_flag  <= 1'b1;			// 标志位为有效标志
	end
	else begin
		key_value <= key_value;		// 计时器为结束则保持原有数值
		key_flag  <= 1'b0;
	end


end	
	
endmodule

KEY0和KEY1在复位状态下为高电平,当按键按下后变更为低电平。在此模块中,我们用key_reg寄存器来存储上一时刻KEY的状态,当检测到其与当前时刻的KEY状态不一致时进行100_0000个时钟周期的延时,也就是20ns*100_0000=20ms的延时,如果按键状态在该时间内为发生变化说明按下有效。将key_valuekey_flag位变更。

2.3 操作写入模块

此模块,用于将操作字根据一定的时序写入。

module AD_Write(
	input clk,
	input rst_n,
	
	input [15:0] 	TxData,						// 写入16位二进制操作数
	input 			TxData_en,					// 开始写入标志位
	output reg 		TxData_done,				// 写入完成标志位	
	output 			counter_rem,				// 实时检查写入位置
	output reg 		AD_SCK,						// AD操作时钟
	output reg 		AD_FSYNC,					// AD使能标志位
	output reg 		AD_SI						// AD写入数据
    );
	
// reg define
reg [15:0] 	data;								// 写入的16位操作数
reg [ 7:0] 	counter;							// 写入时序		
reg 		tx_en;								// 保存写入标志位

// wire define

	
//***************************************************
//**** main code ****
//***************************************************

assign counter_rem = counter;

// 使能后频数计数器
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		counter 	<= 8'd0;
		TxData_done <= 1'b0;
		tx_en 		<= 1'b0;
	end
	else begin
		// 接收到发送信号
		if(TxData_en) begin
			tx_en <= 1'b1;						// 保存开始写入标志位
		end
		else begin
			if(tx_en) begin						// 数据发送时使能计数器
				if(counter < 8'd33) begin
					counter 	<= counter + 1'b1;
					TxData_done <= 1'b0;
				end else begin					// 在counter计数到完成操作后重置
					counter 	<= 8'd0;
					tx_en 		<= 1'b0;		// 结束发送
					TxData_done <= 1'b1;		// 置位操作完成位
				end
			end	
			else begin
				counter 	<= 8'd0;
				TxData_done <= 1'b0;
				tx_en 		<= tx_en;
			end
		end
	end

end

// Control the AD_SCK, AD_FSYNC, and DAT pins based on the TxData input
// AD在SCK为高电平时改变状态 在SCK下降沿时写入数据
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        AD_SCK 		<= 1;
        AD_FSYNC 	<= 1;
        AD_SI 		<= 0;
    end else begin
		// 开始发送16bits数据
		if(tx_en) begin
			case(counter)
				// 准备工作
				8'd0: begin
					data 		<= TxData;			// 寄存数据
					AD_SCK 		<= 1;				// FSYNC拉低时SCK为高
					AD_FSYNC 	<= 0;				// 使能发送
					AD_SI 		<= 0;				// 默认
				end
				// first 16bits
				8'd1: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[15];			// SI取数据最高位
				end
				8'd2: begin
					AD_SCK 	<= 0;
				end
				8'd3: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[14];			
				end
				8'd4: begin
					AD_SCK 	<= 0;
				end
				8'd5: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[13];			
				end
				8'd6: begin
					AD_SCK 	<= 0;
				end
				8'd7: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[12];			
				end
				8'd8: begin
					AD_SCK 	<= 0;
				end
				8'd9: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[11];			
				end
				8'd10: begin
					AD_SCK 	<= 0;
				end
				8'd11: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[10];			
				end
				8'd12: begin
					AD_SCK 	<= 0;
				end
				8'd13: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[9];			
				end
				8'd14: begin
					AD_SCK 	<= 0;
				end
				8'd15: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[8];			
				end
				8'd16: begin
					AD_SCK 	<= 0;
				end
				8'd17: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[7];			
				end
				8'd18: begin
					AD_SCK 	<= 0;
				end
				8'd19: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[6];			
				end
				8'd20: begin
					AD_SCK 	<= 0;
				end
				8'd21: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[5];			
				end
				8'd22: begin
					AD_SCK 	<= 0;
				end
				8'd23: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[4];			
				end
				8'd24: begin
					AD_SCK 	<= 0;
				end
				8'd25: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[3];			
				end
				8'd26: begin
					AD_SCK 	<= 0;
				end
				8'd27: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[2];			
				end
				8'd28: begin
					AD_SCK 	<= 0;
				end
				8'd29: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[1];			
				end
				8'd30: begin
					AD_SCK 	<= 0;
				end
				8'd31: begin
					AD_SCK 	<= 1;
					AD_SI 	<= data[0];			// SI取数据最低位	
				end
				8'd32: begin
					AD_SCK 	<= 0;				// 写入最后一位数据			
				end
				// the end
				8'd33: begin
					AD_SCK 		<= 1;
					AD_FSYNC 	<= 1;
					AD_SI 		<= 0;
				end
				default: begin
					AD_SCK 		<= 1;
					AD_FSYNC 	<= 1;
					AD_SI 		<= 0;
				end
			endcase
		end
		else begin
			AD_SCK 		<= 1;
			AD_FSYNC 	<= 1;
			AD_SI 		<= 0;
		end
    end
end
	
endmodule

当检测到操作写入信号TxData_en被置位时,将模块内寄存器tx_en置位,用于保存置位信号,因为TxData_en将在一个时钟周期后被拉低。
当置位信号保存完成后,将开始写入操作,counter作为写入的寄存器用于寄存当前写入的位置。当counter为0时,将需要写入的数据保存到data中,并将FSYNC拉低,表示我们将要开始写入操作。注意:在FSYNC拉低时,我们需要确保SCK为高电平,否则FSYNC的拉低无效。
FSYNC拉低后,我们在SCK为高电平时,变更SI为需要写入数据的位次,保持一个时钟周期20ns后,将SCK拉低,AD9833在SCK为下降沿时将SI的数据读入。以此往复直至写入完成的操作字。
在写入完成后,我们需要在10ns-20ns的时间内将FSYNC拉高,否则写入无效。并将TxData_done置位,表示写入完成了,可以进行后续操作。

2.4 驱动操作模块

驱动模块,主要对写入的操作字进行变更,通过选择合适的波形和频率控制AD9833输出。

module AD_Driver(
	input 	clk,
	input 	rst_n,
	
	input  	key0_value,
	input  	key0_flag,
	input  	key1_value,
	input  	key1_flag,
	
	output 	   [ 7:0] 	counter_rem_driver,
	
	input 				TxData_done,	// 写入完成标志位 	输入	
	output reg [15:0] 	TxData,			// 写入操作数 		输出
	output reg 			TxData_en		// 开始写入标志位 	输出
    );
	
// local parameter define
localparam [15:0] reset_add 		= 16'h0100;
localparam [15:0] write_freq 		= 16'h2100;
// 为了避免在AD9833被初始化时出现虚假的DAC输出
// 复位位应该被设置为1 直到部件准备好开始生成输出为止

// fMck=25Mhz
// (fMclk/2^28/fwanted)^(-1)=FREQREG; fwanted(min)=0.0931, fwanted(max)=25M
localparam [15:0] write_freq_lsb_3M = 16'h70b7;		// 输出3M信号
localparam [15:0] write_freq_msb_3M = 16'h47ae;
localparam [15:0] write_freq_lsb_1M = 16'h6592;		// 输出1M信号
localparam [15:0] write_freq_msb_1M = 16'h428f;
localparam [15:0] write_freq_lsb_1k = 16'h69f1;		// 输出1k信号
localparam [15:0] write_freq_msb_1k = 16'h4000;
localparam [15:0] write_phase_c0 	= 16'hc000;		// 相位配置

localparam [15:0] write_sin 		= 16'h2000;		// 正弦波
localparam [15:0] write_traingle 	= 16'h2002;		// 三角波
localparam [15:0] write_square  	= 16'h2028;		// 方波

// reg define
reg [ 7:0] counter;				// 写入操作开始后的位置
reg [31:0] delay_counter;		// 开始写入操作前的延迟	
reg 	   delay_start;			// 延迟开始标志位
reg        delay_end;			// 延迟结束标志位

reg [ 2:0] waveform_select;		// 选择波形
reg [ 2:0] freq_select;			// 选择频率
reg [15:0] write_waveform;		// 写入的波形类型
reg [15:0] write_freq_lsb_c0;	// 写入频率数据的低位
reg [15:0] write_freq_msb_c0;	// 写入频率数据的高位

// wire define

//***************************************************
//**** main code ****
//***************************************************

assign counter_rem_driver = counter;

// 开始延迟计数器
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		delay_counter 	<= 32'b0;
		delay_start 	<= 1'b1;
		delay_end 		<= 1'b0;
		waveform_select <= 2'd0;
		freq_select 	<= 2'd0;
	end
	else begin
		// 初始化和波形频率选择后根据delay_start开启延迟
		if(delay_start) begin
			if(delay_counter < 10'd1000) begin
				delay_counter = delay_counter + 1'b1;
			end
			else begin
				delay_counter 	<= 32'b0;
				delay_start 	<= 1'b0;
				// 将delay_end作为延迟结束的标志 置1只存在一个周期
				delay_end 		<= 1'b1;
			end
		end
		// 根据按键调整波形和频率
		else begin
			// key0按下调整输出波形
			if((key0_flag == 1) && (key0_value == 0)) begin
				waveform_select <= waveform_select + 1'd1;
				delay_start 	<= 1'b1;
				delay_counter 	<= 32'b0;
				delay_end 		<= 1'b0;
			end
			// key1按下调整输出频率
			else if((key1_flag == 1) && (key1_value == 0)) begin
				freq_select 	<= freq_select + 1'd1;
				delay_start 	<= 1'b1;
				delay_counter 	<= 32'b0;
				delay_end 		<= 1'b0;
			end
			else begin
			// default波形和频率不变化
				waveform_select <= waveform_select;
				freq_select 	<= freq_select;
				delay_counter 	<= 32'b0;
				delay_end 		<= 1'b0;
			end
		end
		
	end
end

// 切换波形种类
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		write_waveform <= write_sin;
	end
	else begin
		case(waveform_select) 
			2'd0: begin
				write_waveform <= write_sin;
			end
			2'd1: begin
				write_waveform <= write_traingle;
			end
			2'd2: begin
				write_waveform <= write_square;
			end
			default: begin
				write_waveform <= write_sin;
			end
		endcase
	end
end

// 切换频率
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		write_freq_lsb_c0 <= write_freq_lsb_1M;
		write_freq_msb_c0 <= write_freq_msb_1M;
	end
	else begin
		case(freq_select)
			2'd0: begin
				write_freq_lsb_c0 <= write_freq_lsb_1M;
				write_freq_msb_c0 <= write_freq_msb_1M;
			end
			2'd1: begin
				write_freq_lsb_c0 <= write_freq_lsb_3M;
				write_freq_msb_c0 <= write_freq_msb_3M;			
			end
			2'd2: begin
				write_freq_lsb_c0 <= write_freq_lsb_1k;
				write_freq_msb_c0 <= write_freq_msb_1k;			
			end
			default: begin
				write_freq_lsb_c0 <= write_freq_lsb_1M;
				write_freq_msb_c0 <= write_freq_msb_1M;			
			end
		endcase
	end
end

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		TxData 		<= 16'd0;
		TxData_en 	<= 1'b0;
		counter 	<= 8'd12;
	end
	else begin
		// delay_end被置位后 将counter的值归零 开始写入操作数
		// 否则写入操作会在结束处不断循环
		if(delay_end) begin 			
			counter <= 8'd0;
		end
		else begin
			case(counter) 
				// 重置寄存器
				8'd0: begin
					TxData 		<= reset_add;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd1: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 选择数据一次写入
				8'd2: begin
					TxData 		<= write_freq;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd3: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 写入频率寄存器低14位
				8'd4: begin
					TxData 		<= write_freq_lsb_c0;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd5: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 写入频率寄存器高14位
				8'd6: begin
					TxData 		<= write_freq_msb_c0;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd7: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 写入相位寄存器12位
				// 不确定相位寄存器是否同时写入 在图例中相位寄存器无任何写入的使能标志位
				8'd8: begin
					TxData 		<= write_phase_c0;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd9: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 选择波形输出器
				8'd10: begin
					TxData 		<= write_waveform;
					TxData_en 	<= 1'b1;
					counter 	<= counter + 1'b1;
				end
				8'd11: begin
					TxData 		<= TxData;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
					if(TxData_done) begin
						counter <= counter + 1'b1;
					end
				end
				// 结束 将使能位TxData_en拉低
				8'd12: begin
					TxData 		<= 16'd0;
					TxData_en 	<= 1'b0;
					counter 	<= counter;
				end
				default: begin
					counter 	<= counter;
				end
			endcase
			
		end
	end
end

endmodule

FPGA重置后,delay_start置位,经过一定的延时后,开始初始化写入1Mhz的正弦波信号。写入的过程如下:

  1. 重置寄存器
  2. 选择数据一次写入
  3. 写入频率寄存器低14位
  4. 写入频率寄存器高14位
  5. 写入相位寄存器12位
  6. 选择波形输出
  7. 循环等待按键按下重置上述流程

当检测到按键按下时,即key_flagkey_value变化时,波形选择寄存器和频率选择寄存器会发生变化。以此来选择正弦波、三角波、方波和1M、3M和1K的频率信号。

三、结果展示

FPGA引脚输出。

图5 FPGA引脚输出

图5是FPGA的引脚输出状态。可以看到FSYNC为低电平时开始写入操作,TxData为写入操作字,SI逐一读取写入位,跟随着SCK的变化写入。

3M的正弦波。
AD9833输出图

图6 AD9833输出图 3M正弦波

可以观察到周期是333ns,也就是3M,输出频率很精准。另外是436mV的峰峰值(经过外部运放放大后的,放大倍数为6.1倍),不超过手册中的650mV。

1K的正弦波。

图7 AD9833输出图 1K正弦波

其余波形和频率,博主忘记拍了=.=,但确实完整输出了三角波和方波(只要按一下按键就行)。就不重新连了,有点懒。
最后的最后,展示一下AD9833在原理图中的连接图。

图8 AD9833原理图

总结

本文,首先分析了AD9833的芯片手册,根据其芯片手册的内容,我们用Verilog语言编写了控制程序,并烧录至FPGA中用于控制AD9833。最后得到不同波形和频率的输出结果。

版权声明:本文为博主作者:Authony.原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/weixin_65191557/article/details/135332995

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2024年4月10日
下一篇 2024年4月10日

相关推荐