R-2R DDS信号发生器设计
设计目标
在本文中,我们将在XO2-4000HC开发平台上,借助外部高速DAC实现:
- DC~20MHz的正弦波,方波,三角波,锯齿波
- 通过旋转编码器控制产生的信号频率与幅度(1 Vpp)
- 波形的信息可以实时通过OLED显示屏查看
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左右的频率分辨率。
- 相位控制字增量:在上文中我们确定了频率的分辨率,将频率与分辨率相除即可得到单位增量。
查找表
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