按键消抖
1、行列按键基本原理
1.1 原理图
第一行是高电平时,假如key2闭合,那么列输出就是0100
第二行是高电平时,假如key8闭合,那么列输出就是0001
1.2 实物图
按键是图片右下角4*4的按键
行:R10、P10、M6、K3
列:T10、R11、T12、R12
可以对按键进行任意编号
1.3 按键消抖
按键过程:按下抖动可能花销5-20ms,
将按下抖动+稳定期+释放抖动 转化成一个标准的矩形波(如上面波形);使用时钟是20ms的移位寄存器可以满足取到稳定期的信号
1.4 按键消抖设计
用数码管显示我们的按键,比如;按第0个按键,数码管最低位显示0;按第10个按键,数码管最低位显示A;
去抖的设计是:声明3个寄存器btn0、btn1、btn2,并将它们组合成移位寄存器,将移位方向定义为btn0->btn1->bun2
always@ (posedge btn_clk)
begin
btn0<=btn;
btn1<=btn0;
btn2<=btn1;
end
将按键输入送btn0。每隔20ms执行一次移位。
在每20ms后,btn0中存储的是当前的按键电平,btn1中存储的是20ms之前的按键电平,btn2中存储的是40ms之前的按键电平
assign btn_out=(btn2&btn1&btn0)|(~btn2&btn1&btn0)|(btn2&btn1&~btn0);
2、行列按键实践
2.1 行列代码
代码如下(示例):
reg[3:0] row=4'b0001;
always @ (posedge clk)begin
if (row[3:0]==4'b1000)
row[3:0]=4'b0001;
else
row[3:0]=row[3:0]<<1;
end
always @ (negedge clk)begin
case (row[3:0])
4'b0001:begin btn[3:0]=col;end
4'b0010:begin btn[7:4]=col;end
4'b0100:begin btn[11:8]=col;end
4'b1000:begin btn[15:12]=col;end
default:btn=0;
endcase
end
2.2 功能代码
2.2.1 分频代码
// Description:分频时钟
module divclk(
clk,clk_ms,btnclk
);
input clk;
output clk_ms,btnclk;
reg [31:0] cnt1 = 0;
reg [31:0] btnclk_cnt = 0;
reg clk_ms = 0;//1ms的时钟
reg btnclk = 0;//20ms按键消抖的时钟
//***************1ms的时钟**********************
always @ (posedge clk)begin //系统时钟分频50M/1000=50000 1000Hz
if(cnt1==26'd25000)begin
clk_ms = ~clk_ms;
cnt1 = 0;
end
else
cnt1 = cnt1 + 1'b1;
end
//***************1ms的时钟**********************
//***************20ms的时钟**********************
always @ (posedge clk)begin//20ms 50M/50=100000 50Hz
if(btnclk_cnt==500000)begin
btnclk = ~btnclk;
btnclk_cnt = 0;
end
else
btnclk_cnt = btnclk_cnt + 1'b1;
end
//***************20ms的时钟**********************
endmodule
2.2.2 按键消抖代码
// Description: 按键消抖模块
module v_ajxd(
input clk,
input btn_clk,//20ms时钟
input [3:0]col,//列(输入是一行一行的扫描)
output [3:0]row,//行,注意,这里是输出
output [15:0]btn_out//处理之后的按键输出
);
reg [15:0] btn = 0;
reg [15:0] btn0 = 0;
reg [15:0] btn1 = 0;
reg [15:0] btn2 = 0;
reg [3:0] row = 4'b0001;
//**************对每一行按键扫描*************
always @ (posedge clk)begin
if(row[3:0]==4'b1000)
row[3:0] = 4'b0001;
else
row[3:0] = row[3:0]<<1;
end
//**************对每一行按键扫描*************
//**************读取值*************
always @ (negedge clk)begin
case (row[3:0])
4'b0001:begin btn[3:0] = col ;end
4'b0010:begin btn[7:4] = col ;end
4'b0100:begin btn[11:8] = col ;end
4'b1000:begin btn[15:12] = col ;end
default:begin btn =0 ;end
endcase
end
//**************读取值*************
assign btn_out=(btn2&btn1&btn0)|(~btn2&btn1&btn0)|(btn2&btn1&~btn0);
//**************移位*************
always @ (posedge btn_clk)begin
btn0 <= btn;
btn1 <= btn0;
btn2 <= btn1;
end
//**************移位*************
endmodule
2.2.3 显示模块代码
// Description: 数码管显示模块
module v_disp(
input clk,//输入是50MHz的时钟
input rst,
input [23:0]dispdate,
output [7:0]seg,
output [5:0]an
);
reg [14:0] divclk_cnt = 0; //分频计数值
reg divclk = 0; //分频后的时钟
reg [7:0] seg = 8'b0; //段码
reg [5:0] an = 6'b111110; //位码
reg [3:0]disp_dat = 0; //要显示的数据
reg [2:0]disp_bit = 0; //要显示的位
parameter maxcnt=25000;
always @ (posedge clk)begin
if(divclk_cnt==maxcnt)begin
divclk = ~divclk;
divclk_cnt = 0;
end
else
divclk_cnt = divclk_cnt + 1'b1;
end
always @ (posedge divclk)begin
if(disp_dat>=5)
disp_bit = 0;
else
disp_bit = disp_bit + 1'b1;
case (disp_bit)//**********位码***************
3'h0:begin disp_dat = dispdate[3:0];an = 6'b111110;end//显示第一个数码管,低电平有效
3'h1:begin disp_dat = dispdate[7:4];an = 6'b111101;end//显示第二个数码管,低电平有效
3'h2:begin disp_dat = dispdate[11:8];an = 6'b111011;end//显示第三个数码管,低电平有效
3'h3:begin disp_dat = dispdate[15:12];an = 6'b110111;end//显示第四个数码管,低电平有效
3'h4:begin disp_dat = dispdate[19:16];an = 6'b101111;end//显示第五个数码管,低电平有效
3'h5:begin disp_dat = dispdate[23:20];an = 6'b011111;end//显示第六个数码管,低电平有效
default:begin disp_dat = dispdate[15:12];an = 6'b111111;end//显示第四个数码管,低电平有效
endcase
end
//**********段码***************
always @ (disp_dat)begin
case (disp_dat)
4'h0 : seg = 8'h3f; //显示"0",abcdrfg_dp分别是1111_1100,二进制倒过来看是0011_1111= 8'h3f
4'h1 : seg = 8'h06;
4'h2 : seg = 8'h5b;
4'h3 : seg = 8'h4f;
4'h4 : seg = 8'h66;
4'h5 : seg = 8'h6d; //显示"5" abcdrfg_dp分别是1011_0110,二进制倒过来看是0110_1101= 8'h6d
4'h6 : seg = 8'h7d; //显示"6"
4'h7 : seg = 8'h07; //显示"7"
4'h8 : seg = 8'h7f; //显示"8"
4'h9 : seg = 8'h6f; //显示"9"
4'ha : seg = 8'h77; //显示"a"
4'hb : seg = 8'h7c; //显示"b"
4'hc : seg = 8'h39; //显示"c"
4'hd : seg = 8'h5e; //显示"d"
4'he : seg = 8'h79; //显示"e"
4'hf : seg = 8'h71; //显示"f"
endcase
end
//**********段码***************
endmodule
2.2.4 顶层模块代码
module v1(
input clk,
input [11:0]sw,
input [3:0]col,
output [3:0]row,
output [11:0]led,
output [7:0]seg,//段码
output [5:0]an//位码
);
wire clk_ms,clk_20ms;
wire [15:0]btnout;
reg [11:0] led = 12'b0;
reg [23:0] showdat = 0;
v_disp a(//例化显示模块
.clk(clk),
.rst(0),
.dispdate(showdat),
.seg(seg),
.an(an)
);
divclk my_divclk(//调用分频模块
.clk(clk),
.clk_ms(clk_ms),
.btnclk(clk_20ms)
);
reg[4:0] i=0;
always @ (posedge clk_ms)begin
led<=btnout[11:0];//led调试使用,用led显示一下按键
for(i=0;i<=15;i=i+1)
if(btnout[i]==1)
showdat <= i;
end
v_ajxd ajxd_uut(//调用按键消抖
.clk(clk_ms),
.btn_clk(clk_20ms),
.col(col),
.row(row),
.btn_out(btnout)
);
endmodule