FPGA VerilogHDL语言 数字钟 按键消抖

1.描述

一个简单的基于FPGA的数字钟,语言用的是VerilogHDL,可以实现以下功能:
1. 数码管显示0-59(秒表)
2. 数码管显示:时-分-秒
3. 数码管显示时分秒并且可以设置时间(小时和分钟)
4. 在3的基础上,当分钟为59时,秒数从56-59依次对应不同LED亮
5. 在4的基础上,55秒、57秒、59秒警报,且59秒高音。
开发板上有四个LED灯(脚管输出高点亮)、一个长的数码管(有六个单个的,型号是LG3661BH)、四个按键(按下是低电平)

2.环境

硬件 :altera公司的Cyclone IV --EP4CE6F17C8N
软件: Quartus Prime Lite Edition 17.1
操作系统:Windows10

3.代码示例

1)按键消抖模块

按键消抖模块主要实现原理:采用两级触发器储存两个时钟节拍下连续读到的管教电平信息,通过比较两级触发器的数值来确定按键按下的状态是否稳定,稳定的话(两级触发器的值相同),启动定时器开始定时,期间如果值不稳定的话定时器会清零,若是定时器计数到指定的值后,输出键值。其具体代码如下

//按键消抖模块
module Btn_debounce(
		input       Btn_Clk,  //输入时钟
		input			Btn_in,	 //键值输入
		input			Btn_nRst, //复位
		output reg  Btn_out	 //输出
);

parameter 	BTN_FRE = 50;  //50MHZ
parameter	DLY_TIME = 20;  //20ms延时计数
parameter   DLY_TIME_VAL =  1000*DLY_TIME*BTN_FRE;//计数值

reg[31:0]   store_cnt;
reg[31:0]   next_cnt;
//采用两级触发器来比较前后键值
reg			DFF1;
reg			DFF2;

wire 			IsReachTime;   //稳定时间到达信号
wire			IsKeyValSame;  //触发器两次值相同信号

//获取当前按键状态
always@(posedge Btn_Clk or negedge Btn_nRst)
begin
	if(1'b0 == Btn_nRst)
		begin
			DFF1 <= 1'b1;
			DFF2 <= 1'b1;
			store_cnt <= 1'b0 ;
		end
	else
		begin
			DFF1 <= Btn_in ; //获取当前键值
			DFF2 <= DFF1;
			store_cnt <= next_cnt;
		end
end 


//输出稳定键值
always@(posedge Btn_Clk or negedge Btn_nRst)
begin
	if(1'b0 == Btn_nRst)	Btn_out <= 1'b1 ;//复位输出值
	//到达时间(按键以稳定一段时间了)输出键值
	else if(store_cnt == DLY_TIME_VAL) Btn_out <= DFF2 ;
	else Btn_out <= Btn_out ;
end

			
//键值稳定时间计数
assign IsKeyValSame = (DFF1^DFF2);//异或,不同为1相同为0
assign IsReachTime = ~(store_cnt == DLY_TIME_VAL); //

always@(IsKeyValSame or IsReachTime or store_cnt)
begin
	if({IsKeyValSame,IsReachTime} == 2'b00) //时间到了并且稳定,可以输出
		next_cnt <= store_cnt; //锁定计数值;
	else if({IsKeyValSame,IsReachTime} == 2'b01) //计数时间没到
		next_cnt <= store_cnt+1;
	else
	next_cnt <= 32'b0;  //键值不稳定,计数值清零
end

endmodule
2)数码管译码模块
//共阳极数码管译码器
module seg_decoder(
	input					nEn,
	input[3:0]			bin_data, //4位二进制输入
	output reg[6:0]	seg_data  //段码输出
);

always@(seg_data or nEn)
begin
	if(nEn == 1'b1) seg_data <=7'b111_1111;	
	else
	begin
	case(bin_data)
		4'd0:seg_data <= 7'b100_0000;  //0
		4'd1:seg_data <= 7'b111_1001;
		4'd2:seg_data <= 7'b010_0100;
		4'd3:seg_data <= 7'b011_0000;
		4'd4:seg_data <= 7'b001_1001;
		4'd5:seg_data <= 7'b001_0010;
		4'd6:seg_data <= 7'b000_0010;
		4'd7:seg_data <= 7'b111_1000;
		4'd8:seg_data <= 7'b000_0000;
		4'd9:seg_data <= 7'b001_0000;
		4'ha:seg_data <= 7'b000_1000;
		4'hb:seg_data <= 7'b000_0011;
		4'hc:seg_data <= 7'b100_0110;
		4'hd:seg_data <= 7'b010_0001;
		4'he:seg_data <= 7'b000_0110;
		4'hf:seg_data <= 7'b000_1110;  //F
		default:seg_data <= 7'b111_1111;
	endcase
	end
end
endmodule
3)数码管扫描模块
module seg_scaner(
	input			Clk,     //时钟信号
	input			nRst,
	output[3:0] seg_cnt, //
	output[5:0] seg_sel	
);


//parameter SCAN_FRE = 10000000 ;  //用于波形仿真测试
parameter SCAN_FRE = 10000 ;  
parameter SYS_FRE = 50000000;
parameter SCAN_COUNT = SYS_FRE/SCAN_FRE*6-1; //6个数码管

reg[31:0]   scan_counter;
reg[3:0]		Tscan_sel;
reg[5:0]    Tseg_sel;  
 
assign seg_sel = Tseg_sel;
assign seg_cnt = Tscan_sel;

always@(posedge Clk or negedge nRst)
begin
	if(1'b0 == nRst) 
	begin
		scan_counter <= 32'd0;
		Tscan_sel <= 4'd0;
	end
		
	else if(scan_counter >= SCAN_COUNT)
	begin
	   scan_counter <= 32'd0;
		if(Tscan_sel == 4'd5) Tscan_sel <= 4'd0;
		else Tscan_sel <= Tscan_sel+4'd1;
	end
	
	else 
	begin
		scan_counter <= scan_counter+32'd10;
	end
end

//类似于译码器
always@(posedge Clk or negedge nRst)
begin
	if(1'b0 == nRst) Tseg_sel <= 6'b111111; //共阳极,位选端
	else
	begin
		case(Tscan_sel)
			4'd0:Tseg_sel <= 6'b111110;
			
			4'd1:Tseg_sel <= 6'b111101;
			
			4'd2:Tseg_sel <= 6'b111011;
			
			4'd3:Tseg_sel <= 6'b110111;
			
			4'd4:Tseg_sel <= 6'b101111;
			
			4'd5:Tseg_sel <= 6'b011111;
		   
			default:Tseg_sel <= 6'b111111;
		endcase
	end
end
endmodule
4)计数器模块
//计数器模块
module MyNcounter 
			#(parameter BIT=4,
			  parameter N = 10)
				(
					input					CntClk,  
					input					CntnRst,
					output[BIT-1:0]	CntDout,  //数据输出
					output  				CntCout   //进位输出
				);


reg[BIT-1:0]	rDout;
reg				rCout;

assign CntDout = rDout;
assign CntCout = rCout;


always@(posedge CntClk or negedge CntnRst)
begin
	if(1'b0 == CntnRst)  
		begin
			rDout <= {BIT {1'b0}};
			rCout <= 1'b0;
		end
	 else
	 begin
		if(rDout < N-1)
			begin
				//rDout <= rDout+{BIT {1'b1}};
				rDout <= rDout+1;
				rCout <= 1'b0;
			end
		else
		   begin
				rDout <= {BIT {1'b0}};
				rCout <= 1'b1;
			end
	end
end

endmodule
5)两位计数器
//23点计数器
module My2BitNcounter
			#(parameter BIT=8)
				(
					input					CntClk,  
					input					CntnRst,
					output[BIT/2-1:0]	CntHDout,  //数据输出
					output[BIT/2-1:0]	CntLDout,
					output  				CntCout   //进位输出
				);
	
reg[BIT/2-1:0] 	rHDout;
reg[BIT/2-1:0] 	rLDout;
reg					rCout;

assign CntHDout = rHDout;
assign CntLDout = rLDout;
assign CntCout = rCout;


always@(posedge CntClk or negedge CntnRst)
begin
	if(1'b0 == CntnRst) 
	begin
		rHDout <= {BIT/2 {1'b0}};
		rLDout <= {BIT/2 {1'b0}};
		rCout  <= 1'b0;
	end
	else
	begin    //23
		if({rHDout,rLDout} < 8'b0010_0011)  //正常
		//if({rHDout,rLDout} <= 8'd23)  //错误代码
		begin
			if(rLDout < 9)
			begin
				rLDout <= rLDout+1;
				rCout <= 1'b0;
			end
			else
				begin
					rLDout <= {BIT/2 {1'b0}};
					if(rHDout < 9)
					begin
						rHDout <= rHDout+1;
						rCout <= 1'b0;
					end
					else
						begin
							rHDout <= {BIT/2 {1'b0}};
							rCout <= 1'b0;
						end
				end
		end
		else
		begin
			rHDout <= {BIT/2 {1'b0}};
			rLDout <= {BIT/2 {1'b0}};
			rCout  <= 1'b1;
		end
	end
	
end

endmodule

####6)分频器模块

//分频器独立小模块
module DigCtrlFre #(parameter N=8)
(
	input				PreClk,
	input				DivnRst,
	input[N-1:0]	PreNum,
	output			DivClk
);

reg[N-1:0]		rCnt;
reg				rDivClk;
reg[N-1:0]		rPreNum;

assign DivClk = rDivClk;

always@(posedge PreClk or negedge DivnRst)
begin
	if(1'b0 == DivnRst)
	begin	
		rDivClk <= {N {1'b0}};
		rPreNum <= {N {1'b0}};
		rCnt <= {N {1'b0}};
	end
	else
	begin
	   //防止从高分频系数到低分频系数变化时需等待1周期
		if(rPreNum == PreNum) 
		begin
			rCnt <= rCnt+1;
			if(rCnt == PreNum)
				begin
					rDivClk <= (~rDivClk);
					rCnt <= {N {1'b0}};
				end
			else
				rDivClk <= rDivClk;
		end
	   else
			begin
				rPreNum <= PreNum;
				rCnt <= {N {1'b0}};
			end
			
	end
end
		
endmodule		
7)顶层代码
/*======================================================================
实验内容:
		1.	数码管显示0-59(秒表)
		2.	数码管显示:时-分-秒
		3.	数码管显示时分秒并且可以设置时间(小时和分钟)
		4.	在3的基础上,当分钟为59时,秒数从56-59依次对应不同LED亮
		5.	在4的基础上,55秒、57秒、59秒警报,且59秒高音
文件说明:
		top1:实验内容1的顶层文件         seg_scaner:  6个数码管扫描显示文件
		top2:实验内容2的顶层文件			 seg_decoder: 数码管段码译码文件
		top3:实验内容3的顶层文件			 Btn_debounce:按键消抖模块文件
		top4:实验内容4的顶层文件			 MyNcounter:  任意进制计数器
		top5:实验内容5的顶层文件			 DigCtrlFre:  时钟分频模块

注意事项:
	管脚分配:
			CLK:				E1
			NRST:				N13
			SEG_SEL[0-5]:	N9,P9,M10,N11,P11,M11
			SEG_DATA[0-7]: R14,N16,P16,T15,P15,N12,N15,R16
			KEYTSET:			M15
			KEYMADD:			M16
			KEYHADD:			E16
			LED_OUT[0-3]:  E10,F9,C9,D9(高电平亮)
			BUZ_OUT:			C11(无源,低电平响)
	代码方面:
			按设置键后,小时和分钟个位会加一次(已解决)
=======================================================================*/

module top5(
		input				 CLK,
		input				 NRST,
		input           KEYTSET,
		input				 KEYHADD,    //小时加
		input				 KEYMADD,    //分钟加
		output[5:0] 	 SEG_SEL,
		output[7:0]		 SEG_DATA,
		output reg[3:0] LED_OUT,
		output			 BUZ_OUT		 
);

reg[3:0]		rBinData;
//reg			rSegDecoderEn;
reg			IsTimeSetState;
reg			rBUZ_OUT;

wire[6:0]	rSEG_DATA;     //数码管译码器译码输出
wire			wClk1HZ;			//1HZ时钟线
wire        wClk2kHZ;
wire			wClk500HZ;

wire[3:0] 	wSegCnt;	

//秒钟
wire[3:0]   wSecCntGe;
wire 			wSecCntGeCy;  //个位计数器进位输出
wire[3:0]	wSecCntShi;	  //十位计数器数据输出
wire 			wSecCntShiCy;  //十位计数器进位输出
//分钟
wire[3:0]   wMinCntGe;
wire 			wMinCntGeCy;  //个位计数器进位输出
wire[3:0]	wMinCntShi;	  //十位计数器数据输出
wire 			wMinCntShiCy;  //个位计数器进位输出
//小时
wire[3:0]   wHorCntGe;
wire 			whorCntGeCy;  //个位计数器进位输出
wire[3:0]	wHorCntShi;	  //十位计数器数据输出

//按键消抖模块输出
wire        wKeyBdSet;
wire			wKeyBdHadd;
wire			wKeyBdMadd;

reg  rHorDrivClk;  //小时驱动时钟
reg  rMinDrivClk;  //分钟驱动时钟
reg  rSecDrivClk;


assign BUZ_OUT = rBUZ_OUT;

//显示时间格式为23.00.00(时 分 秒)
assign SEG_DATA = (wSegCnt%2==0)?{1'b1,rSEG_DATA}:{1'b0,rSEG_DATA};


//1HZ时间显示更新时钟
DigCtrlFre #(.N(32))  
			  DigCtrlFre_m4(
								.PreClk(CLK),
								.DivnRst(NRST),
								.PreNum(25000000),
								//.PreNum(1), //用于波形仿真测试
								.DivClk(wClk1HZ)
								 );
//2khz蜂鸣器驱动时钟								 
DigCtrlFre #(.N(32))
			  DigCtrlFre_m41(
								.PreClk(CLK),
								.DivnRst(NRST),
								.PreNum(12500),
								//.PreNum(1), //用于波形仿真测试
								.DivClk(wClk2kHZ)
								 );								 

//500hz蜂鸣器驱动时钟								 
DigCtrlFre #(.N(32))
			  DigCtrlFre_m42(
								.PreClk(CLK),
								.DivnRst(NRST),
								.PreNum(50000),
								//.PreNum(1), //用于波形仿真测试
								.DivClk(wClk500HZ)
								 );								 
								 
//数码管扫描
seg_scaner seg_scaner_m4(
								.Clk(CLK),
								.nRst(NRST),
								.seg_cnt(wSegCnt),
								.seg_sel(SEG_SEL)
								);

//位选								
always@(wSegCnt)
begin
	case(wSegCnt)
	3'b000:rBinData <= wHorCntShi;
	3'b001:rBinData <= wHorCntGe;
	3'b010:rBinData <= wMinCntShi;
	3'b011:rBinData <= wMinCntGe;
	3'b100:rBinData <= wSecCntShi;
	3'b101:rBinData <= wSecCntGe;
	default:rBinData <= 1'b0;
	endcase
end
								
//秒个位计数器
MyNcounter #(.BIT(4), //计数器位宽
				 .N(10)   //计数器进制
				)
				MyNcounter_m40(
								  .CntClk(rSecDrivClk),
								  .CntnRst(NRST),
								  .CntDout(wSecCntGe),
								  .CntCout(wSecCntGeCy)
		
								 );


//秒十位计数器
MyNcounter #(.BIT(4), //计数器位宽
				 .N(6)   //计数器进制
				)
				MyNcounter_m41(
								  .CntClk(wSecCntGeCy),
								  .CntnRst(NRST),
								  .CntDout(wSecCntShi),
								  .CntCout(wSecCntShiCy)
		
								 );

//分个位计数器
MyNcounter #(.BIT(4), //计数器位宽
				 .N(10)   //计数器进制
				)
				MyNcounter_m42(
								  .CntClk(rMinDrivClk),
								  .CntnRst(NRST),
								  .CntDout(wMinCntGe),
								  .CntCout(wMinCntGeCy)
		
								 );


//分十位计数器
MyNcounter #(.BIT(4), //计数器位宽
				 .N(6)   //计数器进制
				)
				MyNcounter_m43(
								  .CntClk(wMinCntGeCy),
								  .CntnRst(NRST),
								  .CntDout(wMinCntShi),
								  .CntCout(wMinCntShiCy)
		
								 );

//时个位计数器
//MyNcounter #(.BIT(4), //计数器位宽
//				 .N(10)   //计数器进制
//				)
//				MyNcounter_m44(
//								  .CntClk(rHorDrivClk),
//								  .CntnRst(NRST),
//								  .CntDout(wHorCntGe),
//								  .CntCout(wHorCntGeCy)
//		
//								 );
//
//
时十位计数器
//MyNcounter #(.BIT(4), //计数器位宽
//				 .N(3)   //计数器进制
//				)
//				MyNcounter_m45(
//								  .CntClk(wHorCntGeCy),
//								  .CntnRst(NRST),
//								  .CntDout(wHorCntShi),
//								  .CntCout()
//		
//								 );
//时分
My2BitNcounter #(.BIT(8)) 
					My2BitNcounter_m4(
											.CntClk(rHorDrivClk),  
											.CntnRst(NRST),
											.CntHDout(wHorCntShi),  //数据输出
											.CntLDout(wHorCntGe),
											.CntCout()   //进位输出
										  );
								 
								 
								 
//时间设置模块
always@(posedge wKeyBdSet or negedge NRST)
begin
   if(1'b0 == NRST) IsTimeSetState <= 1'b0; //防止初次启动不计数
	else
	begin
		if(wKeyBdSet)
		begin
			IsTimeSetState <= ~IsTimeSetState;
		end
		else IsTimeSetState <= IsTimeSetState;
	end
end

always@(IsTimeSetState)
begin
   //(1'b1 == IsTimeSetState)//问题代码
	if((1'b1 == IsTimeSetState) && (wKeyBdHadd == 1'b0 || wKeyBdMadd == 1'b0)) //正常   
	begin
		
		rHorDrivClk <= wKeyBdHadd;
		rMinDrivClk <= wKeyBdMadd;
		rSecDrivClk <= 1'b0;
	end
	else if(1'b0 == IsTimeSetState)
	begin
		rHorDrivClk <= wMinCntShiCy;
		rMinDrivClk <= wSecCntShiCy;
		rSecDrivClk <= wClk1HZ;
	end
end


//LED驱动模块
always@(posedge wClk1HZ or negedge NRST)
begin
	if(1'b0 == NRST) LED_OUT <= 4'b0000;
	else
		begin
			//if((wMinCntGe == 9) && (wMinCntShi == 1))  //测试用
			if((wMinCntGe == 9) && (wMinCntShi == 5))
			begin
				if(wSecCntShi == 5) 
				begin
					if(wSecCntGe == 6) LED_OUT <= 4'b0001;
					else LED_OUT <= (LED_OUT<<1);
				end
				else LED_OUT <= 4'b0000;
			end
			else LED_OUT <= 4'b0000;
		end
end


//蜂鸣器驱动模块
always@(posedge CLK)
begin
	if((wMinCntGe == 9) && (wMinCntShi == 5))
	begin
		if(wSecCntShi == 5)
		begin
			case(wSecCntGe)
				4'd5:rBUZ_OUT <= wClk500HZ;
			
				4'd7:rBUZ_OUT <= wClk500HZ;   //低音
				
				4'd9:rBUZ_OUT <= wClk2kHZ;   //高音
				default:rBUZ_OUT <= 1'b1;
		  endcase
		end
		else rBUZ_OUT <= 1'b1;
	end
	else rBUZ_OUT <= 1'b1;
end


//按键消抖模块
Btn_debounce Btn_debounce_m40(
								.Btn_Clk(CLK),  
								.Btn_in(KEYTSET),	 
								.Btn_nRst(NRST), 
								.Btn_out(wKeyBdSet)	 	
);

Btn_debounce Btn_debounce_m41(
								.Btn_Clk(CLK),  
								.Btn_in(KEYHADD),	 
								.Btn_nRst(NRST), 
								.Btn_out(wKeyBdHadd)
		
);

Btn_debounce Btn_debounce_m42(
								.Btn_Clk(CLK),  
								.Btn_in(KEYMADD),	 
								.Btn_nRst(NRST), 
								.Btn_out(wKeyBdMadd)
		
);




//数码管译码器
seg_decoder seg_decoder_m4(
									.nEn(1'b0),
									.bin_data(rBinData),
									.seg_data(rSEG_DATA)
									
									);								 
								 
							 
endmodule




4思路描述

设计具体实现思路是:通过分频器模块将50Mhz的输入时钟分别分为:
wClk1HZ(1HZ)给时分秒计数器
wClk2kHZ(2KHZ)驱动蜂鸣器发出高音
wClk500HZ(500HZ)驱动蜂鸣器发出低音

然后通过wClk1HZ驱动四个计数器级联实现分和秒的功能,使用两位24进制计数器实现时的功能。利用快速扫描和视觉残留效果实现6个数码管的同时显示(seg_scaner模块),在通过多路选择器(顶层文件中的case语句)实现不同数码管显示不同的数值。

对于时间设置的功能,设置按键按下,将分钟个位和小时个位的驱动时钟源换为按键按下抬起过程中产生的上升沿驱动,当设置按键再次按下时,时钟源再次切换为经过分频器分频后的1HZ时钟,时间加按键失效。

LED和蜂鸣器功能实现的方法差不多,通过判断秒数个位来进行不同的操作,LED模块通过移位实现不同灯的点亮,蜂鸣器硬件上采用的是有源蜂鸣器(接上高电平就叫),通过切换不同频率的驱动时钟源,实现不同声调的声音输出。

5总结

因为刚开始学FPGA 和verilogHDL语言,所以代码在资源节约利用和规范性上有所欠缺,但是基本的功能都已实现;按照惯例,每次新学一个东西总是想发个博客记录一下。各位参考着看,若是有些可以改进之处也欢迎大家讨论。

你可能感兴趣的:(#,FPGA)