项目简述
这是乐鑫提前批的一道笔试题,其实不算难,但是想在半小时内完成可能性非常小,所以乐鑫的笔试还是挺难的。
题目:请实现对4x4矩阵式键盘的按键识别,假设每次都是单按键输入,需要有去抖功能(持续20ms以上被认为是有效键值),模块时钟频率为1kHz,要求用状态机实现,定义状态,画出状态转移图,并用verilog完整描述该识别模块。矩阵式键盘电路结构参见下图,其中列线1-4由识别模块控制输出,行线5-8为识别模块的输入。
本次实验所用到的软硬件环境如下:
1、VIVADO 2019.1
2、Modelsim 10.7
求解过程
首先根据题意写出状态,然后画出状态转移图:
IDLE:自动跳转到P_FILTER状态;
P_FILTER:如果四条行线不全为1,代表有按键按下则跳转到DELAY状态,没有按键按下则停留在该状态;
DELAY:计数状态,计数20ms在这个过程中如果如果四条行线全为1,则跳转到P_FILTER状态,计数到20ms跳转到SCAN_C0状态,并将第 0 列拉低,进入 SCAN_C0 状态进行行扫描;
SCAN_C0:进行列扫描,来判断该列是否有按键被按下, 判断行输入是否等于 15,如果不等于则说明该列有按键被按下,否则将第 1 列拉低,状态跳转到 SCAN_C1状态进行行扫描;
SCAN_C1: 进行列扫描,来判断该列是否有按键被按下,判断行输入是否等于 15,如果不等于则说明该列有按键被按下,否则将第 2 列拉低,状态跳转到 SCAN_C3状态进行行扫描;
SCAN_C2: 进行列扫描,来判断该列是否有按键被按下, 判断行输入是否等于 15,如果不等于则说明该列有按键被按下,否则将第 3 列拉低,状态跳转到 SCAN_C3状态进行行扫描;SCAN_C3: 进行列扫描,来判断该列是否有按键被按下, 判断行输入是否等于 15,如果不等于则说明该列有按键被按下,状态跳转到WAIT_R状态;
WAIT_R:等待按键松开;
DELAY_P:等待按键松开过程稳定输出;
状态转移图如下:
经过上面的分析,我们便可以书写FPGA代码。
FPGA代码
key模块:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website : https://blog.csdn.net/zhangningning1996
// Module Name : key.v
// Create Time : 2020-06-16 14:39:12
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module key(
input sclk ,
input rst_n ,
input [ 3:0] key_row ,
output reg [ 3:0] key_col ,
output reg [ 3:0] key ,
output reg key_en
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
parameter DELAY_20MS = 20000-1 ;
parameter IDLE = 9'b000000001;
parameter P_FILTER = 9'b000000010;
parameter DELAY = 9'b000000100;
parameter SCAN_C0 = 9'b000001000;
parameter SCAN_C1 = 9'b000010000;
parameter SCAN_C2 = 9'b000100000;
parameter SCAN_C3 = 9'b001000000;
parameter WAIT_R = 9'b010000000;
parameter DELAY_P = 9'b100000000;
reg [ 8:0] state ;
reg [20:0] cnt_20ms ;
reg [ 3:0] key_row_c0 ;
reg [ 3:0] key_row_c1 ;
reg [ 3:0] key_row_c2 ;
reg [ 3:0] key_row_c3 ;
wire [15:0] key_row_c ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
assign key_row_c = {key_row_c3,key_row_c2,key_row_c1,key_row_c0};
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE : state <= P_FILTER;
P_FILTER: if(key_row != 4'hf)
state <= DELAY;
else
state <= state;
DELAY : if(key_row == 4'hf)
state <= P_FILTER;
else if(cnt_20ms == DELAY_20MS)
state <= SCAN_C0;
else
state <= state;
SCAN_C0 : state <= SCAN_C1;
SCAN_C1 : state <= SCAN_C2;
SCAN_C2 : state <= SCAN_C3;
SCAN_C3 : state <= WAIT_R;
WAIT_R : if(key_row == 4'hf)
state <= DELAY_P;
else
state <= state;
DELAY_P : if(cnt_20ms == DELAY_20MS)
state <= P_FILTER;
else if(key_row != 4'hf)
state <= WAIT_R;
else
state <= state;
default : state <= IDLE;
endcase
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_20ms <= 21'd0;
else if(state == DELAY || state == DELAY_P)begin
if(cnt_20ms == DELAY_20MS)
cnt_20ms <= DELAY_20MS;
else
cnt_20ms <= cnt_20ms + 1'b1;
end else
cnt_20ms <= 21'd0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c0 <= 4'hf;
else if(state == SCAN_C0)
key_row_c0 <= key_row;
else
key_row_c0 <= key_row_c0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c1 <= 4'hf;
else if(state == SCAN_C1)
key_row_c1 <= key_row;
else
key_row_c1 <= key_row_c1;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c2 <= 4'hf;
else if(state == SCAN_C2)
key_row_c2 <= key_row;
else
key_row_c2 <= key_row_c2;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c3 <= 4'hf;
else if(state == SCAN_C3)
key_row_c3 <= key_row;
else
key_row_c3 <= key_row_c3;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key <= 4'd0;
else case(key_row_c)
16'b1111_1111_1111_1110 : key <= 4'd0;
16'b1111_1111_1111_1101 : key <= 4'd4;
16'b1111_1111_1111_1011 : key <= 4'd8;
16'b1111_1111_1111_0111 : key <= 4'd12;
16'b1111_1111_1110_1111 : key <= 4'd1;
16'b1111_1111_1101_1111 : key <= 4'd5;
16'b1111_1111_1011_1111 : key <= 4'd9;
16'b1111_1111_0111_1111 : key <= 4'd13;
16'b1111_1110_1111_1111 : key <= 4'd2;
16'b1111_1101_1111_1111 : key <= 4'd6;
16'b1111_1011_1111_1111 : key <= 4'd10;
16'b1111_0111_1111_1111 : key <= 4'd14;
16'b1110_1111_1111_1111 : key <= 4'd3;
16'b1101_1111_1111_1111 : key <= 4'd7;
16'b1011_1111_1111_1111 : key <= 4'd11;
16'b0111_1111_1111_1111 : key <= 4'd15;
default : key <= 4'd0;
endcase
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_en <= 1'b0;
else if(state == DELAY_P && cnt_20ms == DELAY_20MS)
key_en <= 1'b1;
else
key_en <= 1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_col <= 4'd0;
else case(state)
DELAY : if(cnt_20ms == DELAY_20MS)
key_col <= 4'b1110;
else
key_col <= key_col;
SCAN_C0 : key_col <= 4'b1101;
SCAN_C1 : key_col <= 4'b1011;
SCAN_C2 : key_col <= 4'b0111;
default : key_col <= 4'd0;
endcase
endmodule
测试模块代码
key_tb模块:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : nnzhang1996@foxmail.com
// Website : https://blog.csdn.net/zhangningning1996
// Module Name : key_tb.v
// Create Time : 2020-06-16 15:47:58
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module key_tb();
reg sclk ;
reg rst_n ;
reg [ 3:0] Key_Row ;
reg [ 3:0] Key_Row_r ;
reg [15:0] myrand ;
reg [ 1:0] now_col,now_row ; //当前行与当前列
reg key_row_sel ; //行选通信号
wire [ 3:0] Key_Col ;
wire [ 3:0] key ;
wire key_en ;
initial begin
sclk = 1'b0;
key_row_sel <= 1'b0;
rst_n <= 1'b0;
Key_Row <= 4'hf;
#(1000)
rst_n <= 1'b1;
#(10000)
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);
end
always #5 sclk = ~sclk;
key key_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.key_row (Key_Row ),
.key_col (Key_Col ),
.key (key ),
.key_en (key_en )
);
task press_key;
input [1:0]row,col;
begin
key_row_sel = 0;
Key_Row_r = 4'b1111;
Key_Row_r[row] = 0;
now_row = row;
repeat(10)begin //重复 20 次,随机产生按键按下时 20 个不同的行状态
myrand = {$random} % 6556;
#myrand Key_Row_r[row] = ~Key_Row_r[row];
end
key_row_sel = 1; //将行选择信号设置为有效状态
now_col = col; //获取当前列状态
#22000000;
key_row_sel = 0;
Key_Row_r = 4'b1111;
repeat(20)begin //重复 20 次,随机产生按键松开时 20 个不同行状态
myrand = {$random} % 6556;
#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) //根据当前行输出,键盘的行信号
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
上面的测试代码写起来还是挺有难度的大家可以好好学习一下。
仿真结果
仿真结果如下:
总结
创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。为行业贡献及其微小的一部分。或者对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群: