**这是本文档旧的修订版!**

在电赛训练板上实现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核心模块

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> 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转换器位数

编码器输入模块

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> 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

OLED显示模块