**这是本文档旧的修订版!**
在电赛训练板上实现DDS的功能
关于用FPGA实现DDS的过程可以参考文档:DDS生成任意波形的方法及Verilog代码实例
顶层模块
module top(clk_in,sys_rst_n,key_a,key_b,key_ok,dac_data,dac_clk,oled_rst,oled_dcn,oled_clk,oled_dat); input clk_in,sys_rst_n,key_a,key_b,key_ok; //input [3:0] sw; output [9:0] dac_data; output dac_clk; output oled_rst; output oled_dcn; output oled_clk; output oled_dat; wire L_pulse,R_pulse,O_pulse; wire clk120,clk60,clk96; wire locked; wire rst_n; assign led=8'hff; assign rgbled=6'b111111; wire [1:0] wave; wire [26:0] WaveFreq; OLED12864 OLED12864_u1 ( .clk (clk_in) , .rst_n (rst_n), //.sw (cnt_seg), .wave (wave), //.oled_csn(oled_csn), .WaveFreq(WaveFreq), .oled_rst(oled_rst), .oled_dcn(oled_dcn), .oled_clk(oled_clk), .oled_dat(oled_dat) ); assign dac_clk=clk120; dds dds_u( .clk_in (clk120) , .rst_n (rst_n) , .O_pulse (O_pulse) , .L_pulse (L_pulse) , .R_pulse (R_pulse) , .dac_data(dac_data) , .wave (wave), .WaveFreq(WaveFreq) );// assign rst_n=sys_rst_n&locked; pll1 pll1_inst ( .areset ( ~sys_rst_n ), .inclk0 ( clk_in ), .c0 ( clk120 ), .locked ( locked ) ); Encoder Encoder_u( .clk_in (clk120), .rst_n_in (rst_n), .key_a (key_a), .key_b (key_b), .key_ok (key_ok), .Left_pulse (L_pulse), .Right_pulse(R_pulse), .OK_pulse (O_pulse) ); endmodule
DDS核心模块
这是DDS的主代码,可以选择输出的波形以及相应的频率
// -------------------------------------------------------------------- // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< // -------------------------------------------------------------------- // Module: dds // // Author: Step // // Description: dds // // Web: www.stepfapga.com // // -------------------------------------------------------------------- // Code Revision History : // -------------------------------------------------------------------- // Version: |Mod. Date: |Changes Made: // V1.0 |2021.11.27 |Initial ver // -------------------------------------------------------------------- module dds(clk_in,rst_n,O_pulse,L_pulse,R_pulse,dac_data,wave,WaveFreq);// localparam SIN = 2'b00, SAW = 2'b01, TRI = 2'b10, SQU = 2'b11; input clk_in; // 小脚丫FPGA的外部时钟频率为12MHz input rst_n; input wire O_pulse,L_pulse,R_pulse; output reg [9:0] dac_data; // 10位数据输出送给外部的DAC output reg [1:0] wave; output reg [26:0] WaveFreq; reg [23:0] phase_acc; //增加相位累加器位数使得分辨率提高 wire [23:0] phase; reg [23:0] f_inc; assign phase=phase_acc; always @(posedge clk_in) phase_acc <= phase_acc + f_inc; //f_inc=24'd27962;主时钟为12MHz,则产生20KHz的正弦波信号 wire [9:0] sin_dat; //正弦波 wire [9:0] saw_dat = phase[23:14]; //锯齿波 wire [9:0] tri_dat = phase[23]? (~phase[22:13]) : phase[22:13]; //三角波 wire [9:0] squ_dat = phase[23]? 10'h3ff : 10'h000; //方波 always @(*) begin case(wave) 2'b00: dac_data = sin_dat; //正弦波 2'b01: dac_data = saw_dat; //锯齿波 2'b10: dac_data = tri_dat; //三角波 2'b11: dac_data = squ_dat; //方波 default: dac_data = sin_dat; //正弦波 endcase end lookup_tables u_lookup_tables(.phase(phase_acc[23:16]), .sin_out(sin_dat)); //波形输出选择 always @(posedge clk_in or negedge rst_n) begin if(!rst_n) wave <= SIN; else if(O_pulse)begin case(wave) SIN: wave <= SAW; SAW: wave <= TRI; TRI: wave <= SQU; SQU: wave <= SIN; default: wave <= SIN; endcase end else wave <= wave; end //频率控制 always@(posedge clk_in or negedge rst_n) begin if(!rst_n) begin f_inc <= 24'h22222; WaveFreq<=1_000_000; end else if(L_pulse==1'b1) begin if(f_inc <= 24'h369d) f_inc <= f_inc; else begin f_inc <= f_inc - 24'h369d; WaveFreq<=WaveFreq-100000; end end else if(R_pulse==1'b1) begin if(f_inc >= 24'h155554) f_inc <= f_inc; else begin f_inc <= f_inc + 24'h369d; WaveFreq<=WaveFreq+100000; end end else f_inc <= f_inc; end endmodule //dds时钟频率给定后,输出信号的频率取决于频率控制字, // 频率分辨率取决于累加器位数, // 相位分辨率取决于ROM的地址线位数, // 幅度量化噪声取决于ROM的数据位字长和D/A转换器位数
查找表的程序,主要是利用了正弦波的对称性,为了节省FPGA的资源,可以只构建一个象限的波形,另外3个象限可以基于其对称性通过逻辑来实现。
/* lookup_tables.v */ module lookup_tables(phase, sin_out); input [7:0] phase; output [9:0] sin_out; wire [9:0] sin_out; reg [5:0] address; wire [1:0] sel; wire [8:0] sine_table_out; reg [9:0] sine_onecycle_amp; //assign sin_out = {4'b0, sine_onecycle_amp[9:4]} + 9'hff; // 可以调节输出信号的幅度 assign sin_out = sine_onecycle_amp[9:0]; assign sel = phase[7:6]; sin_table u_sin_table(address,sine_table_out); always @(sel or sine_table_out) begin case(sel) 2'b00: begin sine_onecycle_amp = 9'h1ff + sine_table_out[8:0]; address = phase[5:0]; end 2'b01: begin sine_onecycle_amp = 9'h1ff + sine_table_out[8:0]; address = ~phase[5:0]; end 2'b10: begin sine_onecycle_amp = 9'h1ff - sine_table_out[8:0]; address = phase[5:0]; end 2'b11: begin sine_onecycle_amp = 9'h1ff - sine_table_out[8:0]; address = ~ phase[5:0]; end endcase end endmodule
构建正弦波表的程序,Lattice的Diamond以及Inel的Quartus编译工具中都有相应的IP可以直接调用使用。
module sin_table(address,sin); output [8:0] sin; input [5:0] address; reg [8:0] sin; always @(address) begin case(address) 6'h0: sin=9'h0; 6'h1: sin=9'hC; 6'h2: sin=9'h19; 6'h3: sin=9'h25; 6'h4: sin=9'h32; 6'h5: sin=9'h3E; 6'h6: sin=9'h4B; 6'h7: sin=9'h57; 6'h8: sin=9'h63; 6'h9: sin=9'h70; 6'ha: sin=9'h7C; 6'hb: sin=9'h88; 6'hc: sin=9'h94; 6'hd: sin=9'hA0; 6'he: sin=9'hAC; 6'hf: sin=9'hB8; 6'h10: sin=9'hC3; 6'h11: sin=9'hCF; 6'h12: sin=9'hDA; 6'h13: sin=9'hE6; 6'h14: sin=9'hF1; 6'h15: sin=9'hFC; 6'h16: sin=9'h107; 6'h17: sin=9'h111; 6'h18: sin=9'h11C; 6'h19: sin=9'h126; 6'h1a: sin=9'h130; 6'h1b: sin=9'h13A; 6'h1c: sin=9'h144; 6'h1d: sin=9'h14E; 6'h1e: sin=9'h157; 6'h1f: sin=9'h161; 6'h20: sin=9'h16A; 6'h21: sin=9'h172; 6'h22: sin=9'h17B; 6'h23: sin=9'h183; 6'h24: sin=9'h18B; 6'h25: sin=9'h193; 6'h26: sin=9'h19B; 6'h27: sin=9'h1A2; 6'h28: sin=9'h1A9; 6'h29: sin=9'h1B0; 6'h2a: sin=9'h1B7; 6'h2b: sin=9'h1BD; 6'h2c: sin=9'h1C3; 6'h2d: sin=9'h1C9; 6'h2e: sin=9'h1CE; 6'h2f: sin=9'h1D4; 6'h30: sin=9'h1D9; 6'h31: sin=9'h1DD; 6'h32: sin=9'h1E2; 6'h33: sin=9'h1E6; 6'h34: sin=9'h1E9; 6'h35: sin=9'h1ED; 6'h36: sin=9'h1F0; 6'h37: sin=9'h1F3; 6'h38: sin=9'h1F6; 6'h39: sin=9'h1F8; 6'h3a: sin=9'h1FA; 6'h3b: sin=9'h1FC; 6'h3c: sin=9'h1FD; 6'h3d: sin=9'h1FE; 6'h3e: sin=9'h1FF; 6'h3f: sin=9'h1FF; endcase end endmodule
编码器输入模块
// -------------------------------------------------------------------- // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< // -------------------------------------------------------------------- // Module: Encoder // // Author: Step // // Description: Driver for rotary encoder // // Web: www.stepfapga.com // // -------------------------------------------------------------------- // Code Revision History : // -------------------------------------------------------------------- // Version: |Mod. Date: |Changes Made: // V1.0 |2021.11.27 |Initial ver // -------------------------------------------------------------------- module Encoder ( input clk_in, //系统时钟 input rst_n_in, //系统复位,低有效 input wire key_a, input wire key_b, input wire key_ok, output reg Left_pulse, //左旋转脉冲输出 output reg Right_pulse, //右旋转脉冲输出 output OK_pulse //按动脉冲输出 ); localparam NUM_500US = 6_0000; reg [15:0] cnt; //计数器周期为500us,控制键值采样频率 always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) cnt <= 0; else if(cnt >= NUM_500US-1) cnt <= 1'b0; else cnt <= cnt + 1'b1; end reg [5:0] cnt_20ms; reg key_a_r,key_a_r1; reg key_b_r,key_b_r1; reg key_ok_r; //针对A、B、D管脚分别做简单去抖操作, //如果对旋转编码器的要求比较高,建议现对旋转编码器的输出做严格的消抖处理后再来做旋转编码器的驱动 //对旋转编码器的输入缓存,消除亚稳态同时延时锁存 always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin key_a_r <= 1'b1; key_a_r1 <= 1'b1; key_b_r <= 1'b1; key_b_r1 <= 1'b1; cnt_20ms <= 1'b1; key_ok_r <= 1'b1; end else if(cnt == NUM_500US-1) begin key_a_r <= key_a; key_a_r1 <= key_a_r; key_b_r <= key_b; key_b_r1 <= key_b_r; if(cnt_20ms >= 6'd40) begin //对于按键D信号还是采用20ms周期采样的方法,40*500us = 20ms cnt_20ms <= 6'd0; key_ok_r <= key_ok; end else begin cnt_20ms <= cnt_20ms + 1'b1; key_ok_r <= key_ok_r; end end end reg key_ok_r1; //对按键D信号进行延时锁存 always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) key_ok_r1 <= 1'b1; else key_ok_r1 <= key_ok_r; end wire A_state = key_a_r1 && key_a_r && key_a; //旋转编码器A信号高电平状态检测 wire B_state = key_b_r1 && key_b_r && key_b; //旋转编码器B信号高电平状态检测 assign OK_pulse = key_ok_r1 && (!key_ok_r); //旋转编码器D信号下降沿检测 reg A_state_reg; //延时锁存 always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) A_state_reg <= 1'b1; else A_state_reg <= A_state; end //旋转编码器A信号的上升沿和下降沿检测 wire A_pos = (!A_state_reg) && A_state; wire A_neg = A_state_reg && (!A_state); //通过旋转编码器A信号的边沿和B信号的电平状态的组合判断旋转编码器的操作,并输出对应的脉冲信号 always@(posedge clk_in or negedge rst_n_in)begin if(!rst_n_in)begin Right_pulse <= 1'b0; Left_pulse <= 1'b0; end else begin if(A_pos && B_state) Left_pulse <= 1'b1; else if(A_neg && B_state) Right_pulse <= 1'b1; else begin Right_pulse <= 1'b0; Left_pulse <= 1'b0; end end end endmodule