设计目标

在本文中,我们将在XO2-4000HC开发平台上,借助外部高速DAC实现:

  • DC~20MHz的正弦波,方波,三角波,锯齿波
  • 通过旋转编码器控制产生的信号频率与幅度(1 Vpp)
  • 波形的信息可以实时通过OLED显示屏查看

DDS发生器

DDS原理及实现

DDS全称为直接数字式频率合成计(Direct Digital Synthesizer),将需要输出的数据储存在ROM中,按照一定的频率去读取后通过DAC输出即可。一个完整的DDS模块应当包括:查找表、相位控制器、DAC、外部滤波器,后文将详细叙述。如果希望更加直观地了解到DDS的工作原理以及设计细节,可以访问ADI DDS仿真器,在该在线工具中你可以直观地看到每个周期的采样过程以及后置滤波器的原理。

DDS实现信号发生器需要以下参数:

  • 信号的采样率:根据奈奎斯特采样定理$f_s\ge2f_h$,理论上每个周期采样两个点即可恢复原始波形。但是请注意,以正弦波为例采样波形此时为三角波,这就对滤波器的设计提出了很高的要求。因为在实际应用中我们常常会在一周期采样多个点,本设计中采样率为120MHz,即一个周期采样6个点。
  • 相位控制字宽度:相位控制字决定了信号采样的增量间隔,理论上一个信号的最小分辨率$res=\frac{f_s}{2^n}$,在本设计中取28 bit以达到0.447Hz左右的频率分辨率。
  • 相位控制字增量:在上文中我们确定了频率的分辨率,将频率与分辨率相除即可得到单位增量。

ADI DDS仿真器

查找表

DDS的实现通过查找ROM表实现,ROM表保存了一个正弦信号在不同相位时的信号幅值,当以一定频率取出时即实现了信号的发生。在改设计中,使用了一个长度为256,宽度为10 bit的正弦表。由于正弦信号的幅值存在对称性,因此实际只储存64*9 bit的正弦表。实现的代码如下所示,通过地址的高两位来判断信号所处的象限,进而实现对幅值的补偿。

module lut(
    input [7:0] address,
    output reg [9:0] cos
);

wire [1:0] section;
reg [5:0] lut_address;
reg [8:0] lut_cos;
assign section = address[7:6]; //Get the sections of cos wave to reduce memory

always@(address)begin
    case(section)
        2'b00: begin
                    lut_address = address[5:0];
                    cos = 9'h1ff + lut_cos;
               end
        2'b01: begin
                    lut_address = ~address[5:0];
                    cos = 9'h1ff + lut_cos;
               end
        2'b10: begin
                    lut_address = address[5:0];
                    cos = 9'h1ff - lut_cos;
               end
        2'b11: begin
                    lut_address = ~address[5:0];
                    cos = 9'h1ff - lut_cos;
               end               
    endcase
end

always @(lut_address) begin
    case(lut_address)    
      6'h0: lut_cos=9'h0;
      6'h1: lut_cos=9'hC;
      6'h2: lut_cos=9'h19;
      6'h3: lut_cos=9'h25;
      6'h4: lut_cos=9'h32;
      6'h5: lut_cos=9'h3E;
      6'h6: lut_cos=9'h4B;
      6'h7: lut_cos=9'h57;
      6'h8: lut_cos=9'h63;
      6'h9: lut_cos=9'h70;
      6'ha: lut_cos=9'h7C;
      6'hb: lut_cos=9'h88;
      6'hc: lut_cos=9'h94;
      6'hd: lut_cos=9'hA0;
      6'he: lut_cos=9'hAC;
      6'hf: lut_cos=9'hB8;
      6'h10: lut_cos=9'hC3;
      6'h11: lut_cos=9'hCF;
      6'h12: lut_cos=9'hDA;
      6'h13: lut_cos=9'hE6;
      6'h14: lut_cos=9'hF1;
      6'h15: lut_cos=9'hFC;
      6'h16: lut_cos=9'h107;
      6'h17: lut_cos=9'h111;
      6'h18: lut_cos=9'h11C;
      6'h19: lut_cos=9'h126;
      6'h1a: lut_cos=9'h130;
      6'h1b: lut_cos=9'h13A;
      6'h1c: lut_cos=9'h144;
      6'h1d: lut_cos=9'h14E;
      6'h1e: lut_cos=9'h157;
      6'h1f: lut_cos=9'h161;
      6'h20: lut_cos=9'h16A;
      6'h21: lut_cos=9'h172;
      6'h22: lut_cos=9'h17B;
      6'h23: lut_cos=9'h183;
      6'h24: lut_cos=9'h18B;
      6'h25: lut_cos=9'h193;
      6'h26: lut_cos=9'h19B;
      6'h27: lut_cos=9'h1A2;
      6'h28: lut_cos=9'h1A9;
      6'h29: lut_cos=9'h1B0;
      6'h2a: lut_cos=9'h1B7;
      6'h2b: lut_cos=9'h1BD;
      6'h2c: lut_cos=9'h1C3;
      6'h2d: lut_cos=9'h1C9;
      6'h2e: lut_cos=9'h1CE;
      6'h2f: lut_cos=9'h1D4;
      6'h30: lut_cos=9'h1D9;
      6'h31: lut_cos=9'h1DD;
      6'h32: lut_cos=9'h1E2;
      6'h33: lut_cos=9'h1E6;
      6'h34: lut_cos=9'h1E9;
      6'h35: lut_cos=9'h1ED;
      6'h36: lut_cos=9'h1F0;
      6'h37: lut_cos=9'h1F3;
      6'h38: lut_cos=9'h1F6;
      6'h39: lut_cos=9'h1F8;
      6'h3a: lut_cos=9'h1FA;
      6'h3b: lut_cos=9'h1FC;
      6'h3c: lut_cos=9'h1FD;
      6'h3d: lut_cos=9'h1FE;
      6'h3e: lut_cos=9'h1FF;
      6'h3f: lut_cos=9'h1FF;
   endcase
end

endmodule

相位控制器

相位控制器是DDS设计中的核心,此设计可以直接影响信号的精度与稳定性。在前文叙述参数时已经提及,信号的控制字增量满足:$Add = \frac{f}{res}$,即增量等于信号频率除以分辨率。由于乘除法操作在没有DSP单元的FPGA内部十分消耗资源,因此此处采用移位相加,如下所示。将频率与分辨率的除法转化为乘法,对乘法因子转换为二进制后取一定的精度即可。

assign dds_phase_add = (wave_freq << 1) + (wave_freq >> 3) + (wave_freq >> 4) + (wave_freq >> 5) + (wave_freq >> 6) + (wave_freq >> 9) + (wave_freq >> 11) + (wave_freq >> 13); //wave_freq*2.2369384765625(10.00111100101010);

相位增量的控制单元有如下实现,可以观察到查找表的输出取决于当前的信号分量。

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        dds_phase <= 28'd0;
    end else begin
        dds_phase <= dds_phase + dds_phase_add;
    end
end

lut u_lut(
    .address(dds_phase[27:20]),
    .cos(cos_dac)
);

同时,在输出正弦波信号的同时我们需要输出方波、三角波、锯齿波,这些波形的产生均可通过对相位控制字截位进行。

//Code below is designed to generate Square wave
assign square_dac = {10{dds_phase[27]}};
//Code below is designed to generate Trig wave
assign trig_dac = dds_phase[27] ? ~dds_phase[26:17] : dds_phase[26:17];
//Code below is designed to generate Saw wave
assign saw_dac = dds_phase[27:18];

将所有信号通过一个多路选择器送出,即可实现不同类型的信号输出。

DAC与滤波器

本设计中高速DAC的时钟输入为120MHz,直接使用内部的锁相环IP核即可。注意在一般的应用中通常将外部的时钟信号经过锁相环稳定后再用作系统时钟,以保证时钟质量,使用锁相环的locked信号与外部复位信号一起用作系统复位。将该信号直接输出到DAC的时钟引脚,同时在D9~D0上并行送出数据即可实现数模转换。

wire pll_locked;
assign sys_rst_n = in_rst_n & pll_locked;
pll u_pll(
    .CLKI(in_clk),
    .CLKOP(dac_clk),
    .CLKOS(sys_clk),
    .LOCK(pll_locked)
);

为保证模拟信号的质量,滤除高次谐波,往往会在DAC的输出级加入一个低通滤波器。在选择滤波器的截止频率与阶次时需注意,信号越接近需要发射的频点最后的波形信噪比越高,但同时会引入些许衰减。我一般的做法是选择一个高阶的滤波器,同时设置较高的截止频率。

旋转编码器译码

板子上选用的编码器为增量式触电电刷编码器,型号为EC11。FPGA通过对A、B、S引脚的读取,进而识别出按键的状态:左转、右转、按下。其中S与传统按键类似,本电路中按下输出高电平,松手下拉到低电平。

旋转编码器原理图

按键S的消抖操作如下,对其进行20ms的消抖并输出一个时钟的高电平信号。

parameter CNT_20MS_MAX = 240_000; //To scan keys

//To D-reg the S input to sync data
reg r_encoder_s;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        r_encoder_s <= 1'b0;
    end else begin
        r_encoder_s <= encoder_s;
    end
end

//To provide 20ms counter to filter keys
reg [19:0] cnt_20ms;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        cnt_20ms <= 20'd0;
    end else if(encoder_s == 1'b0)begin
        cnt_20ms <= 20'd0;
    end else if(cnt_20ms == CNT_20MS_MAX - 1 && encoder_s == 1'b1)begin
        cnt_20ms <= cnt_20ms;
    end else begin
        cnt_20ms <= cnt_20ms + 1'b1;
    end
end
                
//To provide 1 clock cycle signal
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        encoder_ok <= 1'b0;
    end else if(cnt_20ms == CNT_20MS_MAX - 2)begin
        encoder_ok <= 1'b1;
    end else begin
        encoder_ok <= 1'b0;
    end
end

通过对A、B输出的信号相位进行判断即可得到编码器的旋转方向,当顺时针旋转时A信号提前B信号90°,逆时针旋转时B信号提前A信号90°。根据A、B信号相位的变化可以得到下述判断准则:

  • A信号上升沿B信号低电平,A信号下降沿B信号高电平,编码器顺时针旋转。
  • A信号上升沿B信号高电平,A信号下降沿B信号低电平,编码器逆时针旋转。

旋转编码器示意

编码器旋转方向的判断如下,在边沿进行检测即可。

parameter CNT_1MS_MAX = 12_000; //To get 1k sample rate 

//The counter of 1k signal
reg [15:0] cnt_1ms;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        cnt_1ms <= 16'd0;
    end else if(cnt_1ms == CNT_1MS_MAX - 1)begin
        cnt_1ms <= 16'd0;
    end else begin
        cnt_1ms <= cnt_1ms + 1'b1;
    end
end

//To D-reg the A&B input to sync data using 1k clock
reg r_encoder_a,rr_encoder_a;
reg r_encoder_b,rr_encoder_b;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        r_encoder_a <= 1'b0;
        r_encoder_b <= 1'b0;
        rr_encoder_a <= 1'b0;
        rr_encoder_b <= 1'b0;
    end else if(cnt_1ms == CNT_1MS_MAX - 1)begin
        r_encoder_a <= encoder_a;
        r_encoder_b <= encoder_b;
        rr_encoder_a <= r_encoder_a;
        rr_encoder_b <= r_encoder_b;
    end
end

wire state_a = encoder_a && r_encoder_a && rr_encoder_a;
wire state_b = encoder_b && r_encoder_b && rr_encoder_b;
reg r_state_a;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        r_state_a <= 1'b0;
    end else begin
        r_state_a <= state_a;
    end
end

//To get the rise_edge and fall_edge
wire encoder_a_rise;
wire encoder_a_fall;
assign encoder_a_rise = (r_state_a | state_a)&(r_state_a == 1'b0);
assign encoder_a_fall = (r_state_a | state_a)&(state_a == 1'b0);
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        encoder_left <= 1'b0;
        encoder_right <= 1'b0;
    end else if((encoder_a_rise && rr_encoder_b)||(encoder_a_fall && !rr_encoder_b))begin
        encoder_left <= 1'b1;
        encoder_right <= 1'b0;
    end else if((encoder_a_fall && rr_encoder_b)||(encoder_a_rise && !rr_encoder_b))begin
        encoder_right <= 1'b1;
        encoder_left <= 1'b0;
    end else begin
        encoder_left <= 1'b0;
        encoder_right <= 1'b0;
    end
end

注意,在上述实现的代码中均加入了信号的触发器锁存,此举的目的为了消除亚稳态与跨时钟域同步。

菜单调节

在本设计中,需要调节的共有三个参量:信号类型、信号频率、信号幅度。信号的调节过程通过状态机实现,共有四个状态:类型调节、频率调节、幅度调节、闲置。画出状态转移图后即可轻松完成该部分的设计,状态通过encoder_ok进行跳转。

module menu(
    input sys_clk,
    input sys_rst_n,
    
    input encoder_left,
    input encoder_right,
    input encoder_ok,
    
//    output [7:0] debug,
    
    output [1:0] type_ctrl,
    output [31:0] freq_ctrl,
    output [3:0] amp_ctrl
);

parameter TYPE_CTRL = 2'b00;
parameter FREQ_CTRL = 2'b01;
parameter AMP_CTRL = 2'b10;
parameter IDLE = 2'b11;

reg [1:0] state;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        state <= TYPE_CTRL;
    end else if(encoder_ok)begin
        state <= state + 1'b1;
    end
end

reg [1:0] buff_type_ctrl;
reg [31:0] buff_freq_ctrl;
reg [3:0] buff_amp_ctrl;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        buff_type_ctrl <= 2'd0;
    end else if(state == TYPE_CTRL && encoder_right)begin
        buff_type_ctrl <= buff_type_ctrl + 2'd1;
    end else begin
        buff_type_ctrl <= buff_type_ctrl;
    end
end

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        buff_freq_ctrl = 32'd100_000;
    end else begin
        if(state == FREQ_CTRL && encoder_right)begin
            buff_freq_ctrl = buff_freq_ctrl + 32'd100_000;
        end else if(state == FREQ_CTRL && encoder_left)begin
            buff_freq_ctrl = buff_freq_ctrl - 32'd100_000;
        end
        
        if(buff_freq_ctrl >= 32'd20_000_000)begin
            buff_freq_ctrl = 32'd20_000_000;
        end else if(buff_freq_ctrl <= 32'd100_000)begin
            buff_freq_ctrl = 32'd100_000;
        end
    end 
end

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        buff_amp_ctrl = 4'd5;
    end else begin
        if(state == AMP_CTRL && encoder_right)begin
            buff_amp_ctrl = buff_amp_ctrl + 4'd1;
        end else if(state == AMP_CTRL && encoder_left)begin
            buff_amp_ctrl = buff_amp_ctrl - 4'd1;
        end
        
        if(buff_amp_ctrl >= 4'd10)begin
            buff_amp_ctrl = 4'd10;
        end else if(buff_amp_ctrl <= 4'd1)begin
            buff_amp_ctrl = 4'd1;
        end
    end
end

assign type_ctrl = buff_type_ctrl;
assign freq_ctrl = buff_freq_ctrl;
assign amp_ctrl = buff_amp_ctrl;

endmodule

结果测试

条件受限,家里只有一台20M带宽的示波器,且无硬件触发。因此在显示高频信号时候存在衰减,同时精度过低,幅度只进行了初校。显示方波这种由不同频率合成的信号,失真程度更甚,测试如下:

  • 100KHz正弦波,0.5Vpp

  • 500KHz方波,0.6Vpp

  • 1MHz三角波,0.7Vpp

  • 2MHz锯齿波,1.0Vpp

  • 20MHz正弦波,1.0Vpp

完整工程资料可在此处下载:Signal_generate_XO2-4000HC.zip

标签: DSP, FPGA, DDS

添加新评论