一、需求分析
-
通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
- 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
- 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
- 在OLED上显示当前波形的形状、波形的频率以及幅度
- 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
二、设计思路
首先由控制模块来统一控制所需要的所有按键,并赋予其相应的功能。
数据产生模块受控于控制模块,实现数据的产生,并对产生的数据进行增减操作。
OLED显示模块将产生的数据显示在屏幕上,方便人的观察及使用。
波形产生模块使用产生的数据来从ROM中读取对应的波形文件,再将读取到的数据输出到DAC芯片中,就完成的波形的生成。
这里只是简单的对该信号发生器做了基本的介绍,关于DDS波形发生器的原理在电子森林中有及其详细的解释。
三、模块介绍
1.时钟分频模块
该分频是为了给按键消抖模块提供一个远小于主频的时钟用以消抖,所以采用了最简单的计数器分频法。此处仅给出关键代码。
reg [24:0] cnt;
reg clk_reg;
assign clk_o = clk_reg;
always@(posedge clk or negedge rst)
begin
if(~rst) begin cnt <= 25'd0; clk_reg <= 0; end
else
if(cnt<25'd3000) begin cnt <= cnt+1; end
else begin cnt <= 25'd0; clk_reg <= ~clk_reg; end
end
2.按键消抖模块
为避免按键本身的硬件缺点,如硬件老化或硬件设计/材料有缺陷等,有必要在软件上进行消抖处理。
该模块的原理是存储2个连续的电位值,判断其是否相等,相等时输出该电位值,不相等时,输出上次输出的电位值。
module key_de (
input wire clk,
input wire key,
output wire value
);
reg key_r,key_r1,key_r2,state;
assign value = state;
//消除亚稳态
always@(posedge clk) begin
key_r <= key;
key_r1 <= key_r;
key_r2 <= key_r1;
end
//简单去抖动处理
always@(key_r1 or key_r2) begin
case({key_r1,key_r2})
2'b11: state <= 1'b1;
2'b00: state <= 1'b0;
default: state <= state;
endcase
end
endmodule //key_de
3.边沿检测模块
利用消抖之后稳定的按键数据来检测边沿信号,既可直接使用该模块的输出达到更稳定的按键控制效果,也可做为左/右旋标志产生模块的输入来检测旋转编码器的旋转方向。
其原理是存储两个连续的电位值,相同时不产生边沿标志,不相同时根据其为01或10输出不同的边沿标志。此处仅给出关键代码。
reg state_r,state_r1;
//对state信号进行边沿检测
always@(posedge clk) begin
state_r <= state;
state_r1 <= state_r;
end
assign pos = (!state_r1) && state_r;
assign neg = state_r1 && (!state_r);
4.左/右旋标志产生模块
用于判断旋转编码器的旋转方向,左右旋时会产生对应的脉冲信号。原理在代码的注释中有写出,此处不再赘述。
module flag (
input wire clk,
input wire rst,
input wire A_pos,
input wire A_neg,
input wire B_state,
output wire flag_L,
output wire flag_R
);
reg L_pulse,R_pulse;
assign flag_L = L_pulse;
assign flag_R = R_pulse;
//当A的上升沿伴随B的高电平或当A的下降沿伴随B的低电平 为向左旋转
always@(posedge clk or negedge rst) begin
if(!rst) L_pulse <= 1'b0;
else if((A_pos&&B_state)||(A_neg&&(!B_state))) L_pulse <= 1'b1;
else L_pulse <= 1'b0;
end
//当A的上升沿伴随B的低电平或当A的下降沿伴随B的高电平 为向右旋转
always@(posedge clk or negedge rst) begin
if(!rst) R_pulse <= 1'b0;
else if((A_pos&&(!B_state))||(A_neg&&B_state)) R_pulse <= 1'b1;
else R_pulse <= 1'b0;
end
endmodule //flag
5.数据产生单元
频率控制需要8个数字,幅值控制需要2个数字,为了稳定,将每个数字的产生都使用同一个小模块,再在另一个模块里将该模块多次例化即可。同时为了能被位选信号控制,将数字改变的条件和enable信号进行了与操作。对于每个数字,其功能为:
enable信号为1时,若数字已经为0时再减将其置为9,若数字为9时再加将其置为0,其他情况则是正常地进行数字的加减处理。此处给出关键代码及适当解释。
always @(posedge clk or negedge rst) begin//flag_L 为左旋标志,flag_R 为右旋标志
if(~rst) num <= 1'b0;
else if(flag_L && enable)
if(num >= 8'd9) num <= 1'b0;
else num <= num + 1;
else if(flag_R && enable)
if(num <= 8'd0) num <= 8'd9;
else num <= num - 1;
end
6.位选控制模块
对10个数据产生单元进行位选,实现每一位数字单独控制,互不干扰。该位选模块还将在取反后输出到板载的10个led灯上,方便观察。同时为了方便管理,还将波形切换控制也同样集成到这一模块里。
module ctr (
input clk,
input rst,
input key_wave_pos,
input en_add_pos,
input en_minus_pos,
output reg [1:0] wave,
output reg [9:0] enable
);
reg [3:0] cnt;
always @(posedge clk or negedge rst) begin//波形选择
if(~rst)
wave <= 2'b00;
else if(key_wave_pos)
if(wave >= 2'b10) wave <= 2'b00;
else wave <= wave + 1;
else wave <= wave;
end
always @(posedge clk or negedge rst) begin//位选控制
if(~rst) cnt <= 4'd0;
else if(en_add_pos)
if(cnt >= 4'd9) cnt <= 4'd0;
else cnt <= cnt + 1;
else if(en_minus_pos)
if(cnt <= 4'd0) cnt <= 4'd9;
else cnt <= cnt - 1;
end
always @(posedge clk or negedge rst) begin//位选输出
if(~rst) enable <= 8'b0000_0001;
else begin
case(cnt)
4'd0: enable <= 10'b00_0000_0001; 4'd1: enable <= 10'b00_0000_0010;
4'd2: enable <= 10'b00_0000_0100; 4'd3: enable <= 10'b00_0000_1000;
4'd4: enable <= 10'b00_0001_0000; 4'd5: enable <= 10'b00_0010_0000;
4'd6: enable <= 10'b00_0100_0000; 4'd7: enable <= 10'b00_1000_0000;
4'd8: enable <= 10'b01_0000_0000; 4'd9: enable <= 10'b10_0000_0000;
endcase
end
end
endmodule //ctr
7.数据产生模块
例化10次数据产生单元,并将位选控制模块输出的位选信息进行应用,实现对10个数的统一控制。
module num (
input clk,
input rst,
input [9:0] enable,
input wire flag_L,
input wire flag_R,
output [7:0] Mhz_10,Mhz_1,Khz_100,Khz_10,Khz_1,hz_100,hz_10,hz_1,Vpp_10,Vpp_1
);
num_unit h1(.clk(clk),.rst(rst),.enable(enable[0]),.flag_L(flag_L),.flag_R(flag_R),.num(hz_1));
num_unit h10(.clk(clk),.rst(rst),.enable(enable[1]),.flag_L(flag_L),.flag_R(flag_R),.num(hz_10));
num_unit h100(.clk(clk),.rst(rst),.enable(enable[2]),.flag_L(flag_L),.flag_R(flag_R),.num(hz_100));
num_unit Kh1(.clk(clk),.rst(rst),.enable(enable[3]),.flag_L(flag_L),.flag_R(flag_R),.num(Khz_1));
num_unit Kh10(.clk(clk),.rst(rst),.enable(enable[4]),.flag_L(flag_L),.flag_R(flag_R),.num(Khz_10));
num_unit Kh100(.clk(clk),.rst(rst),.enable(enable[5]),.flag_L(flag_L),.flag_R(flag_R),.num(Khz_100));
num_unit Mh1(.clk(clk),.rst(rst),.enable(enable[6]),.flag_L(flag_L),.flag_R(flag_R),.num(Mhz_1));
num_unit Mh10(.clk(clk),.rst(rst),.enable(enable[7]),.flag_L(flag_L),.flag_R(flag_R),.num(Mhz_10));
num_unit Vp_1(.clk(clk),.rst(rst),.enable(enable[8]),.flag_L(flag_L),.flag_R(flag_R),.num(Vpp_1));
num_unit Vp_10(.clk(clk),.rst(rst),.enable(enable[9]),.flag_L(flag_L),.flag_R(flag_R),.num(Vpp_10));
endmodule //num
8.OLED显示模块
将产生数据输出的OLED屏幕上,该模块的代码在电子森林上有例程,且代码太过冗长,所以不在此贴出,需要查看的话可以下载工程。
9.频率&幅值控制字模块
利用产生的数据产生频率&相位控制字,用于在波形数据输出模块控制频率及幅值。
有关控制字的计算方法可按照此例给出的公式进行计算:官方例程
module freq_ampl_compute (
input clk,
input [7:0] Mhz_10,Mhz_1,Khz_100,Khz_10,Khz_1,hz_100,hz_10,hz_1,V_10,V_1,
output reg [31:0] freq_ctrl,
output reg [9:0] ampl_ctrl
);
//频率控制字的合成
always@(posedge clk)
begin freq_ctrl <= hz_1*36 + hz_10*358 + hz_100*3579 + Khz_1*35791 + Khz_10*357914 + Khz_100*3579139 + Mhz_1*35791394 + Mhz_10*357913941; end
//幅度控制字的合成
always@(posedge clk)
begin ampl_ctrl <= V_1*31 + V_10*310; end
endmodule
10.波形数据输出模块
调用事先例化好的3个ROM,用频率控制字控制输入ROM的地址来调节频率,用幅值控制字乘ROM输出的数据来控制幅值,在case语句中实现波形的切换,最后在顶层文件中输出wave信号的高10位。这样就达到了对波形的形状、频率和幅值的控制。
module wave (
input clk,
input rst,
input [31:0] freq_ctrl,
input [9:0] ampl_ctrl,
input [1:0] wave_sel,
output reg [19:0] wave
);
reg [31:0] freq_cnt;
wire [9:0] sin_w, tri_w, squ_w;
always@(posedge clk or negedge rst)
begin
if(~rst) freq_cnt <= 1'b0;
else freq_cnt <= freq_cnt + freq_ctrl;
end
always@(posedge clk or negedge rst)
begin
if(~rst) wave <= 1'b0;
else
case(wave_sel)
2'b00: wave <= sin_w*ampl_ctrl;
2'b01: wave <= tri_w*ampl_ctrl;
2'b10: wave <= squ_w*ampl_ctrl;
default: wave <= 1'b0;
endcase
end
sin_rom u_sin(.Address(freq_cnt[31:22]), .OutClock(clk), .OutClockEn(1'b1), .Reset(1'b0),.Q(sin_w));
tri_rom u_tri(.Address(freq_cnt[31:22]), .OutClock(clk), .OutClockEn(1'b1), .Reset(1'b0),.Q(tri_w));
squ_rom u_squ(.Address(freq_cnt[31:22]), .OutClock(clk), .OutClockEn(1'b1), .Reset(1'b0),.Q(squ_w));
endmodule
四、实现的功能
-
生成波形频率、幅度可调的波形
- 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
- 在OLED上显示当前波形的形状、波形的频率以及幅度
- 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
上述实现的功能可观看上传至B站的视频,此处不再单独贴图展示。
五、未能实现的功能
- 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
对于该功能,不能实现的点并不是不能进行调幅,而是无法达到要求的1Vpp。为了验证ROM中预存的数据是否有误,我将幅度调节部分的代码注释掉,直接输出ROM中的数据,代码及结果如下。可以看到,不调节幅度直接输出的结果是达不到1Vpp的,我认为这个问题可能是硬件导致的,我无法解决。但是幅值的调节是可以做到的,实现的功能可观看上传至B站的视频,此处不再单独贴图展示。
always@(posedge clk or negedge rst)
begin
if(~rst)
wave <= 1'b0;
else
case(wave_sel)
2'b00: wave <= sin_w;//*ampl_ctrl;
2'b01: wave <= tri_w;//*ampl_ctrl;
2'b10: wave <= squ_w;//*ampl_ctrl;
default: wave <= 1'b0;
endcase
end
六、遇到的主要难题及解决方法
主要有两点:OLED使用FPGA驱动以及上板后的调试。
解决方法:①.OLED一开始看不懂代码,在知道OLED由ssd1309驱动后,查阅了该芯片的datasheet,明白了其数据传输时序后就看懂代码了。看懂了代码再对已给的例程进行一定的修改,就成功让数据显示在屏幕上了。②.上板后出现过波形不显示的问题,仔细排查后发现这款FPGA芯片的ROM是低电平复位而不是我们常用的高电平复位,在针对这一点修改后,波形就正常的产生了。
七、未来的计划及展望
这次做的项目只是使用了板载的DAC模块,之后可以尝试利用板载的ADC模块和OLED屏幕做一个简单示波器和频率计。