项目介绍:
本设计通过FPGA结合高速DAC实现了DDS任意波形发生器,实现波形可调、频率可调、幅值可调、OLED显示、按键控制、编码器调节等功能。
本设计主要实现了以下功能:
-
波形类型可调(正弦波、锯齿波、三角波、方波)。
-
频率可调0-20MHz,最低调节精度1Hz,可选调节累加值。
-
幅度可调0-1V,累加值为0.1v。
-
通过OLED显示当前输出波形的类型、频率、幅度;显示频率调节累加值;显示当前可调模式。
-
通过按键实现三种调节模式切换,通过旋转编码器调节波形、频率、频率累加值、幅度。
使用说明:
通过按键K1控制模式切换(调节波形、频率、幅值),OLED的*用于指示当前选择的调节模式。
OLED第一行显示波形类型,第二行显示幅值,第三行显示频率,第四行显示频率调节累加值。
通过旋转编码器左右转实现波形切换、幅值改变(每次0.1V)、频率改变。
当处在修改频率模式时,可通过按下旋转编码器修改频率累加值(ACC),可按照1Hz、10Hz、100Hz、1KHZ、10KHZ、100KHZ、1MHZ修改频率。
硬件介绍:
-
FPGA:Lattice LCMXO2-4000HC-4MG132
-
基于3Peaks的3PD5651制作的高速DAC模块
-
OLED显示屏168*64,SPI三线通信
-
按键和旋转编码器
设计思路:
本次设计采用了自上而下的设计方法,首先定义顶层模块功能,然后将需要实现的功能设计成多个子模块,分别对多个子模块进行设计验证,验证无误后将子模块汇总,最终实现整个设计。
其中时钟模块通过IP核生成,输出120MHz时钟。
按键消抖和编码器驱动模块移植官方参考例程,未作修改。
DDS生成和OLED显示模块基于官方例程进行了修改。
参数控制模块独立编写。
资源使用情况:
Design Summary
Number of registers: 551 out of 4635 (12%)
PFU registers: 551 out of 4320 (13%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1225 out of 2160 (57%)
SLICEs as Logic/ROM: 1225 out of 2160 (57%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 500 out of 2160 (23%)
Number of LUT4s: 2441 out of 4320 (57%)
Number used as logic LUTs: 1441
Number used as distributed RAM: 0
Number used as ripple logic: 1000
Number used as shift registers: 0
Number of PIO sites used: 39 + 4(JTAG) out of 105 (41%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 1 out of 2 (50%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)
主要代码片段:
1.DDS参数控制模块
module dds_ctrl
(
input clk, //时钟
input rst, //复位
input Left_pulse, //旋转编码器
input Right_pulse,//旋转编码器
input OK_pulse, //旋转编码器
input key_pulse, //按键
output reg [1:0] fun_sel, //功能选择,0调类型,1调幅值,2调频率,3调原始波形
output reg [1:0] wave_sel, //波形类型
output reg [2:0] pulse_cnt, //频率调节步进
output reg [31:0] phase_fo, //频率
output reg [3:0] amp_sel, //幅度可调范围 0.1-1
output reg raw_sel //调节波形输出,0:经调幅之后的波形 ,1:原始波形
);
localparam WAVE = 2'h0, AMP = 2'h1, FRE = 2'h2,SEL = 2'h3;
always @(posedge key_pulse or negedge rst )//功能选择
begin
if (!rst)
begin
fun_sel <= 2'd0;
end
else if(fun_sel>=4)
begin
fun_sel <= 2'd0;
end
else
begin
fun_sel <= fun_sel + 2'd1;
end
end
reg [19:0] phase_add [6:0]; //频率调节范围
initial
begin // 调节宽度
phase_add[0] = 20'd1; //1Hz
phase_add[1] = 20'd10; //10Hz
phase_add[2] = 20'd100; //100Hz
phase_add[3] = 20'd1_000; //1KHZ
phase_add[4] = 20'd10_000; //10KHZ
phase_add[5] = 20'd100_000; //100KHZ
phase_add[6] = 20'd1_000_000; //1MHZ
end
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
phase_fo <= 32'd5;
pulse_cnt<= 3'd0;
wave_sel <= 2'd0;
amp_sel <= 3'd5;
raw_sel <= 1'd1;
end
else
begin
case (fun_sel)
WAVE: begin //调节波形
if(Right_pulse)
wave_sel <= wave_sel + 1'b1;
else if(Left_pulse)
wave_sel <= wave_sel - 1'b1;
else
wave_sel <= wave_sel;
end
AMP: begin//调节幅值
if(Right_pulse&&(amp_sel<4'd10)) //限制幅度调节范围
amp_sel <= amp_sel + 1'b1;
else if(Left_pulse&&(amp_sel>4'd1)) //限制幅度调节范围
amp_sel <= amp_sel - 1'b1;
else
amp_sel <= amp_sel;
end
FRE: begin//调节频率
if(Right_pulse)
begin
if(phase_fo>=32'd20_000_000) phase_fo<=32'd20_000_000;
//限制频率调节范围
else phase_fo <= phase_fo + phase_add[pulse_cnt];
end
else if(Left_pulse&&(phase_fo>phase_add[pulse_cnt]))
//限制频率调节范围,防止过零
begin
phase_fo <= phase_fo - phase_add[pulse_cnt];
end
else if(OK_pulse)
begin
pulse_cnt <= pulse_cnt + 3'd1;
if(pulse_cnt>=3'd7)pulse_cnt <= 3'd0;
end
else
begin
phase_fo <= phase_fo;
end
end
SEL:begin
if(Right_pulse)
raw_sel <= ~raw_sel;
else if(Left_pulse)
raw_sel <= ~raw_sel;
else
raw_sel <= raw_sel;
end
default: begin
if(Right_pulse)
wave_sel <= wave_sel + 1'b1;
else if(Left_pulse)
wave_sel <= wave_sel - 1'b1;
else
wave_sel <= wave_sel;
end
endcase
end
end
endmodule
2.DDS生成模块-频率控制原理
通过修改累加值控制实际波形输出频率
phase_fo
即为实际输出的波形频率,phase_fo
已知求phase_m
公式为:
m = fo*232/fc
其中fc
为时钟频率120MHz
,因此公式可以简化为:
m = fo*35.791394125
在FPGA中进行浮点运算会出现问题,此处使用右移的方式来实现,35.791394125
转化为二进制位100011.11001010100110001101
/*dds_wave中 主要的修改部分,实现频率调节*/
reg [31:0] phase_acc; //32位相位累加器 频率可调范围:0.028Hz ~ 20MHz
reg [31:0] phase_m; //频率累加值
//reg [31:0] phase_fo = 2; //可调频率范围0-20Mhz
always @(posedge clk_120m)
begin
//phase_m <= phase_fo * 35.791394125;
phase_m <= (phase_fo<<5)+(phase_fo<<1)+(phase_fo)+(phase_fo>> 1)
+(phase_fo>> 1)+(phase_fo>> 2)+(phase_fo>> 5)+(phase_fo>> 7)+(phase_fo>> 9)
+(phase_fo>> 12)+(phase_fo>> 13)+(phase_fo>> 17)+(phase_fo>> 18)+(phase_fo>> 20); //fo = (m*fc) / (2^32)// 35.791394125 //100011.11001010100110001101
phase_acc <= phase_acc + phase_m; //m = fo/fc * 2^32
end
3.DDS生成模块-幅度控制原理
此处用于调幅和控制波形输出为原始波形还是调幅波形,已知波形输出原始幅值为2.7V
左右,结合公式:
OUT = 2.7*X/K
需要控制OUT
输出范围为0.1-1V
,X
为输入,K
为固定系数,可以设定X
输入范围为1-10
,当X=1
时,OUT=0.1V
,X=10
时OUT=1V
,则K=1/27=0.037109375
//控制波形输出:0经调幅之后的波形 ,1原始波形
always @(*) begin
case(raw_sel)
1'b0: dac_data = amp_sel*((dac_dat>>5)+(dac_dat>>8)+(dac_dat>>9));//*0.037109375 二进制 0.000010011
1'b1: dac_data = dac_dat;
default: dac_data = dac_dat;
endcase
end
4.OLED显示模块
OLED主要修改部分如下,8421BCD码提取频率和幅值的值。通过case进行判断,然后进行字符串拼接。
//显示波形类型
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
type_buff <= "Type:Sin";
end else begin
case(wave_sel)
2'b00: type_buff <= "Type:Sin";
2'b01: type_buff <= "Type:Saw";
2'b10: type_buff <= "Type:Trg";
2'b11: type_buff <= "Type:Squ";
endcase
end
end
//显示输出频率
wire [3:0]fre_unit,fre_ten,fre_hun,fre_tho,fre_t_tho,fre_h_hun,fre_m,fre_mm;
bcd_8421 u1_bcd_8421
(
.sys_clk (clk),
.sys_rst_n (rst_n),
.data (phase_fo),
.unit (fre_unit), //个位
.ten (fre_ten), //十位
.hun (fre_hun), //百位
.tho (fre_tho), //千位
.t_tho (fre_t_tho),//万位
.h_hun (fre_h_hun) //十万
);
bcd_8421 u2_bcd_8421
(
.sys_clk (clk),
.sys_rst_n (rst_n),
.data (phase_fo/1000_000),
.unit (fre_m), //百万位
.ten (fre_mm) //千万位
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
fre_buff <= "Fre: 1Hz";
end else begin
if(phase_fo<32'd1000)
fre_buff <= {"Fre:",4'd0,fre_hun,4'd0,fre_ten,4'd0,fre_unit,"Hz "};
else if(phase_fo>32'd1_000_000)
fre_buff <= {"Fre:",4'd0,fre_mm,4'd0,fre_m,"MHz "};//此处显示有bug
else fre_buff <= {"Fre:",4'd0,fre_h_hun,4'd0,fre_t_tho,4'd0,fre_tho,"KHz"};
end
end
//显示输出幅度峰峰值vpp
wire [3:0]amp_unit,amp_ten;
bcd_8421 u3_bcd_8421
(
.sys_clk (clk),
.sys_rst_n (rst_n),
.data (amp_sel),
.unit (amp_unit), //个位
.ten (amp_ten) //十位
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
amp_buff <= "Amp:0.5v";
end else begin
amp_buff <= {"Amp:",4'd0,amp_ten,".",4'd0,amp_unit,"v"};
end
end
//显示频率调节步进值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_buff <= "Acc: 1Hz";
end else begin
case(pulse_cnt)
5'd0: cnt_buff <= "Acc:1Hz ";
5'd1: cnt_buff <= "Acc:10Hz ";
5'd2: cnt_buff <= "Acc:100Hz";
5'd3: cnt_buff <= "Acc:1KHz ";
5'd4: cnt_buff <= "Acc:10KHz";
5'd5: cnt_buff <= "Acc:100K ";
5'd6: cnt_buff <= "Acc:1MHz ";
endcase
end
end
/*此处省略官方案例代码,以下仅列出修改的代码片段*/
5'd13: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8; char <= type_buff;state <= SCAN; end
5'd14: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd8; char <= amp_buff;state <= SCAN; end
5'd15: begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd10; char <= fre_buff;state <= SCAN; end
5'd16: begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd9; char <= cnt_buff;state <= SCAN; end
5'd17 : begin y_p <= 8'hb0; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1; char <= " ";state <= SCAN; end
5'd18: begin y_p <= 8'hb2; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1; char <= " ";state <= SCAN; end
5'd19: begin y_p <= 8'hb4; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1; char <= " ";state <= SCAN; end
5'd20: begin y_p <= {4'hb,(fun_sel<<1)}; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1; char <= "*";state <= SCAN; end
5'd21: begin y_p <= 8'hb6; x_ph <= 8'h16; x_pl <= 8'h04; num <= 5'd1; char <= " ";state <= SCAN; end
default: state <= IDLE; //如果你需要动态刷新一些信息,此行应该取消注释
实验结果:
1.四种类型波形
2.不同频率的正弦波信号
3.实物图片
设计心得:
第一次使用FPGA完成的项目,本次项目耗时大约一周的时间。因为具有STM32的软硬件开发经验,Verilog语言从语法规则上和C语言十分相似,所以使用Verilog编写代码上手很快。但也带来一些坏处,总是跳不出用软件编程的思想编写Verilog代码。
在整个设计过程中主要出现了以下几个问题:
1.提示旋转编码器三个输入接口未连接问题
ERROR - Port 'encoder_a' is unconnected.
问题分析:没有在顶层指定dds_ctrl子模块输出位宽,导致生成的硬件链路出现问题。
解决方法:顶层模块调用子模块时一定要声明输出数据的位宽,否则默认当作1bit。
2.DAC模块CLK时钟短路
问题分析:使用探针测量CLK引脚时不小心将焊盘附近的绝缘层划开,铜箔露出导致CLK与GND短路。
解决方法:使用烙铁将露出铜箔稍微刮几下。
3.训练板DAC输出引脚无信号输出
问题分析:刚开始测量时习惯将示波器探针插到Aout的中间位置,次数多了导致导线断路。
解决方法:从DAC模块输出引脚飞线。
以上问题解决起来都很简单,但是找到问题所在需要不断试错。当代码找不出bug但是还是没有输出时就不要死磕软件了,看看硬件是不是出现了什么问题。
本次设计还有几个问题没有解决:
-
OLED显示问题,调节频率到MHz以上时OLED显示的频率出现bug。
-
波形失真问题,当进行调幅时输出的波形有明显的失真。频率调整到10MHz左右,不进行调幅也会失真(此处提高PLL频率应该可以解决)。调幅失真还没有找到好的解决办法。
分享一个简单的脚本,实现自动将.jed
文件复制到小脚丫开发板,能自动化解决的事情就绝不自己动手。注意:需要修改磁盘编号
copy *.jed K:\
pause
::https://zhidao.baidu.com/question/513072753.html?qbl=relate_question_3&word=%C5%FA%B4%A6%C0%ED%CE%C4%BC%FE%D0%DE%B8%C4%CA%B1%BC%E4
链接:https://pan.baidu.com/s/1cIHFOxkQ9hn8O0rkTIdSYw?pwd=ck80
提取码:ck80