【Kevin原创】《基于FPGA的简易计算器设计》第二章:矩阵键盘按键检测

作者: Kevin 分类: 微课堂 发布时间: 2016-02-21 20:51

2.1矩阵键盘检测原理说明

首先,我们的这个设计为了能够满足我们的数字输入,采用4×4矩阵键盘。大家可能听过独立按键,但是在咱们这里只对矩阵键盘进行讲解。

咱们先看一下矩阵键盘的电路原理图:

keyscan

电路图中,总共有8根线,ROW[3:0]和COL[3:0]。

接着,咱们来说下这个矩阵键盘电路的工作原理。首先,ROW默认接收到的电平为4’b1111。但是如果有按键按下,那我们怎么来确定是哪个按键按下了呢?我们先一步一步来,假设当COL=4’b1110时,“0”按键按下,那我们接收到的ROW应该是4’b1110。这样一来,首先我们由COL=4’b1110确定了按键是在第一列,ROW=4’b1110确定了按键在第一行,所以确定按键的结果就是“0”按键。确定其余按键也是这样的,所以当我们确定某一个按键时,相应的COL值就应该为低电平,但是由于我们按下的按键是随机的,所以我们就需要使COL进行扫描,即从4’b1110,4’b1101,4’b1011,4’0111循环变化,当然变化的时间肯定是有间隔的。然后我们再通过接收到的ROW值来确定具体是哪个按键按下。这就是4×4矩阵按键的检测原理,当然4×4的原理搞清楚了,5×5或6×6那就不成问题了,对于独立按键检测那就更是小菜一碟了。

2.2按键消抖介绍

虽然矩阵键盘的原理讲清楚了,但是咱们怎么来写代码呢?这应该困扰过很多朋友。

在写代码之前,Kevin再给大家普及一个常识,对于机械键盘,在按下或松开时都是会有机械振动的。

key_dou

而对于抖动的时间,一般为5ms~10ms,为了是按键检测更稳定,这里我们把抖动时间定义为10ms。过了10ms之后,我们再去检测按键的值,这个操作就是我们通常说的“消抖”。

2.2矩阵按键检测逻辑框图

对于我们的这个设计,我们先对矩阵键盘编号,然后对应的按键按下之后,我们需要将按键的值显示到数码管上。

key_module

2.3矩阵键盘按键检测时序设计

在矩阵键盘检测时,我们必须要知道是否有按键按下,检测到有按键按下之后,先进行10ms的消抖,再来检测处于稳定的按键值。

那咱们怎么来确定是否有按键按下了呢?这里我们可以用这样的一条语句“&row”,咱们在这里使用的是按位与的运算,即row[3]&row[2]&row[1]&row[0]。只要我们检测到“&row”的值为0,那我们就能够确定此时一定有按键按下。

key_timming

对于这个时序图,Kevin在这里向大家解释一下:

Falg_col:当检测到按键按下后立即拉高,当按键的后消抖结束后再拉低

Col:根据flag_col信号的低电平来产生列扫描信号(时间间隔为20us)

Row_and:寄存&row的结果

Row_and_t:row_and 的延拍

Flag_neg:前消抖标志信号,前消抖计数器计满10ms时拉低

Cnt_pos:前消抖计数器,当前消抖结束后,继续保持计数器的值,直到后消抖结束后才清零

Flag_pos:后消抖标志

Cnt_pos:后消抖计数器

Col_lock:在检测到有按键按下时,寄存col的值,直到下次有按键按下时才变化

Row_lock:与col_lock作用相似

下面,咱们就来写代码了,写代码之前再说一句,col的扫描信号变换的时间为20us:

/***********************************************************************
*	Module	Name	:	keyscan
*	Engineer	:	Kevin
*	Function:	:	矩阵键盘检测
*	Blog	Website	:	http://dengkanwen.com
*	Version		:	1.0
************************************************************************/
module	keyscan(
		input	wire		sclk,
		input	wire		s_rst_n,
		input	wire	[3:0]	row,		//接收到的行信号
		
		output	reg	[3:0]	col,		//按键的列扫描
		output	reg	[3:0]	key_val
		);

	parameter	CNT_END	=	10'd999,		//20us计数
			CNT_F	=	19'd499_999;		//10ms计数	
	
	wire		row_and;			//row按位与结果
	reg		row_and_r;
	reg	[9:0]	cnt;				//20us计时,用于col扫描
	reg		flag_col;			//col扫描标志位

	reg		flag_neg;
	reg	[18:0]	cnt_neg;			//10ms计数,
	reg		flag_pos;
	reg	[18:0]	cnt_pos;
	reg	[3:0]	col_lock;			//有按键按下时,col的值
	reg	[3:0]	row_lock;			//有按键按下时,row的值
//===========================记录row_and的值=================================
	assign	row_and	=	&row;
	
	always	@(posedge sclk)
		row_and_r	<=	row_and;
			
//================================20us计数=================================
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			cnt	<=	10'd0;
		else if(flag_col == 1'b0 && cnt == CNT_END)
			cnt	<=	10'd0;
		else if(flag_col == 1'b1)
			cnt	<=	10'd0;
		else
			cnt	<=	cnt + 1'b1;
			
//=============================产生列扫描col信号===========================
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			col	<=	4'b1110;
		else if(flag_col == 1'b1)
			col	<=	col;
		else if(cnt == CNT_END)
			col	<=	{col[2:0], col[3]};
			
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			flag_col	<=	1'b0;
		else if(cnt_pos == CNT_F && cnt_neg == CNT_F)
			flag_col	<=	1'b0;
		else if(row_and == 1'b0 && row_and_r == 1'b1)
			flag_col	<=	1'b1;
		
//==============================进行前消抖=================================
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			cnt_neg	<=	19'd0;
		else if(flag_col == 1'b0)
			cnt_neg	<=	19'd0;
		else if(cnt_neg == CNT_F && flag_col == 1'b1)
			cnt_neg	<=	CNT_F;
		else if(flag_neg == 1'b1)
			cnt_neg	<=	cnt_neg + 1'b1;
			
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			flag_neg	<=	1'b0;
		else if(cnt_neg == CNT_F)
			flag_neg	<=	1'b0;
		else if(row_and == 1'b0 && row_and_r == 1'b1)
			flag_neg	<=	1'b1;

//==============================进行后消抖==================================
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			flag_pos	<=	1'b0;
		else if(cnt_pos == CNT_F)
			flag_pos	<=	1'b0;
		else if(row_and == 1'b1 && row_and_r == 1'b0 && cnt_neg == CNT_F)
			flag_pos	<=	1'b1;
			
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			cnt_pos	<=	19'd0;
		else if(flag_pos == 1'b0)
			cnt_pos	<=	19'd0;
		else 
			cnt_pos	<=	cnt_pos + 1'b1;

//==========================col与row的值进行锁存=============================	
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			col_lock	<=	4'b1111;
		else if(row_and == 1'b0 && row_and_r == 1'b1 && cnt_neg == 22'd0)
			col_lock	<=	col;
			
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			row_lock	<=	4'b1111;
		else if(row_and == 1'b0 && row_and_r == 1'b1 && cnt_neg == 22'd0)
			row_lock	<=	row;
//===========================================================================
	always	@(posedge sclk or negedge s_rst_n)
		if(s_rst_n == 1'b0)
			key_val	<=	4'd0;
		else case({col_lock, row_lock})
			8'b1110_1110:	key_val	<=	4'd0;
			8'b1101_1110:	key_val	<=	4'd1;
			8'b1011_1110:	key_val	<=	4'd2;
			8'b0111_1110:	key_val	<=	4'd3;

			8'b1110_1101:	key_val	<=	4'd4;
			8'b1101_1101:   key_val	<=	4'd5;
			8'b1011_1101:   key_val	<=	4'd6;
			8'b0111_1101:   key_val	<=	4'd7;

			8'b1110_1011:	key_val	<=	4'd8;
			8'b1101_1011:   key_val	<=	4'd9;
			8'b1011_1011:   key_val	<=	4'd10;
			8'b0111_1011:   key_val	<=	4'd11;

			8'b1110_0111:	key_val	<=	4'd12;
			8'b1101_0111:   key_val	<=	4'd13;
			8'b1011_0111:   key_val	<=	4'd14;
			8'b0111_0111:   key_val	<=	4'd15;
			
			default:	key_val	<=	4'd0;
			
		endcase
endmodule

下面来说下这个矩阵按键怎么进行仿真,当然如果你对自己的设计有足够的自信,也可以不仿真。

(未完,明天继续)

2条评论
  • BZH星

    2017年3月28日 下午6:24

    请问电子书哪里可以看到

    1. Kevin

      2017年3月28日 下午10:44

      这套电子书没有写完哦,已经写的部分,都已经发布在博客中了

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注