基于FPGA实现DDS任意波形发生器
基于小脚丫FPGA核心板,使用板载高速DAC,配合FPGA内部DDS的逻辑,生成波形可调、频率可调、幅度可调的波形
标签
FPGA
DDS
2022寒假在家练
sytnocui
更新2022-03-04
哈尔滨工业大学
4013

1 项目需求

项目7 - 频谱分析类电赛题目。1.通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形。

  • 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz

  • 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V

  • 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节

2 硬件介绍

      项目使用的开发板为搭载小脚丫FPGA核心板(全FPGA方案)的电赛训练板:

FqmDlam6zaTXlKZ15tPXa9XDzVbx

      系统框图如下:

Fh0nQlrWgyfvXd0FMaWCW0izbr2T

      项目用到的功能包括:编码器,按键,高速DAC。

3 完成的功能及达到的性能

3.1 波形输出

通过板载高速DAC输出任意波形,默认为正弦波,将输出引脚接在示波器上即可观察。

FkBQWJsHGm22hNg97BoB_9ROJB2s

3.2 按键切换波形

通过扩展板上的按键K1,K2切换波形,目前可输出正弦波,三角波,方波三种波形。

FmU6DtY2nnTqpW-uEYiEU7WDvTza

FhrzbmU96e13PTHfB-fKbDtpx-UK

3.3 编码器调频

按下编码器可切换“调频/调幅模式”,可在调频模式下通过编码器左右旋转调节输出波形的频率。

Fhpseqi3bGplqiaW0JVEvc_azoMn

3.4 编码器调幅

按下编码器可切换“调频/调幅模式”,可在调幅模式下通过编码器左右旋转调节输出波形的幅值。

FjqtaXq3exaXKr3UqJacyW60B18u

4 实现思路

  • 使用FPGA内置DDS逻辑产生10位数字电压信号,输出给板载高速DAC模块,由DAC模块输出相应的波形。

  • 设置波形状态机,管理FPGA输出的波形,可通过按键在正弦波,方波,三角波之间切换。

  • 设置编码器状态机,管理当前编码器的调节对象,可通过编码器OK键在调频和调幅之间切换。

5 实现过程

5.1 程序架构图

FmbWEr5nl_3KOoVV6CbPvIdMFMyM

(注:每个框图右下角名称灰色名称为文件执行的主要功能)

5.2 DDS波形产生

板载了10bit/120Msp 高速DAC,通过FPGA内部的DDS逻辑产生10bit的数字量电压值,向DAC模块输出电压值数据,DAC解析数据并在输出引脚输出对应的波形信号。

为了提高生成波形的最大频率,调用了Diamond内置的锁相环ip核,将12MHz的时钟信号倍频到120MHz输入给dds产生模块,使整个系统能产生的最大波形信号提高到20MHz左右。但波形在20MHz左右,每个周期的采样点仅有6个,波形的是真也较为严重。

具体波形产生方面,定义了32位累加器(即代码中cnt),120MHz的时钟信号上升沿用以使累加器累加。三角波和方波均可直接通过累加器得到,其中三角波获得方法为:

ori_out <= (cnt[31])?cnt[30:21]:~cnt[30:21];

方波获得方法为:

ori_out <= {10{ cnt[31] }};

正弦波无法通过累加器获得,项目采用查找表的方式,调用了diamond内置的sin-cos查找表IP核,输入8bit地址,输出10bit的数据。

//sin table
wire [9:0] dds_out_sin_temp;
wire sin_clk_en = 1'b1;
wire sin_reset = 1'b0;
dds_sin_table u_dds_sin_table(
.Clock(clk_pll),  
.ClkEn(sin_clk_en),
.Reset(sin_reset),
.Theta(cnt[31:24]),
.Sine(dds_out_sin_temp)
);

DDS产生文件(文件:dds.v,调用位置:main.v):

module dds(
	input clk_pll,				//输入时钟
	input [2:0] wave_st,
	input [31:0] fm_step,
	input [7:0] am_factor,
	output reg [9:0]dds_out		//输出设置成reg,可去毛刺
);
//状态机
parameter WAVE_STATE_SIN = 3'b110;	//正弦波
parameter WAVE_STATE_SQUARE = 3'b101;//方波
parameter WAVE_STATE_TRI = 3'b011;  //三角波

//调幅后data
reg [17:0] am_out;
//原始输出
reg [9:0] ori_out;

//累加器
reg [31:0] cnt;
always @(posedge clk_pll) cnt <= cnt + fm_step;

//sin table
wire [9:0] dds_out_sin_temp;
wire sin_clk_en = 1'b1;
wire sin_reset = 1'b0;
dds_sin_table u_dds_sin_table(
	.Clock(clk_pll),  
	.ClkEn(sin_clk_en),
	.Reset(sin_reset),
	.Theta(cnt[31:24]),
	.Sine(dds_out_sin_temp)
);

always @ (posedge clk_pll) 
begin
	case(wave_st)
		WAVE_STATE_SIN:
			ori_out <= dds_out_sin_temp + 10'd512;
		WAVE_STATE_SQUARE:
			ori_out <= {10{ cnt[31] }};
		WAVE_STATE_TRI:
			ori_out <= (cnt[31])?cnt[30:21]:~cnt[30:21];
		default:
			ori_out <= dds_out_sin_temp + 10'd512;
	endcase
	//调幅
	am_out <= ori_out * am_factor;
	dds_out <= am_out[17:8];
end

endmodule

5.3 状态机

状态机是FPGA编程重要的一环,由于系统需要完成切换波形和调节波形的幅值和频率,系统的状态主要分为当前波形种类状态和编码器状态,项目对其定义如下:

//状态机wave
parameter WAVE_STATE_SIN = 3'b110; //正弦波
parameter WAVE_STATE_SQUARE = 3'b101;//方波
parameter WAVE_STATE_TRI = 3'b011;  //三角波
reg [2:0] curr_wave_st;
reg [2:0] next_wave_st;
//状态机enc
parameter ENC_STATE_FM = 3'b110; //调频
parameter ENC_STATE_AM = 3'b101;     //调幅
reg [2:0] curr_enc_st;
reg [2:0] next_enc_st;

状态切换方面,系统采用按键的左右键切换波形显示,编码器OK键切换调频/调幅模式。对上述两个状态分别采用三段式编写了状态切换代码,可减少代码重复,同时便于管理,提高时序逻辑的稳定性。

状态切换代码如下:

    //第一段 同步逻辑 描述次态到现态的转移
    always @ (posedge clk or negedge rst_n)
    begin
        if(!rst_n) 
            curr_wave_st <= WAVE_STATE_SIN;
        else 
            curr_wave_st <= next_wave_st;
    end
    //第二段 组合逻辑描述状态转移的判断
    always @ (curr_wave_st or rst_n or key_pulse)
    begin
        if(!rst_n) begin
                next_wave_st = WAVE_STATE_SIN;
            end
        else begin
            case(curr_wave_st)
                WAVE_STATE_SIN: begin
                    if(key_pulse[0]) 
                        next_wave_st = WAVE_STATE_TRI;
                    else if(key_pulse[1])
                        next_wave_st = WAVE_STATE_SQUARE;
                    else
                        next_wave_st = WAVE_STATE_SIN;
                end
 
                WAVE_STATE_SQUARE: begin
                    if(key_pulse[0]) 
                        next_wave_st = WAVE_STATE_SIN;
                    else if(key_pulse[1])
                        next_wave_st = WAVE_STATE_TRI;
                    else
                        next_wave_st = WAVE_STATE_SQUARE;
                end
 
                WAVE_STATE_TRI: begin
                    if(key_pulse[0]) 
                        next_wave_st = WAVE_STATE_SQUARE;
                    else if(key_pulse[1])
                        next_wave_st = WAVE_STATE_SIN;
                    else
                        next_wave_st = WAVE_STATE_TRI;
                end
 
                default: next_wave_st = WAVE_STATE_SIN;
            endcase
        end
    end
    //第三段  同步逻辑 描述次态的输出动作
    always @ (posedge clk or negedge rst_n)
    begin
        if(!rst_n==1) begin
            led_out[2:0] <= WAVE_STATE_SIN;
            end 
        else begin
            case(next_wave_st)
                WAVE_STATE_SIN: begin
                    led_out[2:0] <= WAVE_STATE_SIN;
                end
 
                WAVE_STATE_SQUARE: begin
                    led_out[2:0] <= WAVE_STATE_SQUARE;
                end
 
                WAVE_STATE_TRI: begin
                    led_out[2:0] <= WAVE_STATE_TRI;
                end
 
                default:begin
                    led_out[2:0] <= WAVE_STATE_SIN;
                    end
            endcase
        end
    end
    
    //编码器的状态机,和上面一样
    always @ (posedge clk or negedge rst_n)
    begin
        if(!rst_n) 
            curr_enc_st <= ENC_STATE_FM;
        else 
            curr_enc_st <= next_enc_st;
    end
    //第二段 组合逻辑描述状态转移的判断
    always @ (curr_enc_st or rst_n or enc_pulse_ok)
    begin
        if(!rst_n) begin
                next_enc_st = ENC_STATE_FM;
            end
        else begin
            case(curr_enc_st)
                ENC_STATE_FM: begin
                    if(enc_pulse_ok) 
                        next_enc_st = ENC_STATE_AM;
                    else
                        next_enc_st = ENC_STATE_FM;
                end
 
                ENC_STATE_AM: begin
                    if(enc_pulse_ok) 
                        next_enc_st = ENC_STATE_FM;
                    else
                        next_enc_st = ENC_STATE_AM;
                end
 
                default: next_enc_st = ENC_STATE_FM;
            endcase
        end
    end
    //第三段  同步逻辑 描述次态的输出动作
    always @ (posedge clk or negedge rst_n)
    begin
        if(!rst_n==1) begin
            led_out[5:3] <= ENC_STATE_FM;
            end 
        else begin
            case(next_enc_st)
                ENC_STATE_FM: begin
                    led_out[5:3] <= ENC_STATE_FM;
                end
 
                ENC_STATE_AM: begin
                    led_out[5:3] <= ENC_STATE_AM;
                end
 
                default:begin
                    led_out[5:3] <= ENC_STATE_FM;
                    end
            endcase
        end
    end

5.4 按键消抖及编码器解码

按键消抖和编码器解码内容参考了电子森林的代码,网址如下:

其中,按键消抖模块调用时可自定义按键个数,仅例化一个模块即可实现多个按键的消抖操作。

5.5 调频及调幅

调频和调幅原理均参考了电子森林的DDS原理文章:[dds_verilog 电子森林] (eetree.cn)

其中,调频通过改变DDS累加器的累加值实现,调幅通过将产生的波形乘一个因数实现,具体请参考上述链接的文章。

将DDS累加器的累加值设置为fm_step,将调幅的因数值设为am_factor。分别定义两个模块负责调节这两个值,并通过顶层文件main.v例化,将调节的两个参数通过input的方式输入给dds波形产生模块。

其中,调频的代码为:

module dds_fm(
input clk, //输入时钟
input rst_n,
input enc_pulse_l,
input enc_pulse_r,
input [2:0] enc_st,
output reg [31:0] dds_fm_step //调频累加值
);
//状态机
parameter ENC_STATE_FM = 3'b110; //调频
parameter ENC_STATE_AM = 3'b101;     //调幅
​
​
//调频
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
dds_fm_step <= 32'h00_00_ff_ff;
else
case(enc_st)
ENC_STATE_FM:
if(enc_pulse_l)
dds_fm_step <= dds_fm_step - 8'hff;
else if(enc_pulse_r)
dds_fm_step <= dds_fm_step + 8'hff;
else
dds_fm_step <= dds_fm_step;
default:
dds_fm_step <= dds_fm_step;
endcase
end
​
endmodule

调幅的代码为:

module dds_am(
    input clk,              //输入时钟
    input rst_n,
    input enc_pulse_l,
    input enc_pulse_r,
    input [2:0] enc_st,
    output reg [7:0] dds_am_factor   //调幅因数
);
//状态机
parameter ENC_STATE_FM = 3'b110;    //调频
parameter ENC_STATE_AM = 3'b101;     //调幅
​
//调幅
always @(posedge clk or negedge rst_n) 
begin
    if(!rst_n) 
        dds_am_factor <= 8'hff;
    else
        case(enc_st)
            ENC_STATE_AM: 
                if(enc_pulse_l)
                    dds_am_factor <= dds_am_factor - 8'h0f;
                else if(enc_pulse_r)
                    dds_am_factor <= dds_am_factor + 8'h0f;
                else
                    dds_am_factor <= dds_am_factor;
            default: 
                dds_am_factor <= dds_am_factor;
        endcase
end
​
endmodule

 

 

6.资源报告

FljKBmFMLq5OarrHqyqolP_cGDGC

7 遇到的主要难题

7.1 内置sin_table的ip核波形不正常

系统在生成DDS波形时,调用了Diamond内置的ip核,调用方式如下:

//sin table
wire [9:0] dds_out_sin_temp;
wire sin_clk_en = 1'b1;
wire sin_reset = 1'b0;
dds_sin_table u_dds_sin_table(
.Clock(clk_pll),  
.ClkEn(sin_clk_en),
.Reset(sin_reset),
.Theta(cnt[31:24]),
.Sine(dds_out_sin_temp)
);

但是,该调用方式查找到的波是如下图所示的一种奇怪波形:

FivJegTfugJUiTX6fdh7EZpx0HNb

猜测是由于ip核设置问题,或ip核内部逻辑有误导致。为了生成一个完美的正弦波,在这里,我将sin_table输出的wire整体加上了10'd512,即:

ori_out <= dds_out_sin_temp + 10'd512;

得到的波形即为一个完美的正弦波。

7.2 reg与wire的区别问题

在初期学习时,我基本上使用的都是默认的wire类型,对wire与reg的区别理解也不是很深入,曾误认为wire和reg无法相互赋值,但在日渐的使用中,通过尝试,发现了wire和reg是可以相互赋值的。

由于前期使用的都是wire,无法在always内赋值,故在根据状态机确定波形时较难操作,在三选一的选择语句中,只能通过如下形式撰写:

assign dds_out =
(wave_st == WAVE_STATE_SIN)? dds_out_sin:
((wave_st == WAVE_STATE_SQUARE)?dds_out_square:dds_out_tri)

这种方式的可读性和逻辑性都较差,后来,将dds_out及有关变量均改为reg类型,即可将根据状态确定波形种类的语句迁移进always内处理(代码见上文4.2),可大大提高代码可读性。

另外,经测试,使用reg代替wire还可以起到减小毛刺的作用。

8 改进措施

本项目已经成功实现了简易信号发生器的功能,并达到了预期指标,但还有许多可以提升与扩展的地方:

  • 调用板载OLED对波形,参数灯进行显示,提高用户交互体验。

  • 按键使用不是很充分,可增加状态机的复杂度,实现波形频率和幅值的按位调节。

  • 调用板载ADC,实现一个简易的示波器,并与信号发生器联动,形成一个完整的系统。

附件下载
dds_demo02.zip
fpga源码压缩包,可直接烧录的文件在impl1文件夹下的.jed文件
团队介绍
来自哈尔滨工业大学的宋以拓
团队成员
sytnocui
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号