基于FPGA的4x4矩阵键盘驱动调试

时间:2021-08-30 06:48:22

好久不见,因为博主最近两个月有点事情,加上接着考试,考完试也有点事情要处理,最近才稍微闲了一些,这才赶紧记录分享一篇博文。FPGA驱动4x4矩阵键盘。这个其实原理是十分简单,但是由于博主做的时候遇到了一些有意思的情况,所以我个人觉得值得记录分享一下。

首先找了本书看了下矩阵键盘的驱动原理,一般来说4x4矩阵键盘的原理图如下,有四根行线和四根列线,行选通和列选通可以确定键盘上的一个位置。从原理图上看出,在没有操作的情况下,行线上接了一个10K的上拉电阻接vcc,这使得键盘在没有按下时,四根行线始终是高电平。

基于FPGA的4x4矩阵键盘驱动调试

列线是由处理器输入给矩阵键盘,空闲状态下保持为0。也就是行空闲时输出给处理器为四个1,列空闲时由处理器输入给四个0。

当按下按键时,比如第一行第一个按键,对应的那一行导通输出为0,即row_data = 0111,此时由处理器逐渐输入列扫描信号由col_data = 0111——1110,当所按下按键为对应的那一行列的按键,矩阵键盘的行才会导通输出为0,否则会回到1111。其他按键类似,就是利用这个原理来驱动矩阵键盘。

基于FPGA的4x4矩阵键盘驱动调试

最后FPGA部分模块引脚设计如图,我们需要对按键进行消抖,和普通按键一样,采用20ms的延时对按键进行消抖,分为按下消抖和松开消抖,中间的状态转移,因为列信号需要输出判断行信号的变化,所以状态机状态转移用两个系统时钟周期跳转。采用状态机进行描述,状态转移图如下。

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

代码如下:(点击阅读原文查看博客)

 `timescale      1ns/1ps
// *********************************************************************************
// Project Name :
// Author : NingHeChuan
// Email : ninghechuan@foxmail.com
// Blogs : http://www.cnblogs.com/ninghechuan/
// File Name : Matrix_Key_Scan.v
// Module Name :
// Called By : // Abstract :
//
// CopyRight(c) 2018, NingHeChuan Studio..
// All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// 2018/7/28 NingHeChuan 1.0 Original
//
// ********************************************************************************* module Matrix_Key_Scan(
input clk, //50Mhz
input rst_n,
input [:] row_data,
output key_flag,
output reg [:] key_value,
output reg [:] col_data
); //FSM state
parameter SCAN_IDLE = 'b0000_0001;
parameter SCAN_JITTER1 = 'b0000_0010;
parameter SCAN_COL1 = 'b0000_0100;
parameter SCAN_COL2 = 'b0000_1000;
parameter SCAN_COL3 = 'b0001_0000;
parameter SCAN_COL4 = 'b0010_0000;
parameter SCAN_READ = 'b0100_0000;
parameter SCAN_JITTER2 = 'b1000_0000;
//
parameter DELAY_TRAN = ;
parameter DELAY_20MS = 1000_000;
//parameter DELAY_20MS = 100;//just test
reg [:] delay_cnt;
wire delay_done;
//
reg [:] pre_state;
reg [:] next_state;
reg [:] tran_cnt;
wire tran_flag;
//
reg [:] row_data_r;
reg [:] col_data_r;
// //-------------------------------------------------------
//delay 20ms
always @(posedge clk or negedge rst_n)begin
if(rst_n == 'b0)begin
delay_cnt <= 'd0;
end
else if(delay_cnt == DELAY_20MS)
delay_cnt <= 'd0;
else if(next_state == SCAN_JITTER1 | next_state == SCAN_JITTER2) begin
delay_cnt <= delay_cnt + 'b1;
end
else
delay_cnt <= 'd0;
end assign delay_done = (delay_cnt == DELAY_20MS - 'b1)? 1'b1: 'b0; //-------------------------------------------------------
//delay 2clk
always @(posedge clk or negedge rst_n)begin
if(rst_n == 'b0)begin
tran_cnt <= 'd0;
end
else if(tran_cnt == DELAY_TRAN)begin
tran_cnt <= 'd0;
end
else
tran_cnt <= tran_cnt + 'b1;
end assign tran_flag = (tran_cnt == DELAY_TRAN)? 'b1: 1'b0; //-------------------------------------------------------
//FSM step1
always @(posedge clk or negedge rst_n)begin
if(rst_n == 'b0)begin
pre_state <= SCAN_IDLE;
end
else if(tran_flag)begin
pre_state <= next_state;
end
else pre_state <= pre_state;
end //FSM step2
always @(*)begin
next_state = SCAN_IDLE;
case(pre_state)
SCAN_IDLE:
if(row_data != 'b1111)
next_state = SCAN_JITTER1;
else
next_state = SCAN_IDLE;
SCAN_JITTER1:
if(row_data != 'b1111 && delay_done == 1'b1)
next_state = SCAN_COL1;
else
next_state = SCAN_JITTER1;
SCAN_COL1:
if(row_data != 'b1111)//如果row_data是全1,说明不是列扫描没有对应到该行
next_state = SCAN_READ;
else
next_state = SCAN_COL2;
SCAN_COL2:
if(row_data != 'b1111)
next_state = SCAN_READ;
else
next_state = SCAN_COL3;
SCAN_COL3:
if(row_data != 'b1111)
next_state = SCAN_READ;
else
next_state = SCAN_COL4;
SCAN_COL4:
if(row_data != 'b1111)
next_state = SCAN_READ;
else
next_state = SCAN_IDLE;
SCAN_READ:
if(row_data != 'b1111)
next_state = SCAN_JITTER2;
else
next_state = SCAN_IDLE;
SCAN_JITTER2:
if(row_data == 'b1111 && delay_done == 1'b1)
next_state = SCAN_IDLE;
else
next_state = SCAN_JITTER2;
default:next_state = SCAN_IDLE;
endcase
end //FSM step3
always @(posedge clk or negedge rst_n)begin
if(rst_n == 'b0)begin
col_data <= 'b0000;
row_data_r <= 'b0000;
col_data_r <= 'b0000;
end
else if(tran_flag) begin
case(next_state)
SCAN_COL1:col_data <= 'b0111;
SCAN_COL2:col_data <= 'b1011;
SCAN_COL3:col_data <= 'b1101;
SCAN_COL4:col_data <= 'b1110;
SCAN_READ:begin
col_data <= col_data;
row_data_r <= row_data;
col_data_r <= col_data;
end
default: col_data <= 'b0000;
endcase
end
else begin
col_data <= col_data;
row_data_r <= row_data_r;
col_data_r <= col_data_r;
end
end //这个状态表明是扫开消完抖动的那一瞬间
assign key_flag = (next_state == SCAN_IDLE && pre_state == SCAN_JITTER2 && tran_flag)? 'b1: 1'b0; //-------------------------------------------------------
//decode key_value
always @(posedge clk or negedge rst_n)begin
if(rst_n == 'b0)begin
key_value <= 'd0;
end
else if(key_flag == 'b1)begin
case({row_data_r, col_data_r})
'b0111_0111: key_value <= 4'h1;
'b0111_1011: key_value <= 4'h2;
'b0111_1101: key_value <= 4'h3;
'b0111_1110: key_value <= 4'ha;
'b1011_0111: key_value <= 4'h4;
'b1011_1011: key_value <= 4'h5;
'b1011_1101: key_value <= 4'h6;
'b1011_1110: key_value <= 4'hb;
'b1101_0111: key_value <= 4'h7;
'b1101_1011: key_value <= 4'h8;
'b1101_1101: key_value <= 4'h9;
'b1101_1110: key_value <= 4'hc;
'b1110_0111: key_value <= 4'hf;
'b1110_1011: key_value <= 4'h0;
'b1110_1101: key_value <= 4'he;
'b1110_1110: key_value <= 4'hd;
default : key_value <= key_value;
endcase
end
else
key_value <= key_value;
end endmodule

Matrix_Key_Scan

代码部分其实没啥好说的,有意思的是博主连接硬件做调试的时候,博主的矩阵键盘模块如图,薄膜键盘。某宝客服连原理图都没有,有一家给的我原理图还是错的。问题在于代码第一次下载到板子上的时候,没有出结果,不知道是代码问题还是硬件电路连接问题,我也是试了好久才猜出来正确的连接顺序。

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

这是,某宝客服给的错的原理图,先拿这个看一下,和这个矩阵键盘的构造差不多,从图中可以看到这个图和文章开头的原理图中少了点什么,上拉电阻。这里比较迷惑的是,如果没有上拉电阻,在空闲状态下,row_data怎么能保持输出高电平呢。问了几个客服,有说不懂的有说不用加上拉电阻的。

  我直接上板调试,在键盘的基础上加了个数码管显示按下的数值。按下时发现是可以显示正确的数字的,但是奇怪的是过一会儿数码管显示会清零。最开始以为是代码的问题,检查后从仿真和逻辑看,按键后译码的数值其实是一直保持不变的,没有操作是不会发生变化的,但实际情况不太符合。

  这样奇怪的情况发生,这个时候我们要相信科学。这种仿真发现不了问题,但实际运行却又bug,这个没法猜出来。在线逻辑分析仪就可以看到你的代码在开发板上运行的情况,这里引出Xilinx的Chipscope,用在线逻辑分析仪几乎可以抓到你的代码内部的所有信号,这个时候抓到的是你的电路实际运行的情况,配置流程如下。

基于FPGA的4x4矩阵键盘驱动调试

新建New source界面,选择如图Chipscope,next。

基于FPGA的4x4矩阵键盘驱动调试

然后会自动生成一个后缀为.cdc的文件,双击打开。

基于FPGA的4x4矩阵键盘驱动调试

这一步点击next

基于FPGA的4x4矩阵键盘驱动调试

同样next

基于FPGA的4x4矩阵键盘驱动调试

这里选择,触发信号的数量和位宽,我这里选择了三个触发信号,两个位宽为4,对应矩阵键盘的行和列,一个位宽为1,为复位信号。最后边的滚轮下拉可以看到全部信号。

基于FPGA的4x4矩阵键盘驱动调试

这里设置抓取的信号深度,选择上升沿采样信号。完成后点击next

基于FPGA的4x4矩阵键盘驱动调试

这里选择时钟信号clk

基于FPGA的4x4矩阵键盘驱动调试

选择后点击make connection,OK。

基于FPGA的4x4矩阵键盘驱动调试

同样的选择其他触发信号,加入行和列和复位信号。

基于FPGA的4x4矩阵键盘驱动调试

添加完成OK

基于FPGA的4x4矩阵键盘驱动调试

点击完成退出。

基于FPGA的4x4矩阵键盘驱动调试

保存

基于FPGA的4x4矩阵键盘驱动调试

这里点击这里就会启动Chipscope了,这个时候板子就可以上电了。

基于FPGA的4x4矩阵键盘驱动调试

点击链接板子

基于FPGA的4x4矩阵键盘驱动调试

照图点击下载板子。

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

基于FPGA的4x4矩阵键盘驱动调试

配置相关文件

基于FPGA的4x4矩阵键盘驱动调试

然后会弹出这个窗口,这里可以设置触发类型和触发方式,添加的信号都会显示出来

基于FPGA的4x4矩阵键盘驱动调试

设置触发方式为M2,即复位信号。

基于FPGA的4x4矩阵键盘驱动调试

点击上面的按钮开始运行,复位键释放,就可以抓取到一部分信号了。

基于FPGA的4x4矩阵键盘驱动调试

按下一个按键会看到对应的行列变换。

这是Chipscope的调用流程,通过在线逻辑分析仪,博主发现了问题,在空闲无操作时,触发复位抓取信号,抓到的row_data有时候是1111。有时候是0000或其他,但是理论上矩阵键盘在无操作下应该一直row_data输出1111。就是这个奇怪的问题导致的错误。我们要相信科学。应该是硬件电路的问题,检查了与开发板连接的杜邦线没问题后,应该就是矩阵键盘自己的问题,上拉电阻这块的原理,我所使用的矩阵键盘没有上拉电阻,但是实际上这样的驱动,如果row_data线上没有上拉电阻,它很难保持为高电平,而这个地方加不加其实和驱动开发板的构造有关,据我了解,有些单片机的I/O引脚会内置上拉电阻,默认情况下是高电平,所以用这些单片机驱动是不需要加上拉电阻的。

我这里使用FPGA驱动,FPGA的引脚特性来说,还是需要加的,使矩阵键盘的信号输出稳定,对于Xilinx FPGA来说有意思的是,通过综合工具添加引脚约束可以启动同样的效果,比如在ucf文件的引脚电平约束中加上pullup就可以了。

基于FPGA的4x4矩阵键盘驱动调试

由于我使用的Spartan-3E系列的开发板,从它手册上可以得到。在引脚约束在电平为3.3v时加上pull up,可以等下出相当于10.8k欧姆的电阻这和矩阵键盘的驱动原理是完全相符。

基于FPGA的4x4矩阵键盘驱动调试

这篇博文主要分享的是硬件的一个调试过程,Chipscope还是很好用的。对于硬件来说,你没办法确定他的状态,所以使用工具抓取他的实际信号,帮助我们更好调试。

基于FPGA的4x4矩阵键盘驱动调试

转载请注明出处:NingHeChuan(宁河川)

个人微信订阅号:开源FPGA

如果你想及时收到个人撰写的博文推送,可以扫描左边二维码(或者长按识别二维码)关注个人微信订阅号

知乎ID:NingHeChuan

微博ID:NingHeChuan

原文地址:https://www.cnblogs.com/ninghechuan/p/9401744.html