赞
踩
硬件: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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。