赞
踩
本文利用FPGA控制AD9833,实现信号发生器的功能。本文将对AD9833的手册进行详细的解读,并对其配置方法进行解析,最后在Verilog中进行编码,将代码烧录置FPGA中,FPGA通过外部引脚控制AD9833输出所需要的正弦波、方波和三角波。三种波形能够输出的频率范围为0~12.5Mhz。
AD9833是一种低功率可编程波形发生器,能够产生正弦、三角形和方波输出。在各种类型的传感、驱动和时域反射测量应用中,都需要产生波形。输出频率和相位是软件可编程的,允许易于调整。不需要任何外部组件。频率寄存器为28位;以25MHz的时钟速率,可以达到0.1 Hz的分辨率。类似地,使用1MHz的时钟速率,AD9833可以被调谐到0.004 Hz的分辨率。
AD9833是通过三线串行接口写入的。该串行接口以时钟速率高达40MHz,并与DSP和微控制器标准兼容。该设备的电源供电范围为2.3V至5.5V。
博主将首先分析AD9833的数据手册,并列出博主认为手册中需要着重注意的点,以便于后续代码的提出。
在AD9833的规格中,我们可以知道,其
V
o
u
t
V_{out}
Vout 也就是最大输出电压可以达到0.65V,其具体的表达式如下:
V
O
U
T
=
V
R
E
F
×
18
×
R
LOAD
/
R
S
E
T
×
(
1
+
(
S
I
N
(
2
π
(
F
R
E
Q
R
E
G
×
f
M
C
L
K
×
t
/
2
28
+
P
H
A
S
E
R
E
G
/
2
12
)
)
)
)
V_{\mathsf{OUT}}=V_{\mathsf{REF}}\times18\times\mathsf{R}_\text{LOAD}/{ \mathsf{R}_{\mathsf{SET}}}\times(1+(\mathsf{SIN}(2\pi(\mathsf{FREQREG}\times\mathbf{f}_{\mathsf{MCLK}}\times\mathbf{t}_{/2}28+\mathsf{PHASEREG}_{/2}12))))
VOUT=VREF×18×RLOAD/RSET×(1+(SIN(2π(FREQREG×fMCLK×t/228+PHASEREG/212))))
即:输出电压的大小跟参考电压和负载电阻密切相关,同时随着设置波形频率的变化而变化。
另外,AD9833的VDD的范围为2.3V-5.5V,也就是说你需要提供一定数值的电源电压。
在手册中,我们可以看到AD9833的时钟图如图1所示:
在图1中,我们需要着重关注的是AD9833的MCLK周期最小为40ns,也就是说你需要提供25Mhz的激励时钟信号,用于驱动AD9833进行内部数据操作。
观察图2,我们可知:如何需要写入AD9833的控制字,我们首先需要将FSYNC拉低,在SCLK操作时钟的周期性变化下,将SDATA也就是操作字逐一写入。需要注意的是,SCLK的操作时钟周期最小需要25ns,高低电平的维持时间最小需要10ns,在最后一个下降沿数据写入后需要在10ns-20ns
的时间内将FSYNC的电平拉高,否则写入无效。
AD9833的引脚图如图3所示:
在AD9833的引脚中,MCLK为激励时钟,我们采用25Mhz;FSYNC在拉低后进行写入操作,随着SCLK的电平变化将SDATA的值读入。另外,VDD和AGND之间应连接一个0.1uF和一个10uF的解耦电容。
要想讲清楚AD9833的控制方式,我们需要重点观察其操作字是如何定义的。
在上述的16个控制位中,博主将讲解需要着重观察的控制位,其他控制位请读者自行查看手册。
接下来我们将利用代码来详细论述操作字的写入流程。
我们将利用Verilog语言编写FPGA的代码,并用FPGA控制AD9833的SCLK、SDATA、FSYNC引脚,进行写入操作。
主函数中主要包含了各个模块,并将模块之间的变量联系起来。
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
接下来,我将对函数的主要功能进行介绍。
TxData_done
置位,AD_Driver接收到置位信息后,将后续需要写入的操作字赋值给TxData
中,并将开始写入标志位TxData_en
置位。按键检测模块通过延时消抖来检测按键值变化是否有效。
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_value
和key_flag
位变更。
此模块,用于将操作字根据一定的时序写入。
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
置位,表示写入完成了,可以进行后续操作。
驱动模块,主要对写入的操作字进行变更,通过选择合适的波形和频率控制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的正弦波信号。写入的过程如下:
当检测到按键按下时,即key_flag
和key_value
变化时,波形选择寄存器和频率选择寄存器会发生变化。以此来选择正弦波、三角波、方波和1M、3M和1K的频率信号。
FPGA引脚输出。
图5是FPGA的引脚输出状态。可以看到FSYNC
为低电平时开始写入操作,TxData
为写入操作字,SI
逐一读取写入位,跟随着SCK
的变化写入。
3M的正弦波。
可以观察到周期是333ns,也就是3M,输出频率很精准。另外是436mV的峰峰值(经过外部运放放大后的,放大倍数为6.1倍),不超过手册中的650mV。
1K的正弦波。
其余波形和频率,博主忘记拍了=.=
,但确实完整输出了三角波和方波(只要按一下按键就行)。就不重新连了,有点懒。
最后的最后,展示一下AD9833在原理图中的连接图。
本文,首先分析了AD9833的芯片手册,根据其芯片手册的内容,我们用Verilog语言编写了控制程序,并烧录至FPGA中用于控制AD9833。最后得到不同波形和频率的输出结果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。