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


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

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

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

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

     

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

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

      1 `timescale      1ns/1ps
      2 // *********************************************************************************
      3 // Project Name :       
      4 // Author       : NingHeChuan
      5 // Email        : ninghechuan@foxmail.com
      6 // Blogs        : http://www.cnblogs.com/ninghechuan/
      7 // File Name    : Matrix_Key_Scan.v
      8 // Module Name  :
      9 // Called By    : 
     10 
     11 // Abstract     :
     12 //
     13 // CopyRight(c) 2018, NingHeChuan Studio.. 
     14 // All Rights Reserved
     15 //
     16 // *********************************************************************************
     17 // Modification History:
     18 // Date         By              Version                 Change Description
     19 // -----------------------------------------------------------------------
     20 // 2018/7/28    NingHeChuan       1.0                     Original
     21 //  
     22 // *********************************************************************************
     23 
     24 module Matrix_Key_Scan(
     25     input                   clk,    //50Mhz
     26     input                   rst_n,
     27     input           [3:0]   row_data,
     28     output                  key_flag,
     29     output      reg [3:0]   key_value,
     30     output      reg [3:0]   col_data
     31 );
     32 
     33 //FSM state
     34 parameter       SCAN_IDLE       =   8'b0000_0001;
     35 parameter       SCAN_JITTER1    =   8'b0000_0010;
     36 parameter       SCAN_COL1       =   8'b0000_0100;
     37 parameter       SCAN_COL2       =   8'b0000_1000;
     38 parameter       SCAN_COL3       =   8'b0001_0000;
     39 parameter       SCAN_COL4       =   8'b0010_0000;
     40 parameter       SCAN_READ       =   8'b0100_0000;
     41 parameter       SCAN_JITTER2    =   8'b1000_0000;
     42 //
     43 parameter       DELAY_TRAN      =   2;
     44 parameter       DELAY_20MS      =   1000_000;
     45 //parameter       DELAY_20MS      =   100;//just test
     46 reg     [20:0]  delay_cnt;
     47 wire            delay_done;
     48 //
     49 reg     [7:0]   pre_state;
     50 reg     [7:0]   next_state;
     51 reg     [20:0]   tran_cnt;
     52 wire            tran_flag;
     53 //
     54 reg     [3:0]   row_data_r;
     55 reg     [3:0]   col_data_r;
     56 //
     57 
     58 //-------------------------------------------------------
     59 //delay 20ms
     60 always  @(posedge clk or negedge rst_n)begin
     61     if(rst_n == 1'b0)begin
     62         delay_cnt   <= 'd0;
     63     end
     64     else if(delay_cnt == DELAY_20MS)
     65         delay_cnt <= 'd0;
     66     else if(next_state == SCAN_JITTER1 | next_state == SCAN_JITTER2) begin
     67         delay_cnt <= delay_cnt + 1'b1;
     68     end
     69     else 
     70         delay_cnt <= 'd0;
     71 end
     72 
     73 assign  delay_done = (delay_cnt == DELAY_20MS - 1'b1)? 1'b1: 1'b0;
     74 
     75 
     76 //-------------------------------------------------------
     77 //delay 2clk
     78 always  @(posedge clk or negedge rst_n)begin
     79     if(rst_n == 1'b0)begin
     80         tran_cnt <= 'd0;
     81     end
     82     else if(tran_cnt == DELAY_TRAN)begin
     83         tran_cnt <= 'd0;
     84     end
     85     else 
     86         tran_cnt <= tran_cnt + 1'b1;
     87 end
     88 
     89 assign    tran_flag = (tran_cnt == DELAY_TRAN)? 1'b1: 1'b0;
     90 
     91 
     92 //-------------------------------------------------------
     93 //FSM step1
     94 always  @(posedge clk or negedge rst_n)begin
     95     if(rst_n == 1'b0)begin
     96         pre_state <= SCAN_IDLE;
     97     end
     98     else if(tran_flag)begin
     99         pre_state <= next_state;
    100     end
    101     else pre_state <= pre_state;
    102 end
    103 
    104 //FSM step2
    105 always  @(*)begin
    106     next_state = SCAN_IDLE;
    107     case(pre_state)
    108     SCAN_IDLE:
    109         if(row_data != 4'b1111)
    110             next_state = SCAN_JITTER1;
    111         else 
    112             next_state = SCAN_IDLE;
    113     SCAN_JITTER1:
    114         if(row_data != 4'b1111 && delay_done == 1'b1)
    115             next_state = SCAN_COL1;
    116         else 
    117             next_state = SCAN_JITTER1;
    118     SCAN_COL1:
    119         if(row_data != 4'b1111)//如果row_data是全1,说明不是列扫描没有对应到该行
    120             next_state = SCAN_READ;
    121         else 
    122             next_state = SCAN_COL2;
    123     SCAN_COL2:
    124         if(row_data != 4'b1111)
    125             next_state = SCAN_READ;
    126         else 
    127             next_state = SCAN_COL3;
    128     SCAN_COL3:
    129         if(row_data != 4'b1111)
    130             next_state = SCAN_READ;
    131         else 
    132             next_state = SCAN_COL4;
    133     SCAN_COL4:
    134         if(row_data != 4'b1111)
    135             next_state = SCAN_READ;
    136         else 
    137             next_state = SCAN_IDLE;
    138     SCAN_READ:
    139         if(row_data != 4'b1111)
    140             next_state = SCAN_JITTER2;
    141         else 
    142             next_state = SCAN_IDLE;
    143     SCAN_JITTER2:
    144         if(row_data == 4'b1111 && delay_done == 1'b1)
    145             next_state = SCAN_IDLE;
    146         else
    147             next_state = SCAN_JITTER2;
    148     default:next_state = SCAN_IDLE;
    149     endcase
    150 end
    151 
    152 //FSM step3
    153 always  @(posedge clk or negedge rst_n)begin
    154     if(rst_n == 1'b0)begin
    155         col_data <= 4'b0000;
    156         row_data_r <= 4'b0000;
    157         col_data_r <= 4'b0000;
    158     end
    159     else if(tran_flag) begin
    160         case(next_state)
    161         SCAN_COL1:col_data <= 4'b0111;
    162         SCAN_COL2:col_data <= 4'b1011;
    163         SCAN_COL3:col_data <= 4'b1101;
    164         SCAN_COL4:col_data <= 4'b1110;
    165         SCAN_READ:begin
    166             col_data <= col_data;
    167             row_data_r <= row_data;
    168             col_data_r <= col_data;
    169         end
    170         default: col_data <= 4'b0000;
    171         endcase
    172     end
    173     else begin
    174         col_data <= col_data;
    175         row_data_r <= row_data_r;
    176         col_data_r <= col_data_r;
    177     end
    178 end
    179 
    180 //这个状态表明是扫开消完抖动的那一瞬间
    181 assign key_flag = (next_state == SCAN_IDLE && pre_state == SCAN_JITTER2 && tran_flag)? 1'b1: 1'b0;
    182 
    183 //-------------------------------------------------------
    184 //decode key_value
    185 always  @(posedge clk or negedge rst_n)begin
    186     if(rst_n == 1'b0)begin
    187         key_value <= 'd0; 
    188     end
    189     else if(key_flag == 1'b1)begin
    190         case({row_data_r, col_data_r})
    191         8'b0111_0111: key_value <= 4'h1;  
    192         8'b0111_1011: key_value <= 4'h2;
    193         8'b0111_1101: key_value <= 4'h3;
    194         8'b0111_1110: key_value <= 4'ha;
    195         8'b1011_0111: key_value <= 4'h4;
    196         8'b1011_1011: key_value <= 4'h5;
    197         8'b1011_1101: key_value <= 4'h6;
    198         8'b1011_1110: key_value <= 4'hb;
    199         8'b1101_0111: key_value <= 4'h7;
    200         8'b1101_1011: key_value <= 4'h8;
    201         8'b1101_1101: key_value <= 4'h9;
    202         8'b1101_1110: key_value <= 4'hc;
    203         8'b1110_0111: key_value <= 4'hf;
    204         8'b1110_1011: key_value <= 4'h0;
    205         8'b1110_1101: key_value <= 4'he;
    206         8'b1110_1110: key_value <= 4'hd;
    207         default     : key_value <= key_value;
    208         endcase
    209     end
    210     else 
    211         key_value <= key_value;
    212 end
    213 
    214 
    215 endmodule
    Matrix_Key_Scan

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

     

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

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

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

     

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

     

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

     

    这一步点击next

    同样next

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

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

    这里选择时钟信号clk

    选择后点击make connection,OK。

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

    添加完成OK

    点击完成退出。

     

    保存

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

    点击链接板子

    照图点击下载板子。

    配置相关文件

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

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

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

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

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

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

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

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

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

    个人微信订阅号:开源FPGA

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

    知乎ID:NingHeChuan

    微博ID:NingHeChuan

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

  • 相关阅读:
    linux教材一、二章 练习及遇到的问题解决过程
    20201217王菁——我的电子书阅读时光
    33高阶API示范——eat_tensorflow2_in_30_days
    32中阶API示范eat_tensorflow2_in_30_days
    Remote 'g' packet reply is too long的解决
    海思3559实现KCF算法 转载文章
    Zynqlinux PL与PS通过DMA数据交互 转载
    面试官:Java如何绑定线程到指定CPU上执行?
    vue.js
    linux C语言为什么要fork子进程,哪种场景下需要使用fork方法?
  • 原文地址:https://www.cnblogs.com/ninghechuan/p/9401744.html
Copyright © 2020-2023  润新知