矩阵键盘FPGA设计与实现

目录

顶层:

矩阵键盘(消抖):

数码管:


已成功上板实现!

顶层:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/12/28 16:28:25
// Design Name: 
// Module Name: top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module top(
    input           clk_in_p,
    input           clk_in_n,
    
    input I_rst, // 复位信号(低有效)
    
    input   [3:0]   I_col,
    output  [3:0]   O_raw,
    
    //seg_led interface
    output [7:0] seg_sel , // 数码管位选信号
    output [7:0] seg_led // 数码管段选信号
);
 wire I_clk;
 IBUFDS clkinl_buf(
 .O      (I_clk),
 .I      (clk_in_p),
 .IB     (clk_in_n)
 );
    
    wire [4:0]S_led_data;
martix u_martix(
    .I_sys_clk  (I_clk), 
    .I_sys_rst_n(I_rst),
    .I_key_in   (I_col),
    .O_key_out  (O_raw),
    .O_led      (S_led_data)
    );
    
seg_led u_seg_led(
    .I_clk    (I_clk),
    .I_rst    (!I_rst),
    .I_data   (S_led_data),

    .O_seg_sel(seg_sel),
    .O_seg_led(seg_led)
);

    
endmodule

矩阵键盘(消抖):

module martix
#(
    parameter   CNT_MAX=24'd2000_000//扫描时间 20ms
)
(
    input    wire           I_sys_clk     ,
    input    wire           I_sys_rst_n   ,
    input    wire    [3:0]  I_key_in      ,//从键盘输入信号
    output   reg     [3:0]  O_key_out     , //输出信号到板子
    output   reg     [4:0]  O_led
);

reg     [31:0]      cnt         ;
reg                 cnt_clk     ;
reg     [1:0]       q           ;

always @ (posedge I_sys_clk or negedge I_sys_rst_n)begin
    if(!I_sys_rst_n)
        cnt<=32'd0;
    else if (cnt==CNT_MAX)
        cnt<=32'd0;
    else  cnt<=cnt+32'd1;
end

always @ (posedge I_sys_clk or negedge I_sys_rst_n)begin
    if(!I_sys_rst_n)begin
        cnt_clk<=1'b0;
    end else if (cnt==CNT_MAX-16'd1)begin
        cnt_clk<=1'b1;
    end else begin
        cnt_clk<=1'd0;
    end
end

always @ (posedge cnt_clk or negedge I_sys_rst_n)begin
    if(!I_sys_rst_n)begin
        O_key_out <= 4'b1110;
    end else begin
        O_key_out <= {O_key_out[2:0],O_key_out[3]};
    end
end

always @ (posedge cnt_clk or negedge I_sys_rst_n)begin
    if(!I_sys_rst_n)begin
        O_led<= 5'd16;
    end else begin    
    case ({O_key_out,S_key_in})
    //按下第1个按键后则全灭,0000 -> 1111,按下第16个按键后全部的灯亮,1111 -> 0000。
    //每一行扫描信号,对应的扫描信号灯输出
    8'b1110_1110:begin O_led<=5'd0;  end
    8'b1110_1101:begin O_led<=5'd1;  end
    8'b1110_1011:begin O_led<=5'd2;  end
    8'b1110_0111:begin O_led<=5'd3;  end
    
    8'b1101_1110:begin O_led<=5'd4;  end
    8'b1101_1101:begin O_led<=5'd5;  end
    8'b1101_1011:begin O_led<=5'd6;  end
    8'b1101_0111:begin O_led<=5'd7;  end
    
    8'b1011_1110:begin O_led<=5'd8;  end
    8'b1011_1101:begin O_led<=5'd9;  end
    8'b1011_1011:begin O_led<=5'd10;  end
    8'b1011_0111:begin O_led<=5'd11;  end
    
    8'b0111_1110:begin O_led<=5'd12;  end
    8'b0111_1101:begin O_led<=5'd13;  end
    8'b0111_1011:begin O_led<=5'd14;  end
    8'b0111_0111:begin O_led<=5'd15;  end
    
    default:     begin O_led<=5'd16;  end//没有按键按下灯不亮
    endcase
    end
end
endmodule

数码管:

`timescale 1ns / 1ps

module seg_led(
    input               I_clk   , // 时钟信号
    input               I_rst , // 复位信号
    input       [31:0]  I_data  , // 8 位数码管要显示的数值

    output  reg [7:0]   O_seg_sel ,// 数码管位选,最左侧数码管为最高位
    output  reg [7:0]   O_seg_led  // 数码管段选
 );
 
//每当计数器对数码管驱动时钟计数时间达 1ms,输出一个时钟周期的脉冲信号
    reg [15:0]  S_cnt0;
    reg         S_flag;
 always @ (posedge I_clk) begin
    if (I_rst) begin
        S_cnt0 <= 16'b0;
        S_flag <= 1'b0;
    end else if (S_cnt0 < 99_999) begin//(1000/(1/100) = 100_000)
        S_cnt0 <= S_cnt0 + 1'b1;
        S_flag <= 1'b0;
    end else begin
        S_cnt0 <= 16'b0;
        S_flag <= 1'b1;
    end
 end
 //S_cnt_sel 从 0 计数到 7,用于选择当前处于显示状态的数码管
    reg [3:0]   S_cnt_sel;
 always @ (posedge I_clk) begin
    if (I_rst)begin
        S_cnt_sel <= 3'b0;
    end else if(S_flag) begin//输出1ms标志信号
        if(S_cnt_sel < 3'd7)begin
            S_cnt_sel <= S_cnt_sel + 1'b1;
        end else begin
            S_cnt_sel <= 3'b0;
        end
    end else begin
        S_cnt_sel <= S_cnt_sel;
    end
 end

 //控制数码管位选信号,使 8 位数码管轮流显示
    reg [3:0]   S_num_disp;
    
 always @ (posedge I_clk) begin
    if(I_rst) begin
        O_seg_sel   <= 8'b11111111; //位选信号低电平有效
        S_num_disp  <= 4'b0; //数码管显示数据
    end else begin
        case (S_cnt_sel)
            3'd0 :begin
            O_seg_sel   <= 8'b11111110; //显示数码管最低
            S_num_disp  <= I_data[3:0] ; //显示的数据
            end
            3'd1 :begin
            O_seg_sel   <= 8'b11111101; //显示数码管第 1 位
            S_num_disp  <= I_data[7:4] ;
            end
            3'd2 :begin
            O_seg_sel   <= 8'b11111011; //显示数码管第 2 位
            S_num_disp  <= I_data[11:8];
            end
            3'd3 :begin
            O_seg_sel   <= 8'b11110111; //显示数码管第 3 位
            S_num_disp  <= I_data[15:12];
            end
            3'd4 :begin
            O_seg_sel   <= 8'b11101111; //显示数码管第 4 位
            S_num_disp  <= I_data[19:16];
            end
            3'd5 :begin
            O_seg_sel   <= 8'b11011111; //显示数码管第 5 位
            S_num_disp  <= I_data[23:20];
            end
            3'd6 :begin
            O_seg_sel   <= 8'b10111111; //显示数码管第 5 位
            S_num_disp  <= I_data[27:24];
            end
            3'd7 :begin
            O_seg_sel   <= 8'b01111111; //显示数码管第 5 位
            S_num_disp  <= I_data[31:28];
            end
            default :begin
            O_seg_sel   <= 8'b11111111;
            S_num_disp  <= 4'b0;
            end
        endcase
    end
 end
//控制数码管段选信号,显示字符(共阴极)
 always @ (posedge I_clk) begin
    if (I_rst)
        O_seg_led <= 8'd0;
    else begin
        case (S_num_disp)
            4'd0 : O_seg_led <= {8'b00111111}; //显示数字 0
            4'd1 : O_seg_led <= {8'b00000110}; //显示数字 1
            4'd2 : O_seg_led <= {8'b01011011}; //显示数字 2
            4'd3 : O_seg_led <= {8'b01001111}; //显示数字 3
            4'd4 : O_seg_led <= {8'b01100110}; //显示数字 4
            4'd5 : O_seg_led <= {8'b01101101}; //显示数字 5
            4'd6 : O_seg_led <= {8'b01111101}; //显示数字 6
            4'd7 : O_seg_led <= {8'b00000111}; //显示数字 7
            4'd8 : O_seg_led <= {8'b01111111}; //显示数字 8
            4'd9 : O_seg_led <= {8'b01101111}; //显示数字 9
            4'd10 : O_seg_led <= {8'b01110111}; //显示a
            4'd11 : O_seg_led <= {8'b01111100}; //显示b
            4'd12 : O_seg_led <= {8'b00111001}; //显示c
            4'd13 : O_seg_led <= {8'b01011110}; //显示d
            4'd14 : O_seg_led <= {8'b01111001}; //显示e
            4'd15 : O_seg_led <= {8'b01110001}; //显示f
        default:
        O_seg_led <= {8'b00000000};//不显示任何字符
        endcase
    end
 end

endmodule

矩阵键盘(持续输出或只输出一次)

/*
因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态
在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样
周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间
对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理
*/
module Array_KeyBoard #
(
//250_000//为了缩短仿真用时,仿真的时候用5;实际测试的时候用250_000
	parameter			CNT_200HZ = 5 
)
(
	input				clk       ,//50MHz系统时钟
	input				rst_n     ,//系统复位,低电平有效
	input		[3:0]	col       ,//矩阵键盘列输入
	output	reg	[3:0]	row       ,//矩阵键盘行输出
	output	reg	[15:0]	key_out   ,//按键值(只要按键按下就一直输出)
	output		[15:0]	key_pulse  //按键值(不管按键按下多长时间,只输出一次) 
);
	
	localparam			STATE0 = 2'b00;//四行对应四个状态
	localparam			STATE1 = 2'b01;//四行对应四个状态
	localparam			STATE2 = 2'b10;//四行对应四个状态
	localparam			STATE3 = 2'b11;//四行对应四个状态

	//计数器计数分频实现5ms周期信号clk_200hz
	reg		[15:0]		cnt;
	reg					clk_200hz;
	always@(posedge clk or negedge rst_n) begin  //复位时计数器cnt清零,clk_200hz信号起始电平为低电平
		if(!rst_n) begin
			cnt <= 16'd0;
			clk_200hz <= 1'b0;
		end else begin
			if(cnt >= ((CNT_200HZ>>1) - 1)) begin  //数字逻辑中右移1位相当于除2
				cnt <= 16'd0;
				clk_200hz <= ~clk_200hz;  //clk_200hz信号取反
			end else begin
				cnt <= cnt + 1'b1;
				clk_200hz <= clk_200hz;
			end
		end
	end
	
	reg		[1:0]		c_state;
	//状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效
	always@(posedge clk_200hz or negedge rst_n) begin
		if(!rst_n) begin
			c_state <= STATE0;
			row <= 4'b1110;
		end else begin
			case(c_state)
				//状态c_state跳转及对应状态下矩阵按键的row输出
				STATE0: begin c_state <= STATE1; row <= 4'b1101; end
				STATE1: begin c_state <= STATE2; row <= 4'b1011; end
				STATE2: begin c_state <= STATE3; row <= 4'b0111; end
				STATE3: begin c_state <= STATE0; row <= 4'b1110; end
				default:begin c_state <= STATE0; row <= 4'b1110; end
			endcase
		end
	end
	
	reg	[15:0]	key,key_r;
	//因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环
	always@(negedge clk_200hz or negedge rst_n) begin
		if(!rst_n) begin
			key_out <= 16'hffff; key_r <= 16'hffff; key <= 16'hffff; 
		end else begin
			case(c_state)
				//采集当前状态的列数据赋值给对应的寄存器位
				//对键盘采样数据进行判定,连续两次采样低电平判定为按键按下
				STATE0: begin key_out[ 3: 0] <= key_r[ 3: 0]|key[ 3: 0]; key_r[ 3: 0] <= key[ 3: 0]; key[ 3: 0] <= col; end
				STATE1: begin key_out[ 7: 4] <= key_r[ 7: 4]|key[ 7: 4]; key_r[ 7: 4] <= key[ 7: 4]; key[ 7: 4] <= col; end
				STATE2: begin key_out[11: 8] <= key_r[11: 8]|key[11: 8]; key_r[11: 8] <= key[11: 8]; key[11: 8] <= col; end
				STATE3: begin key_out[15:12] <= key_r[15:12]|key[15:12]; key_r[15:12] <= key[15:12]; key[15:12] <= col; end
				default:begin key_out <= 16'hffff; key_r <= 16'hffff; key <= 16'hffff; end
			endcase
		end
	end
	
	reg		[15:0]		key_out_r;
	always @ ( posedge clk  or  negedge rst_n )
		if (!rst_n) key_out_r <= 16'hffff;
		else  key_out_r <= key_out;   //将前一刻的值延迟锁存
		
	assign key_pulse= key_out_r & ( ~key_out);   //通过前后两个时刻的值判断
	
endmodule

module Decoder
(
input		   clk,
input		   rst_n,
input		   [15:0] key_pulse,
output reg  [7:0]	 seg_data	//表示按下的按键数是多少

);

always@(posedge clk or negedge rst_n) 
	begin
	if(!rst_n) 
		begin
		seg_data <= 8'h00;   //清零
		end 
	else if(rst_n)
		begin case(key_pulse)  //key_pulse脉宽等于clk_in的周期,用于表示摁下第几个按键
			16'h0001: seg_data <= 8'h01;  //1
			16'h0002: seg_data <= 8'h02; 	//2
			16'h0004: seg_data <= 8'h03;  //3
			16'h0008: seg_data <= 8'h04;	//4
			16'h0010: seg_data <= 8'h05;  //5
			16'h0020: seg_data <= 8'h06; 	//6
			16'h0040: seg_data <= 8'h07; 	//7
			16'h0080: seg_data <= 8'h08; 	//8
			16'h0100: seg_data <= 8'h09;  //9
			16'h0200: seg_data <= 8'h10; 	//10
			16'h0400: seg_data <= 8'h11;  //11
			16'h0800: seg_data <= 8'h12;  //12
			16'h1000: seg_data <= 8'h13;  //13
			16'h2000: seg_data <= 8'h14;  //14
			16'h4000: seg_data <= 8'h15;  //15
			16'h8000: seg_data <= 8'h16;  //16
			default:  seg_data <= 8'h00;  //无按键按动
		 endcase 
	  end	
end

endmodule

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值