基于小脚丫FPGA的电赛训练平台的DDS任意波形发生器
使用小脚丫FPGA的电赛训练平台完成了2022寒假在家一起练活动的项目三—— DDS任意波形发生器/本地控制 。
标签
FPGA
显示
DDS
2022寒假在家练
NekoPrinter
更新2022-03-03
成都大学
1121

一、需求分析

  1. 通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
  2. 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
  3. 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
  4. 在OLED上显示当前波形的形状、波形的频率以及幅度
  5. 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节

二、设计思路

首先由控制模块来统一控制所需要的所有按键,并赋予其相应的功能。

数据产生模块受控于控制模块,实现数据的产生,并对产生的数据进行增减操作。

OLED显示模块将产生的数据显示在屏幕上,方便人的观察及使用。

波形产生模块使用产生的数据来从ROM中读取对应的波形文件,再将读取到的数据输出到DAC芯片中,就完成的波形的生成。

这里只是简单的对该信号发生器做了基本的介绍,关于DDS波形发生器的原理在电子森林中有及其详细的解释。

Fjp7oAKOY6AiCyT6w4YfsREMzwV6

三、模块介绍

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

四、实现的功能

  1. 生成波形频率、幅度可调的波形
  2. 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
  3. 在OLED上显示当前波形的形状、波形的频率以及幅度
  4. 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节

上述实现的功能可观看上传至B站的视频,此处不再单独贴图展示。

五、未能实现的功能

  1. 生成模拟信号的幅度为最大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

Fp9viEakC2tLQXhbOtAcnvsj9b96

六、遇到的主要难题及解决方法

主要有两点:OLED使用FPGA驱动以及上板后的调试。

解决方法:①.OLED一开始看不懂代码,在知道OLED由ssd1309驱动后,查阅了该芯片的datasheet,明白了其数据传输时序后就看懂代码了。看懂了代码再对已给的例程进行一定的修改,就成功让数据显示在屏幕上了。②.上板后出现过波形不显示的问题,仔细排查后发现这款FPGA芯片的ROM是低电平复位而不是我们常用的高电平复位,在针对这一点修改后,波形就正常的产生了。

七、未来的计划及展望

这次做的项目只是使用了板载的DAC模块,之后可以尝试利用板载的ADC模块和OLED屏幕做一个简单示波器和频率计。

附件下载
DDS_final.zip
完整工程
report.txt
FPGA资源使用报告
DDS_final_impl1.jed
可烧录文件
团队介绍
胡博-电子信息工程专业
团队成员
胡博
成都大学-胡博
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号