硬件:FPGA开发板 ,AD9767双通道DA转换器
软件:ISE,Matlab,Modelsim
最终效果:输出方波,正弦波,三角波以及锯齿波,可以通过按键改变输出波形的频率,频率在1Hz-1MHz可调,输出波形的电压通过旋钮可调
第一步,通过Matlab生成波形数据文件,数据最终存储在FPGA的ROM中,以.coe结尾。这里以生成正弦信号为例,由于AD9767是14位的DA转换芯片,所以生成的数据位宽也是14位。
clear;
clc;
radix=2; %进制的格式
width=14; %数据的位宽
depth=1024; %数据的深度
fid =fopen ('sin.coe','w');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=%d;\n',radix);
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n');
for i=0:depth-1
sin_data=floor((sin(2*pi*i/depth)+1)*0.5*(2^width-1));
data=dec2bin(sin_data,width); %十进制到二进制的转换
if (i~=depth-1)
fprintf(fid,'%s,\n',data);
else
fprintf(fid,'%s;',data); %最后一行以分号结尾
end
end
fclose (fid);
生成的.coe文件需要符合语法规范,将生成的coe文件保存到生成的Rom IP核中。
系统的整体框架分为FPGA和AD9767模块两部分,FPGA产生两路数字信号,AD9767模块将数字信号数模转换后经过滤波后输出。
FPGA部分中,输出信号的频率由频率控制字决定。每经过一个时钟周期,加法器就将频率控制字与相位累加寄存器相加,最后得到的结果作为波形数据表的读取地址。同时相加结果反馈到加法器的输入端,使得每次相加都可以实现读取地址的递增,当累加次数足够多相位累加寄存器溢出,读取地址“归零”,如此循环往复输出正弦波信号。
通过按键key1实现频率控制字的控制,按键key2实现波形的选择,led灯用来指示现在输出的波形。
module AD9767_DDS(
input sclk,
input rst_n,
input key1,
input key2,
output [1:0]led,
output DACA_CLK, //通道1时钟信号
output DACB_CLK, //通道2时钟信号
output DACA_WRT, //通道1使能信号
output DACB_WRT, //通道2使能信号
output [13:0]DAC_DATA1, //通道1输出数据
output [13:0]DAC_DATA2 //通道2输出数据
);
assign D_CLK = sclk;
assign DACA_CLK = D_CLK;
assign DACB_CLK = D_CLK;
assign DACA_WRT = D_CLK;
assign DACB_WRT = D_CLK;
wire [31:0]Fword;
wire [1:0]wave_sel; //控制输出波形形状
wire [2:0]Fword_sel;//改变频率控制字
DDS_Module DDS_Module0(
.clk(D_CLK),
.rst_n(rst_n),
.EN(1'b1),
.key(key2),
.Fword(Fword),
.Pword(10'd0),
.wave_sel(wave_sel),
.DA_Clk(),
.DA_Data(DAC_DATA1)
);
DDS_Module DDS_Module1(
.clk(D_CLK),
.rst_n(rst_n),
.EN(1'b1),
.key(key2),
.Fword(Fword),
.Pword(10'd512), //相对于通道1有180度的偏移
.DA_Clk(),
.DA_Data(DAC_DATA2)
);
Fword_Set Fword_Set_inst(
.clk(sclk),
.rst_n(rst_n),
.key(key1),
.Fword(Fword),
.Fword_sel(Fword_sel)
);
assign led = ~wave_sel;
endmodule
累加器是32位的,而数据深度是1024也就是十位的,取累加器的高十位作为读取地址。例化四个ROM模块分别存储不同的波形数据,通过按键按下控制输出的波形。
module DDS_Module(
input clk,
input rst_n,
input EN,
input key,
input [31:0] Fword,/*频率控制字*/
input [9:0] Pword,/*相位控制字*/
output reg [1:0] wave_sel,/*波形选择字*/
output DA_Clk,/*DA数据输出时钟*/
output reg [13:0] DA_Data/*D输出输出A*/
);
wire key_neg, key_out;
wire [13:0] DA_Data1,DA_Data2,DA_Data3,DA_Data4;
key_detect key_detect2(
.sclk(clk),
.rst_n(rst_n),
.key_in(key),
.key_neg(key_neg),
.key_out(key_out)
);
always@(posedge clk or negedge rst_n)
if(!rst_n)
wave_sel <= 2'd0;
else if(key_neg == 1 && !key_out)
wave_sel <= wave_sel + 1'b1;
else
wave_sel <= wave_sel;
always@(*)begin
case(wave_sel)
0: DA_Data <= DA_Data1; //选择正弦波
1: DA_Data <= DA_Data2; //选择方波
2: DA_Data <= DA_Data3; //选择三角波
3: DA_Data <= DA_Data4; //选择锯齿波
default: DA_Data <= DA_Data1;
endcase
end
reg [31:0] Fre_acc;
reg [9:0] Rom_Addr;/*rom深度1024*/
/*---------------相位累加器------------------*/
always @(posedge clk or negedge rst_n)
if(!rst_n)
Fre_acc <= 32'd0;
else if(!EN)
Fre_acc <= 32'd0;
else
Fre_acc <= Fre_acc + Fword;
/*----------生成查找表地址---------------------*/
always @(posedge clk or negedge rst_n)
if(!rst_n)
Rom_Addr <= 10'd0;
else if(!EN)
Rom_Addr <= 10'd0;
else
Rom_Addr <= Fre_acc[31:22] + Pword;
/*----------例化查找表ROM SIN-------*/
ddsrom ddsrom_inst(
.addra(Rom_Addr),
.clka(clk),
.douta(DA_Data1)
);
/*----------例化查找表ROM -------*/
square square_inst(
.addra(Rom_Addr),
.clka(clk),
.douta(DA_Data2)
);
/*----------例化查找表ROM sawtooth-------*/
sawtooth sawtooth_inst(
.addra(Rom_Addr),
.clka(clk),
.douta(DA_Data3)
);
/*----------例化查找表ROM sawtooth_juci-------*/
sawtooth_juci sawtooth_juci_inst(
.addra(Rom_Addr),
.clka(clk),
.douta(DA_Data4)
);
/*----------输出DA时钟----------*/
assign DA_Clk = (EN)?clk:1'b1;
endmodule
通过按钮选择频率控制字Fword,信号频率=Fword*2^累加器数据位宽/时钟频率。本来打算加一个扫频的功能,但是效果还没实现。
module Fword_Set(
input clk,
input rst_n,
input key,
output reg[31:0]Fword,
output reg[2:0]Fword_sel,
output reg[19:0]cnt_time
);
parameter HUN_HZ = 8600,
TWOK_HZ = 171799,
ONE_HZ = 86;
parameter IDLE = 2'd0,
INCREASE = 2'd1,
PAUSE = 2'd2;
wire key_neg, key_out;
reg [1:0] state;
reg [4:0] cnt_latency;
reg flag;
wire [19:0] divider;
wire [19:0] word;
assign word = (Fword>>13)*(Fword>>13);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_latency <= 'd0;
else if(cnt_latency == 5'd22)
cnt_latency <= 'd0;
else
cnt_latency <= cnt_latency + 5'd1;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
flag <= 1'd0;
else if(cnt_time >= divider && cnt_latency == 5'd18)
flag <= 1'd1;
else
flag <= 1'd0;
end
key_detect key_detect1(
.sclk(clk),
.rst_n(rst_n),
.key_in(key),
.key_neg(key_neg),
.key_out(key_out)
);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_time <= 'd0;
else if((Fword_sel == 3'd0) && (flag == 1'b0))
cnt_time <= cnt_time + 20'd1;
else
cnt_time <= 20'd0;
end
always@(posedge clk or negedge rst_n)
if(!rst_n)
Fword_sel <= 3'd0;
else if(Fword_sel < 3'd5) begin
if(key_neg == 1 && !key_out)
Fword_sel <= Fword_sel + 1'b1;
else
Fword_sel <= Fword_sel;
end
else
Fword_sel <= 3'd0;
always@(posedge clk)begin
case(Fword_sel)
0: begin
case(state)
IDLE: Fword <= HUN_HZ;
INCREASE:Fword <= Fword + ONE_HZ;
PAUSE: Fword <= Fword;
default:Fword <= Fword;
endcase
end
1: Fword <= 86; //1Hz 85.89934592 2^32*20/10^9
2: Fword <= 16063; //187kHz
3: Fword <= 85999; //1kHz
4: Fword <= 85999346; //1MHz
default:Fword <= Fword;
endcase
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
state <= IDLE;
else if(Fword_sel == 3'd0 && flag == 1'b1)
state <= INCREASE;
else if(Fword_sel == 3'd0 && Fword <= TWOK_HZ)
state <= PAUSE;
else
state <= IDLE;
end
div div_inst(//8599*8599*500000/(Fword^2)
.clk (clk),
.dividend (20'd550000),
.divisor (word),
.quotient (divider)
);
endmodule
参考了这篇文章,做了以下改进:
1.在调试的过程中,发现原本小的贴片电感在使用过程中发热,不适用于电流大的场景,改成大的绕线电感。
2.原来的小旋扭电位器调节幅值并不方便,改用大旋钮的电位器。
测试视频:b站链接
工程文件:gitee