FPGA 34 矩阵键盘 模块设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t93Mpr2Y-1627401356069)(FPGA 09 阻塞和非阻塞状态的理解.assets/test.png)]

FPGA 34 矩阵键盘 模块设计

​ 最早的 MCU(即单片机)其 IO 口相对较少,而且用到按键过多的话, 就会占用过多的 IO。 人们为了解决这个问题就引入了“矩阵键盘”。 在矩阵键盘中,每条行线和列线在交叉处都不是直接连同, 而是通过一个按键直接相连,这样以来一个 4*4 的矩阵键盘只需要 8 根控制线就可以完成16 个按键的控制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-md3ZukjT-1631430335809)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210602155737943.png)]

矩阵键盘工作原理 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oYfNTiT-1631430335811)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210602155831214.png)]

​ 检测矩阵键盘中某一按键是否被按下, 采用的方法是列扫描法。 图中一共有 8条控制线, 4 条行控制线(ROW),4 条列控制线(COL) 。在原理图图中,我们可以看到,ROW的信号【对于FPGA】是一个输入IO,COL0、1、2、3 是一个输出IO的配置,所以FPGA 只需要读取ROW0、1、2、3 端口的信号来进行判断。

​ 假如我们让 COL0=0,然后去读取 ROW 的值,如果 4 个 ROW 信号的电平全部为高,则表明当前列并无按键按下,则切换到扫描下一列,再去读取 4 个 ROW 信号的值,根据读到的 ROW 值判断当前是哪一个按键被按下。例如当扫描第三列的时候,即 COL=4’b1011 时,检测到 ROW=4’b1011,则说明“A”被按下。

矩阵键盘扫描驱动模块设计 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtfU9O1q-1631430335813)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210602170014986.png)]

端口描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKxC7FcN-1631430335815)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210602170036488.png)]

矩阵键盘检测状态转移图 :(P208)

key_board.v 文件

module key_board
(
	Clk,
	Rst_n,
	Key_Board_Row_i,

	Key_flag,
	Key_Value,
	Key_Board_Col_o
);

	input Clk ;
	input Rst_n;
	input [3:0]Key_Board_Row_i;

	output reg Key_flag;
	output reg [3:0]Key_Value;
	output reg [3:0]Key_Board_Col_o;

	
	// 输入列寄存器信号,输入以后做一次寄存
	reg [3:0]Key_Board_Row_r;

	reg En_Cnt ;	//滤波定时器
	reg [19:0] counter1; //滤波定时,时钟周期计数器
	reg Cnt_Done;	//滤波时间完成标志信号

	reg [3:0]Col_Tmp ; 	// 列按键按下状态 
	reg  Key_Flag_r; 	// 按键成功标志位
	
	reg [7:0]Key_Value_tmp;
	
	reg  [10:0]state;
	
	//按键按下标志位
	always@(posedge Clk)
		Key_flag <= Key_Flag_r;
	
	// 状态机状态参数
	localparam 
		IDEL				= 11'b00000000001,
		P_FILTER			= 11'b00000000010,
		READ_ROW_P 		= 11'b00000000100,
		SCAN_C0 			= 11'b00000001000,
		SCAN_C1 			= 11'b00000010000,
		SCAN_C2 			= 11'b00000100000,
		SCAN_C3 			= 11'b00001000000,
		PRESS_RESULT 	= 11'b00010000000,
		WAIT_R			= 11'b00100000000,
		R_FILTER 		= 11'b01000000000,
		READ_ROW_R 		= 11'b10000000000;

	// 滤波定时计数器

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		counter1 <= 20'd0;
	else if(En_Cnt)begin
		if(counter1 == 20'd999999)
			counter1 <= 20'd0;
		else
			counter1 <= counter1 + 1'b1;
	end
	else
		counter1 <= 20'd0;
		
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Cnt_Done <= 1'b0;
	else if(counter1 == 20'd999999)
		Cnt_Done <= 1'b1;
	else
		Cnt_Done <= 1'b0;



	//状态机
	always @(posedge Clk or negedge Rst_n)
	if(!Rst_n)
	begin
	  state <= IDEL ;
	  Key_Board_Col_o <= 4'b0000 ; //输出端口默认设置为低电平,只有按键按下时,默认的上拉电平就会变成低电平
	  En_Cnt <= 1'b0;
	  Col_Tmp <= 4'd0;
	  Key_Flag_r <= 1'b0;
	  Key_Value_tmp <= 8'd0;
	  Key_Board_Row_r <= 4'b1111;
	end
	else
	begin
		case (state)
			IDEL: 
				if(~&Key_Board_Row_i)begin  // Key_Board_Row_i  不等于 4‘d1111,说明有按键按下
					En_Cnt <= 1'b1 ;    // 开启滤波定时器
					state <= P_FILTER;  // 跳转进入前级滤波状态
				end
				else begin			// 按键未按下
					En_Cnt <=1'b0 ; // 消抖滤波定时器关闭
					state  <=IDEL ; // 等待状态 
				end
			P_FILTER :
				if(Cnt_Done) begin	//消抖滤波定时时间结束
				  	En_Cnt <= 1'b0; //关闭滤波定时器
					state <= READ_ROW_P; //跳转进入滤波检测状态
				end
				else begin			//消抖定时未结束
					En_Cnt <= 1'b1; //继续保持
					state <= P_FILTER;// 状态继续保持当前值
				end
			READ_ROW_P:
				if (~&Key_Board_Row_i) begin  //滤波后,第二次确定按键确实按下
					Key_Board_Row_r <= Key_Board_Row_i; // 存取当前状态的值
					Key_Board_Col_o <= 4'b1110;  // 设置 Key_Board_Col_o 的输出信号为 4‘b1110,其它列设置为高电平
					state <= SCAN_C0;	// 跳转进入第C0列的判断
				end
				else begin 			//这次是按键抖动
					state  <=IDEL ; // 进入空闲等待状态
					Key_Board_Col_o <= 4'b0000;  // 设置 Key_Board_Col_o 的输出信号为 4‘b0000
				end
			SCAN_C0 :
				begin
					Key_Board_Col_o <= 4'b1101;  // 设置 Key_Board_Col_o 的输出信号为 4‘b1101
					state <= SCAN_C1;	// 跳转进入第C1列的判断
					if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
						Col_Tmp <= 4'b0001 ; // 表示此次按下的按键是再第一列
					else
						Col_Tmp <= 4'b0000 ; // 表示此次按下的按键不在这列
				end
			SCAN_C1 :
				begin
					Key_Board_Col_o <= 4'b1011;  // 设置 Key_Board_Col_o 的输出信号为 4‘b1011
					state <= SCAN_C2;	// 跳转进入第C2列的判断
					if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
						Col_Tmp <= 4'b0010 | Col_Tmp ; // 表示此次按下的按键是再第2列
					else
						Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
				end
			SCAN_C2 :
				begin
					Key_Board_Col_o <= 4'b0111;  // 设置 Key_Board_Col_o 的输出信号为 4'b0111
					state <= SCAN_C3;	// 跳转进入第C3列的判断
					if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
						Col_Tmp <= 4'b0100 | Col_Tmp ; // 表示此次按下的按键是再第2列
					else
						Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
				end
			SCAN_C3 :
				begin
					state <= PRESS_RESULT;	// 进入此状态后,表示按键检测已经结束,需要进入下一个按键结果分析状态
					if(~&Key_Board_Row_i) 	// 其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
						Col_Tmp <= 4'b1000 | Col_Tmp ; // 表示此次按下的按键是再第3列
					else
						Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
				end
			PRESS_RESULT:
				begin
					state <= WAIT_R;		// 本次结果分析完成后,进入按键松开滤波状态
					Key_Board_Col_o <= 4'b0000; //按键检测查找完成,所以重新设置,输出Key_Board_Col_o的信号为 4‘b0000
					if(((Key_Board_Row_r[0] + Key_Board_Row_r[1] + Key_Board_Row_r[2] + Key_Board_Row_r[3]) == 4'd3) &&
					((Col_Tmp[0] + Col_Tmp[1] + Col_Tmp[2] + Col_Tmp[3]) == 4'd1)) // 检验是否只有一个按键按下
					begin
						Key_Flag_r <= 1'b1;							//产生按键成功标志信号
						Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp}; //位拼接,等下来查找
					end
					else
					begin
						Key_Flag_r <= 1'b0; 			//非单个按键按下,不产生按键完成信号
						Key_Value_tmp <= Key_Value_tmp; //按键值保持上一次按键的值
					end
				end
			WAIT_R :
				begin
					Key_Flag_r <= 0 ; // 按键成功标志信号清零
					if(&Key_Board_Row_i) //  Key_Board_Row_i = 4’b1111 表示(按键未按下)即,按键松开,此时我们进入后消抖状态
					begin
						En_Cnt =1'b1; // 开启消抖滤波的计时器
						state <= R_FILTER ; //进入后消抖判断转态
					end
					else // 按键仍然按下,我们在这次保持等待状态
					begin
						state <= WAIT_R ;
						En_Cnt =1'b0; // 消抖滤波的计时器暂时先不开启
					end	
				end
			R_FILTER :
				if(Cnt_Done)begin  //后消抖滤波定时完成
					En_Cnt <= 1'b0; 		//关闭滤波定时器使能信号
					state <= READ_ROW_R; 	//进入按键完全释放状态判断
				end
				else
				begin
					En_Cnt <= 1'b1; 		// 滤波消抖定时器正在进行
					state  <= R_FILTER;		// 继续保持滤波状态
				end
			READ_ROW_R :
				if(&Key_Board_Row_i)  // (Key_Board_Row_i =4'd1111)即,确定按键已经松开,本次按键检测已经完成
					state <= IDEL;
				else 	// 按键还没松开,再次后消抖滤波
				begin
					state <= R_FILTER;		// 再次进入后消抖滤波状态
					En_Cnt <= 1'b1; 		// 开启滤波消抖定时器
				end
			default:  state <= IDEL; // 默认,或者运行出错,进入空闲状态
		endcase  
	end

	//按键输出状态查找表  根据 : Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp};  实现数据的查找
	always @(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Key_Value <= 4'd0 ;
	else if(Key_Flag_r)  // 先判断,按键是否按下成功
	begin
		case (Key_Value_tmp)
			8'b1110_0001 : Key_Value <= 4'd0;
			8'b1110_0010 : Key_Value <= 4'd1;
			8'b1110_0100 : Key_Value <= 4'd2;
			8'b1110_1000 : Key_Value <= 4'd3;
			
			8'b1101_0001 : Key_Value <= 4'd4;
			8'b1101_0010 : Key_Value <= 4'd5;
			8'b1101_0100 : Key_Value <= 4'd6;
			8'b1101_1000 : Key_Value <= 4'd7;
			
			8'b1011_0001 : Key_Value <= 4'd8;
			8'b1011_0010 : Key_Value <= 4'd9;
			8'b1011_0100 : Key_Value <= 4'd10;
			8'b1011_1000 : Key_Value <= 4'd11;
			
			8'b0111_0001 : Key_Value <= 4'd12;
			8'b0111_0010 : Key_Value <= 4'd13;
			8'b0111_0100 : Key_Value <= 4'd14;
			8'b0111_1000 : Key_Value <= 4'd15; 
			default: Key_Value <= Key_Value ;
		endcase
	end

endmodule







矩阵键盘模型:

`timescale 1ns/1ns
module Key_Board_model(
    Key_Col,
    Key_Row
    );
    // 矩阵键盘模型
    // 注: 写的是一个键盘的模型,相当于模拟矩阵的输入和输出
    // 主要是模拟按键按下,信号输出给fpga 的过程,其过程包括按键抖动信号
    input [3:0]Key_Col; //对于矩阵键盘来说,Key_Col 是一个 输入信号,对fpga是一个输出信号
    output reg [3:0]Key_Row; //Key_Row 是一个输出信号,对pga 是有一个输入信号

    reg [5:0]myrand ;
    reg [3:0]Key_Row_r; //行寄存器
    reg key_row_sel ;
    reg [1:0]now_col,now_row ; //当前行与当前列

    initial begin
        now_col =0;
        now_row =0;
        key_row_sel =0 ;
        myrand =0 ;
    end

    initial begin
        Key_Row_r = 4'b1111 ; // 初始状态,即模型按键未按下时刻,矩阵键盘的行信号默认都是上拉电平,即全是高电平
        #50000000 ;
        // 顺序按键按下,
        press_key(0, 0); //第一行
        press_key(0, 1);
        press_key(0,2);
        press_key(0,3);

        press_key(1, 0); //第二行
        press_key(1, 1);
        press_key(1,2);
        press_key(1,3);

        press_key(2, 0); //第三行
        press_key(2, 1);
        press_key(2,2);
        press_key(2,3);
        
        press_key(3, 0); //第四行
        press_key(3, 1);
        press_key(3,2);
        press_key(3,3);
		  
        $stop;

    end
    // 编写press_key任务(函数)  调用方式: press_key(row,col); //参数输入按键的位置row,col
    task press_key;
        input [1:0]row,col;
        // 按键按下进行执行下列操作
        begin
            key_row_sel = 0;//将行选择信号设置为有效状态
            Key_Row_r = 4'b1111; // 初始状态按键未按下
            Key_Row_r[row] = 0 ; // row,按键按下,让Key_Row_r 的这个row bit 未设置为0 ,表示按键按下
            now_row = row;  
            repeat(20)begin //重复 20 次,随机产生按键按下时 20 个不同的行状态
                myrand = {$random} % 65536; //随机延时
                #myrand Key_Row_r[row] = ~Key_Row_r[row]; // 模拟按键抖动(电平时高时低)状态
            end
            key_row_sel = 1; //将行选择信号设置为有效状态
            now_col = col;   //消抖以后,获取当前列输入信号 col ???
            #22000000;
            key_row_sel =0 ;
            Key_Row_r = 4'b1111; // 进入按键后滤波状态
            repeat(20)begin //重复 20 次,随机产生按键松开时 20 个不同行状态
                myrand = {$random} % 65536;
                #myrand Key_Row_r[row] = ~Key_Row_r[row];
            end
            Key_Row_r = 4'b1111; // 按键完全释放
            #22000000;
        end
    endtask

    always @(*) 
    if (key_row_sel)  //行信号有效
        case(now_row) //根据输入的列信号,获取按键Key_Row输出的行信号
            2'd0:Key_Row = {1'b1,1'b1,1'b1,Key_Col[now_col]};
            2'd1:Key_Row = {1'b1,1'b1,Key_Col[now_col],1'b1};
            2'd2:Key_Row = {1'b1,Key_Col[now_col],1'b1,1'b1};
            2'd3:Key_Row = {Key_Col[now_col],1'b1,1'b1,1'b1};
        endcase
    else
        Key_Row = Key_Row_r ; 

endmodule


验证测试脚本文件:

`timescale 1ns/1ns
//矩阵键盘的仿真模型
module Key_Board_tb;

    reg Clk;
    reg Rst_n;

    wire [3:0]Key_Row;
    wire [3:0]Key_Col;

    wire Key_flag;
    wire [3:0]Key_Value;

    key_board key_board_0(
	.Clk(Clk),
	.Rst_n(Rst_n),
	.Key_Board_Row_i(Key_Row),

	.Key_flag(Key_flag),
	.Key_Value(Key_Value),
	.Key_Board_Col_o(Key_Col)
    );

    Key_Board_model Key_Board_model_inst(Key_Col,Key_Row);

    initial Clk =1 ;
    always #10 Clk = ~Clk ;

    initial begin
        Rst_n =0;
        #200
        Rst_n =1;
    end

endmodule





FPGA 上拉设置问题 (注:不设置矩阵键盘测试无法正常工作,开始被坑了很多遍)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ormashuQ-1631430335818)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213358925.png)]

step1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jm0H98Uj-1631430335819)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213452072.png)]

step2:

在这里插入图片描述

step3:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsc2vOVz-1631430335820)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213600534.png)]

step4:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4LbAMsOt-1631430335821)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213656950.png)]

step5:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfxVTcqw-1631430335821)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213750711.png)]

最终配置效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JA5ZOWJU-1631430335822)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210606213828750.png)]

  • 2
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KEY_4x4扫描键盘FPGAVerilog逻辑源码Quartus工程文件,FPGA为CYCLONE4系列中的EP4CE6E22C8. 完整的工程文件,可以做为你的学习设计参考。 module KEY_4x4 ( input sys_clk, //50MHZ input sys_rst_n , input [3:0] key_row , //�� //output wire [3:0] key_col , //�� output reg [3:0] key_col , //�� output wire [7:0] LED , //��ʾ��ֵ output reg [3:0] key_value //��ֵ ); reg [5:0] count;//delay_20ms reg [2:0] state; //״̬��־ reg key_flag; //������־λ reg clk_500khz; //500KHZʱ���ź� reg [3:0] key_col_reg; //�Ĵ�ɨ����ֵ reg [3:0] key_row_reg; //�Ĵ�ɨ����ֵ always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin clk_500khz <= 0 ; count<=0 ; end else begin if ( count >= 50 ) begin clk_500khz <= ~clk_500khz ; count<=0; end else count <= count + 1; end end always @(posedge clk_500khz or negedge sys_rst_n) if(!sys_rst_n) begin key_col<=4'b0000; state<=0; end else begin case (state) 0: begin key_col[3:0]<=4'b0000; key_flag<=1'b0; if(key_row[3:0]!=4'b1111) begin //�м����£�ɨ����һ�� state<=1; key_col[3:0]<=4'b1110; end else state<=0; end 1: begin if(key_row[3:0]!=4'b1111) //�ж��Ƿ��ǵ�һ�� state<=5; else begin state<=2; key_col[3:0]<=4'b1101; //ɨ���ڶ��� end end 2: begin if(key_row[3:0]!=4'b1111) //�ж��Ƿ��ǵڶ��� state<=5; else begin //ɨ�������� state<=3; key_col[3:0]<=4'b1011; end end 3: begin if(key_row[3:0]!=4'b1111) //�ж��Ƿ��ǵ���һ�� state<=5; else begin state<=4; key_col[3:0]<=4'b0111; end //ɨ�������� end 4: begin if (key_row

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值