在做矩阵键盘的驱动时,写了好多次都没有成功,出现了各种奇奇怪怪的错误。最后在网上看了无数篇的博客和讲解,终于搞懂了原理。
矩阵键盘的电路原理图如图所示(在网上看到别人的就随手down了下来):
矩阵键盘一共十六个按键,共接出八个引脚(四个行四个列),其中四个输入,四个输出。一般都把四个行作为输入的四条线,把四个列作为输出。
先将四条列线全部拉低,若有按键被按下,电路接通,则对应的行线也被拉低,输出低电平,其余行线为高电平。然后在依次拉低每一条列线,其余列线置高电平,来判断是哪一根列线使输出行线拉低,则跟据输入输出八根线的电位情况,就可以判断出哪个按键被按下。
首先要明确程序要实现一个什么样的功能,这里我写的是一个矩阵键盘驱动然后输出键值并通过一位数码管进行显示。
在明确了程序功能后,就要确定功能引脚及接口。我一共使用了两个输入(一个是系统脉冲,一个是四位行输入接口),三个输出接口(一个是四位列输出接口,一个是数码管位接口,一个是数码管的八位段接口)
module key_board(
input clk,
input [3:0] row, // 矩阵键盘 行
output reg [3:0] col, // 矩阵键盘 列
output reg [7:0] seg,
output reg sel
);
接下来就是定义了各种中间信号还有定义的数码管常量(分别根据八段数码管值定义了从0~f的十六进制的数字显示):
reg [19:0] cnt; //计数器
reg [3:0] key_out; // 键盘值
reg [5:0] current_state, next_state; // 现态、次态
reg key_pressed_flag; // 键盘按下标志
reg [3:0] col_val, row_val; // 列行值寄存器
reg key_clk; //分频脉冲
//*************数码管数值定义******************//
parameter disp0=8'b1100_0000;
parameter disp1=8'b1111_1001;
parameter disp2=8'b1010_0100;
parameter disp3=8'b1011_0000;
parameter disp4=8'b1001_1001;
parameter disp5=8'b1001_0010;
parameter disp6=8'b1000_0010;
parameter disp7=8'b1111_1000;
parameter disp8=8'b1000_0000;
parameter disp9=8'b1001_0000;
parameter dispa=8'b1000_1000;
parameter dispb=8'b1000_0011;
parameter dispc=8'b1010_0111;
parameter dispd=8'b1010_0001;
parameter dispe=8'b1000_0110;
parameter dispf=8'b1000_0011;
程序定义了六个状态的状态机,整个程序的重点就在于状态机的列状态切换。六个状态分别为 “没有按键按下”、“扫描第1列”、“扫描第2列”、“扫描第3列”、“扫描第4列”、“有按键按下”:
//*******************状态机定义*********************//
parameter IDLE = 6'b000_001; // 没有按键按下
parameter SCAN_COL0 = 6'b000_010; // 扫描第0列
parameter SCAN_COL1 = 6'b000_100; // 扫描第1列
parameter SCAN_COL2 = 6'b001_000; // 扫描第2列
parameter SCAN_COL3 = 6'b010_000; // 扫描第3列
parameter KEY_PRESSED = 6'b100_000; // 有按键按下
状态切换考虑到按键的消抖,所以通过分频产生21ms的脉冲来作为状态切换的时间间隔。分频模块如下:
//***************分频*******************//
always @ (posedge clk)
begin
if(cnt<=20'd1048_500) //分频21ms
cnt <= cnt + 1'b1;
else begin
cnt<=0;
key_clk<=~key_clk;
end
end
//****************状态切换***************//
always @ (posedge key_clk)
current_state <= next_state;
当有按键被按下时,行输入值必定有一根线被拉低,所以输入不全为高电平,据此来判断是否有按键按下。当行输入值不全为1时,说明有按键按下,则将状态机切换到有按键被按下的状态,将此时的行列值都放到寄存器中:
//************状态机切换************************//
always @ *
case (current_state)
IDLE : // 没有按键按下
if (row != 4'hF)
next_state = SCAN_COL0;
else
next_state = IDLE;
SCAN_COL0 : // 扫描第0列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL1;
SCAN_COL1 : // 扫描第1列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL2;
SCAN_COL2 : // 扫描第2列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL3;
SCAN_COL3 : // 扫描第3列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = IDLE;
KEY_PRESSED : // 有按键按下
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = IDLE;
endcase
行输入值不全为1 的前提一定是对应的列线有一根被拉低了,然后在扫描判断是哪根线被拉低。先将所有列线拉低,则一旦有按键被按下,行输入就不全为1,之后开始扫描每一列。扫描到对应列就将除该列的其他列都拉高,若行线仍全不为0则可判断是该列上的按键被按下,否则进行下一列的扫描:
//***************赋列值***********************//
always @ (posedge key_clk)
case (next_state)
IDLE : // 没有按键按下
begin
col <= 4'h0;
key_pressed_flag <= 0; // 清键盘按下标志
end
SCAN_COL0 : // 扫描第0列
col <= 4'b1110;
SCAN_COL1 : // 扫描第1列
col <= 4'b1101;
SCAN_COL2 : // 扫描第2列
col <= 4'b1011;
SCAN_COL3 : // 扫描第3列
col <= 4'b0111;
KEY_PRESSED : // 有按键按下
begin
col_val <= col; // 锁存列值
row_val <= row; // 锁存行值
key_pressed_flag <= 1; // 置键盘按下标志
end
endcase
在得到行列线的寄存值后,就可以对键值的输出做赋值:
//********************键值输出*************************//
always @ (posedge key_clk)
if (key_pressed_flag)
case ({col_val, row_val})
8'b1110_1110 : key_out <= 4'h0;
8'b1110_1101 : key_out <= 4'h4;
8'b1110_1011 : key_out <= 4'h8;
8'b1110_0111 : key_out <= 4'hC;
8'b1101_1110 : key_out <= 4'h1;
8'b1101_1101 : key_out <= 4'h5;
8'b1101_1011 : key_out <= 4'h9;
8'b1101_0111 : key_out <= 4'hD;
8'b1011_1110 : key_out <= 4'h2;
8'b1011_1101 : key_out <= 4'h6;
8'b1011_1011 : key_out <= 4'hA;
8'b1011_0111 : key_out <= 4'hE;
8'b0111_1110 : key_out <= 4'h3;
8'b0111_1101 : key_out <= 4'h7;
8'b0111_1011 : key_out <= 4'hB;
8'b0111_0111 : key_out <= 4'hF;
endcase
根据输出键值,在对数码管显示的值进行定义:
always@(posedge clk)
begin
sel<=0;
case(key_out)
5'd0:seg<=disp0;
5'd1:seg<=disp1;
5'd2:seg<=disp2;
5'd3:seg<=disp3;
5'd4:seg<=disp4;
5'd5:seg<=disp5;
5'd6:seg<=disp6;
5'd7:seg<=disp7;
5'd8:seg<=disp8;
5'd9:seg<=disp9;
5'd10:seg<=dispa;
5'd11:seg<=dispb;
5'd12:seg<=dispc;
5'd13:seg<=dispd;
5'd14:seg<=dispe;
5'd15:seg<=dispf;
5'd16:seg<=disp0;
endcase
end
endmodule
下面贴出完成代码:
module key_board(
input clk,
input [3:0] row, // 矩阵键盘 行
output reg [3:0] col, // 矩阵键盘 列
output reg [7:0] seg,
output reg sel
);
reg [19:0] cnt; //计数器
reg [3:0] key_out; // 键盘值
reg key_pressed_flag; // 键盘按下标志
reg [3:0] col_val, row_val; // 列行值寄存器
reg key_clk; //分频脉冲
//*************数码管数值定义******************//
parameter disp0=8'b1100_0000;
parameter disp1=8'b1111_1001;
parameter disp2=8'b1010_0100;
parameter disp3=8'b1011_0000;
parameter disp4=8'b1001_1001;
parameter disp5=8'b1001_0010;
parameter disp6=8'b1000_0010;
parameter disp7=8'b1111_1000;
parameter disp8=8'b1000_0000;
parameter disp9=8'b1001_0000;
parameter dispa=8'b1000_1000;
parameter dispb=8'b1000_0011;
parameter dispc=8'b1010_0111;
parameter dispd=8'b1010_0001;
parameter dispe=8'b1000_0110;
parameter dispf=8'b1000_0011;
//***************分频*******************//
always @ (posedge clk)
begin
if(cnt<=20'd1048_500) //分频21ms
cnt <= cnt + 1'b1;
else begin
cnt<=0;
key_clk<=~key_clk;
end
end
//*******************状态机定义*********************//
parameter IDLE = 6'b000_001; // 没有按键按下
parameter SCAN_COL0 = 6'b000_010; // 扫描第0列
parameter SCAN_COL1 = 6'b000_100; // 扫描第1列
parameter SCAN_COL2 = 6'b001_000; // 扫描第2列
parameter SCAN_COL3 = 6'b010_000; // 扫描第3列
parameter KEY_PRESSED = 6'b100_000; // 有按键按下
reg [5:0] current_state, next_state; // 现态、次态
always @ (posedge key_clk)
current_state <= next_state;
//************状态机切换************************//
always @ *
case (current_state)
IDLE : // 没有按键按下
if (row != 4'hF)
next_state = SCAN_COL0;
else
next_state = IDLE;
SCAN_COL0 : // 扫描第0列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL1;
SCAN_COL1 : // 扫描第1列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL2;
SCAN_COL2 : // 扫描第2列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL3;
SCAN_COL3 : // 扫描第3列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = IDLE;
KEY_PRESSED : // 有按键按下
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = IDLE;
endcase
//***************赋列值***********************//
always @ (posedge key_clk)
case (next_state)
IDLE : // 没有按键按下
begin
col <= 4'h0;
key_pressed_flag <= 0; // 清键盘按下标志
end
SCAN_COL0 : // 扫描第0列
col <= 4'b1110;
SCAN_COL1 : // 扫描第1列
col <= 4'b1101;
SCAN_COL2 : // 扫描第2列
col <= 4'b1011;
SCAN_COL3 : // 扫描第3列
col <= 4'b0111;
KEY_PRESSED : // 有按键按下
begin
col_val <= col; // 锁存列值
row_val <= row; // 锁存行值
key_pressed_flag <= 1; // 置键盘按下标志
end
endcase
//********************键值输出*************************//
always @ (posedge key_clk)
if (key_pressed_flag)
case ({col_val, row_val})
8'b1110_1110 : key_out <= 4'h0;
8'b1110_1101 : key_out <= 4'h4;
8'b1110_1011 : key_out <= 4'h8;
8'b1110_0111 : key_out <= 4'hC;
8'b1101_1110 : key_out <= 4'h1;
8'b1101_1101 : key_out <= 4'h5;
8'b1101_1011 : key_out <= 4'h9;
8'b1101_0111 : key_out <= 4'hD;
8'b1011_1110 : key_out <= 4'h2;
8'b1011_1101 : key_out <= 4'h6;
8'b1011_1011 : key_out <= 4'hA;
8'b1011_0111 : key_out <= 4'hE;
8'b0111_1110 : key_out <= 4'h3;
8'b0111_1101 : key_out <= 4'h7;
8'b0111_1011 : key_out <= 4'hB;
8'b0111_0111 : key_out <= 4'hF;
endcase
always@(posedge clk)
begin
sel<=0;
case(key_out)
5'd0:seg<=disp0;
5'd1:seg<=disp1;
5'd2:seg<=disp2;
5'd3:seg<=disp3;
5'd4:seg<=disp4;
5'd5:seg<=disp5;
5'd6:seg<=disp6;
5'd7:seg<=disp7;
5'd8:seg<=disp8;
5'd9:seg<=disp9;
5'd10:seg<=dispa;
5'd11:seg<=dispb;
5'd12:seg<=dispc;
5'd13:seg<=dispd;
5'd14:seg<=dispe;
5'd15:seg<=dispf;
5'd16:seg<=disp0;
endcase
end
endmodule
【注】:个人学习记录,如有错误,望不吝赐教