诗人的眼YI

6、按键可校准数码管时钟

0
阅读(2321)
/*实验有六个数码管,四个按键(一个系统复位按键、一个暂停/启动按键、一个时钟校准时的位选按键,一个对数码管上具体数字调节的按键)。
要求没有按键按下时,六个数码管按照24小时制进行计数。一旦暂停/启动按键按下,系统自动进入默认的“秒”数码管的个位调节,随后可以通过
位选按键选择你所需要校准的那个数码管*/
module seg8_stopwatch(clk, rst_n, En, seg, sel, key0, key1, key2);
	
	input clk;							//50MHz
	input rst_n;
	input En;							//使能信号
	input key0;
	input key1;
	input key2;
	
	output [5:0]sel;					//6位
	output reg[7:0]seg;				//8段
	
	reg [14:0]cnt1;					//为了得到数码管扫描始终clk_1k
	reg [25:0]cnt2;               //为了得到计数1s(即second)
	reg clk_1k;
	reg second;
	
	reg [7:0]disp_data_m;			//定义2个数码管,每四位对应一个“秒”数码管上要显示的数字,[3:0]低位,[7:4]高位
	reg [7:0]disp_data_f;			//定义2个数码管,每四位对应一个“分”数码管上要显示的数字,[3:0]低位,[7:4]高位
	reg [7:0]disp_data_s;			//定义2个数码管,每四位对应一个“时”数码管上要显示的数字,[3:0]低位,[7:4]高位
	
	reg [5:0]sel_r;					//配合En使用,可以选择全部关闭数码管
	reg [3:0]data_tmp;				//暂时寄存软件工具输入的数据disp_data
	
	reg cn;								//“秒”计数到59之后,需要进1给“分”,cn一个时钟高电平
	reg cm;								//“分”计数到59之后,需要进1给“时”,cm一个时钟高电平
	
	reg key0_en;						//系统时钟暂停/启动用的使能信号
	reg [2:0]disp_drive;				//时钟校准时用于位选
	
	wire key0_nedge;					//key0的下降沿
	wire key1_nedge;					//key1的下降沿
	wire key2_nedge;					//key2的下降沿
	
	wire cn_pedge;
	wire cm_pedge;
	
	

//--------------------数码管扫描时钟---------------------
//计数器计数
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt1 <= 15'd0;
		else if(!En)
			cnt1 <= 15'd0;
		else if(cnt1 == 15'd24_999)
			cnt1 <= 15'd0;
		else 
			cnt1 <= cnt1 + 1'b1;
			
//分频计数后产生clk_1k,1ms数码管扫描时钟
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			clk_1k <= 1'b0;
		else if(cnt1 == 15'd24_999)
			clk_1k <= ~clk_1k;
		else 
			clk_1k <= clk_1k;

//-------------------秒钟计数,得到1s------------------------
//分频计数,计数1s
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt2 <= 26'd0;
		else if(cnt2 == 26'd49_999_999)
		//else if(cnt2 == 26'd49_999)
			cnt2 <= 26'd0;
		else if(key0_en == 1)//计时暂停
			cnt2 <= cnt2;
		else
			cnt2 <= cnt2 + 1'b1;
			
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			second <= 1'b0;
		else if(cnt2 == 26'd49_999_999)
		//else if(cnt2 == 26'd49_999)
			second <= 1'b1;
		else 
			second <= 1'b0;

//--------------------显示处理,位选和段选--------------------------
//6个数码管依次从左往右点亮,低电平点亮
	always@(posedge clk_1k or negedge rst_n)
		if(!rst_n)
			sel_r <= 6'b11_1110;
		else if(sel_r == 6'b01_1111)
			sel_r <= 6'b11_1110;
		else 
		sel_r <= {sel_r[4:0],sel_r[5]}; //右移,注意这里用"<<"以为要注意补位的情况
			
//位选,当sel_r等于对应的值时点亮对应的数码管,数码管显示软件工具输入的值,低电平点亮
	always@(*)
		case(sel_r)
			6'b11_1110: data_tmp = disp_data_m[3:0];//时
			6'b11_1101: data_tmp = disp_data_m[7:4];//时
			6'b11_1011: data_tmp = disp_data_f[3:0];//分
			6'b11_0111: data_tmp = disp_data_f[7:4];//分
			6'b10_1111: data_tmp = disp_data_s[3:0];//秒
			6'b01_1111: data_tmp = disp_data_s[7:4];//秒
			default:data_tmp = 4'b0000;
		endcase
		
//段选,显示0-f在数码管上,板子上的是共阳数码管,低电平点亮
	always@(*)
		case(data_tmp)
			4'h0: seg = 8'b1100_0000;
			4'h1: seg = 8'b1111_1001;
			4'h2: seg = 8'b1010_0100;
			4'h3: seg = 8'b1011_0000;
			4'h4: seg = 8'b1001_1001;
			4'h5: seg = 8'b1001_0010;
			4'h6: seg = 8'b1000_0010;
			4'h7: seg = 8'b1111_1000;
			4'h8: seg = 8'b1000_0000;
			4'h9: seg = 8'b1001_0000;
			default: seg = 8'b1100_0000;
		endcase

//-----------------------------计数处理--------------------------------
//“秒”的处理
	always@(posedge clk or negedge rst_n) 
		if(!rst_n) begin
			disp_data_m[3:0] <= 4'd0;
			disp_data_m[7:4] <= 4'd0;
			cn <= 0;
		end
		else if(key0_en == 1 && disp_drive == 3'b000) begin     //秒个位调节
			if(key2_nedge == 1) begin
				if(disp_data_m[3:0] == 4'd9)
					disp_data_m[3:0] <= 4'd0;
				else
					disp_data_m[3:0] <= disp_data_m[3:0] + 1'b1;
			end
		end
		else if(key0_en == 1 && disp_drive == 3'b001) begin     //秒十位调节
			if(key2_nedge == 1) begin
				if(disp_data_m[7:4] == 4'd5)
					disp_data_m[7:4] <= 4'd0;
				else
					disp_data_m[7:4] <= disp_data_m[7:4] + 1'b1;
			end
		end
		else if(key0_en == 0 && second == 1)begin						//暂停/启动按键没有按下时的正常计数
			cn <= 0;
			disp_data_m[3:0] <= disp_data_m[3:0] + 1'b1;
			if(disp_data_m[3:0] == 4'd9) begin
				disp_data_m[3:0] <= 4'd0;
				disp_data_m[7:4] <= disp_data_m[7:4] + 1'b1;
				if(disp_data_m[7:4] == 4'd5) begin
					disp_data_m[7:4] <= 4'd0;
					cn <= 1;
				end
			end
		end
	
//“分”的处理,与“秒的处理”基本一样
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			disp_data_f[3:0] <= 4'd0;
			disp_data_f[7:4] <= 4'd0;
			cm <= 0;
		end
		
		else if(key0_en == 1 && disp_drive == 3'b010) begin     //分个位调节
			if(key2_nedge == 1) begin
				if(disp_data_f[3:0] == 4'd9)
					disp_data_f[3:0] <= 4'd0;
				else
					disp_data_f[3:0] <= disp_data_f[3:0] + 1'b1;
			end
		end
		else if(key0_en == 1 && disp_drive == 3'b011) begin     //分十位调节
			if(key2_nedge == 1) begin
				if(disp_data_f[7:4] == 4'd5)
					disp_data_f[7:4] <= 4'd0;
				else
					disp_data_f[7:4] <= disp_data_f[7:4] + 1'b1;
			end
		end
		
		else if(key0_en == 0 && cn_pedge == 1) begin							//暂停/启动按键没有按下时的正常计数
			cm <= 0;
			disp_data_f[3:0] <= disp_data_f[3:0] + 1'b1;
			if(disp_data_f[3:0] == 4'd9) begin
				disp_data_f[3:0] <= 4'd0;
				disp_data_f[7:4] <= disp_data_f[7:4] + 1'b1;
				if(disp_data_f[7:4] == 4'd5) begin
					disp_data_f[7:4] <= 4'd0;
					cm <= 1;
				end
			end
		end

//“时”的处理
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			disp_data_s[3:0] <= 4'd0;
			disp_data_s[7:4] <= 4'd0;
		end	
		else if(key0_en == 1 && disp_drive == 3'b100) begin     //时个位调节
			if(key2_nedge == 1) begin
				if(disp_data_s[3:0] == 4'd9)
					disp_data_s[3:0] <= 4'd0;
				else
					disp_data_s[3:0] <= disp_data_s[3:0] + 1'b1;
			end
		end
		else if(key0_en == 1 && disp_drive == 3'b101) begin     //时十位调节
			if(key2_nedge == 1) begin
				if(disp_data_s[7:4] == 4'd2)
					disp_data_s[7:4] <= 4'd0;
				else
					disp_data_s[7:4] <= disp_data_s[7:4] + 1'b1;
			end
		end
		else if(key0_en == 0 && disp_data_s[7:4] == 4'd2 && disp_data_s[3:0] >= 4'd3) begin	//暂停/启动按键没有按下时的正常计数
			disp_data_s[3:0] <= 4'd0;
			disp_data_s[7:4] <= 4'd0;
		end
		else if(key0_en ==0 && cm_pedge == 1)begin							//暂停/启动按键没有按下时的正常计数
			disp_data_s[3:0] <= disp_data_s[3:0] + 1'b1;
			if(disp_data_s[3:0] == 4'd9) begin
				disp_data_s[3:0] <= 4'd0;
				disp_data_s[7:4] <= disp_data_s[7:4] + 1'b1;
			end
		end

		
	assign sel = (En)?sel_r:6'b11_1111;
	
	
	
//----------------------按键处理---------------------------
//key0是暂停/启动按键,每次系统复位或者断电重启后的第一按下没用,从第二次之后的按键都是正常的
	reg key0_clk;
	//reg key0_en;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key0_clk <= 0;
			key0_en <= 0;
		end
		else if(key0_nedge) begin
			key0_clk <= ~key0_clk;
			if(key0_clk == 1) 
				key0_en <= 1;
			else 
				key0_en <= 0;
		end


//key1是设置按键
	//reg [2:0]disp_drive;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			disp_drive <= 3'b000;
		else begin
			if(key0_en == 1 && key1_nedge == 1)
				disp_drive <= disp_drive + 1'b1;
			else if(disp_drive == 3'b110)
				disp_drive <= 3'b000;
		end

//key2是调节按键

//-----------------key0/key1/key2的边沿检测----------------------
//key0按键的边沿检测
	reg key0_sa, key0_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key0_sa <= 1'b0;
			key0_sb <= 1'b0;
		end
		else begin
			key0_sa <= key0;
			key0_sb <= key0_sa;
		end
		
	reg key0_tmpa, key0_tmpb;
	//wire key0_nedge;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key0_tmpa <= 1'b0;
			key0_tmpb <= 1'b0;
		end
		else begin
			key0_tmpa <= key0_sb;
			key0_tmpb <= key0_tmpa;
		end
		
	assign key0_nedge = key0_tmpb & ~key0_tmpa;

	
//key1按键的边沿检测
	reg key1_sa, key1_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key1_sa <= 1'b0;
			key1_sb <= 1'b0;
		end
		else begin
			key1_sa <= key1;
			key1_sb <= key1_sa;
		end
		
	reg key1_tmpa, key1_tmpb;
	//wire key1_nedge;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key1_tmpa <= 1'b0;
			key1_tmpb <= 1'b0;
		end
		else begin
			key1_tmpa <= key1_sb;
			key1_tmpb <= key1_tmpa;
		end
		
	assign key1_nedge = key1_tmpb & ~key1_tmpa;
	
	
//key2按键的边沿检测
	reg key2_sa, key2_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key2_sa <= 1'b0;
			key2_sb <= 1'b0;
		end
		else begin
			key2_sa <= key2;
			key2_sb <= key2_sa;
		end
		
	reg key2_tmpa, key2_tmpb;
	//wire key2_nedge;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key2_tmpa <= 1'b0;
			key2_tmpb <= 1'b0;
		end
		else begin
			key2_tmpa <= key2_sb;
			key2_tmpb <= key2_tmpa;
		end
		
	assign key2_nedge = key2_tmpb & ~key2_tmpa;
	

	
//cn的边沿检测
	reg cn_sa, cn_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cn_sa <= 1'b0;
			cn_sb <= 1'b0;
		end
		else begin
			cn_sa <= cn;
			cn_sb <= cn_sa;
		end
		
	reg cn_tmpa, cn_tmpb;
	//wire cn_pedge;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cn_tmpa <= 1'b0;
			cn_tmpb <= 1'b0;
		end
		else begin
			cn_tmpa <= cn_sb;
			cn_tmpb <= cn_tmpa;
		end
		
	assign cn_pedge = ~cn_tmpb & cn_tmpa;
	
	
//cm的边沿检测
	reg cm_sa, cm_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cm_sa <= 1'b0;
			cm_sb <= 1'b0;
		end
		else begin
			cm_sa <= cm;
			cm_sb <= cm_sa;
		end
		
	reg cm_tmpa, cm_tmpb;
	//wire cm_pedge;这里需要放到最开始的位置,否则MODELSIM会报错
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cm_tmpa <= 1'b0;
			cm_tmpb <= 1'b0;
		end
		else begin
			cm_tmpa <= cm_sb;
			cm_tmpb <= cm_tmpa;
		end
		
	assign cm_pedge = ~cm_tmpb & cm_tmpa;
	
endmodule


按键消抖模块(这块直接用的小梅哥的)

blob.png

module key_filter(clk, rst_n, key_in, key_flag, key_state);

	input clk;
	input rst_n;
	input key_in;
	
	output reg key_flag;			//每次按键从高电平到低电平或者从低电平到高电平发生,key_flag输出一个高电平
	output reg key_state;		//从工程附带的图片中可以看到,key_state可以看作是一个没有抖动的按键
	
	localparam 
		IDLE		=4'b0001,		//按键没有按下的状态,初始或者结束
		FILTER0	=4'b0010,		//按键按下过程中的抖动部分
		DOWN		=4'b0100,		//按键处于低电平时间段
		FILTER1 	=4'b1000;		//按键释放过程中的抖动部分
	
	reg [19:0]cnt;
	reg [3:0]state;
	reg en_cnt;						//使能计数寄存器,用于抖动部分计数的开关
	reg cnt_full;					//抖动部分计数满标志信号

	
//---------------------边沿检测------------------------------	
//对外部输入的异步信号进行同步处理
	reg key_in_sa;
	reg key_in_sb;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key_in_sa <= 1'b0;
			key_in_sb <= 1'b0;
		end
		else begin
			key_in_sa <= key_in;
			key_in_sb <= key_in_sa;
		end
		
//使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步到系统时钟域中)的电平状态	
	reg key_tmpa;
	reg key_tmpb;
	wire pedge, nedge;
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			key_tmpa <= 1'b0;
			key_tmpb <= 1'b0;
		end
		else begin
			key_tmpa <= key_in_sb;
			key_tmpb <= key_tmpa;
		end
	
	assign nedge = key_tmpb & ~key_tmpa;
	assign pedge = ~key_tmpb & key_tmpa;

	
//------------------一段式状态机--------------------------
//
	always@(posedge clk or negedge rst_n)
		if(!rst_n) begin
			state <= IDLE;
			en_cnt <= 1'b0;
			key_flag <= 1'b0;
			key_state <= 1'b1;
		end
		else begin
			case(state)
				IDLE:
					begin
						key_flag <= 1'b0;
						if(nedge) begin
							state <= FILTER0;
							en_cnt <= 1'b1;//检测到抖动,开启计数
						end
						else
							state <= IDLE;
					end
					
				FILTER0:
					if(cnt_full) begin
						state <= DOWN;
						en_cnt <= 1'b0;
						key_flag <= 1'b1;
						key_state <= 1'b0;
					end
					else if(pedge) begin//如果检测到上升沿,说明这是一个抖动
						state <= IDLE;
						en_cnt <= 1'b0;
					end
					else 
						state <= FILTER0;
						
				DOWN:
					begin
						key_flag <= 1'b0;
						if(pedge) begin
							state <= FILTER1;
							en_cnt <= 1'b1;
						end
						else
							state <= DOWN;
					end
						
				FILTER1:
					if(cnt_full) begin
						state <= IDLE;
						key_flag <= 1'b1;
						key_state <= 1'b1;
					end
					else if(nedge) begin
						en_cnt <= 1'b0;
						state <= DOWN;
					end
					else	
						state <= FILTER1;
						
				default: 
					begin
						state <= IDLE;
						en_cnt <= 1'b0;
						key_flag <= 1'b0;
						key_state <= 1'b1;
					end
			endcase
		end

		
//en_cnt开关计数器
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt <= 20'd0;
		else if(en_cnt)
			cnt <= cnt + 1'b1;
		else
			cnt <= 20'd0;
	
//计数满20ms,输出一个计数满标志信号cnt_full	
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt_full <= 1'b0;
		else if(cnt == 20'd999_999)
			cnt_full <= 1'b1;
		else
			cnt_full <= 1'b0;



endmodule


顶层模块

/*这里先写好按键消抖部分key_filter,然后将三个输入按键例化消抖后,得到消
除抖动后的三个分别对应的按键key_state0、key_state1、key_state2,连接回
seg8_stopwatch的三个输入按键key0、key1、key2进行按键调节部分编写*/
module seg8_stopwatch_top(clk, rst_n, seg, sel, key0, key1, key2);

	input clk;							//50MHz
	input rst_n;
	input key0;
	input key1;
	input key2;
	
	output [5:0]sel;					//6位
	output [7:0]seg;					//8段
	
	wire key_flag0, key_state0;
	wire key_flag1, key_state1;
	wire key_flag2, key_state2;
	
	
	key_filter key_filter0(
		.clk(clk), 
		.rst_n(rst_n), 
		.key_in(key0), 
		.key_flag(key_flag0), 
		.key_state(key_state0)
	);
	
		key_filter key_filter1(
		.clk(clk), 
		.rst_n(rst_n), 
		.key_in(key1), 
		.key_flag(key_flag1), 
		.key_state(key_state1)
	);
	
		key_filter key_filter2(
		.clk(clk), 
		.rst_n(rst_n), 
		.key_in(key2), 
		.key_flag(key_flag2), 
		.key_state(key_state2)
	);
	
	seg8_stopwatch seg8_stopwatch(
		.clk(clk),
		.rst_n(rst_n),
		.key0(key_state0),
		.key1(key_state1),
		.key2(key_state2),
		.En(1'b1),
		.seg(seg),
		.sel(sel)
	);
	
	
	
endmodule