微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > ARM技术讨论 > 之编写4X4矩阵键盘驱动及基于QT GUI的键盘输入测试

之编写4X4矩阵键盘驱动及基于QT GUI的键盘输入测试

时间:10-02 整理:3721RD 点击:

【创龙AM4379 Cortex-A9试用体验】之编写4X4矩阵键盘驱动及基于QT GUI的键盘输入测试

在上篇试用报告《【创龙AM4379 Cortex-A9试用体验】之采用GPIO扩展口自定义键盘Input驱动+QT界面键盘输入测试》中,我们用4个按键模拟了数字1,数字2,以及TAB、ENTER键,使得通过GPIO的扩展口连接按键,结合上拉电阻实现了一般机械键盘的功能,但是,对按键较少的应用中,比如只需要输入0-9字符时,单个按键连接单个I/O口实现键盘功能还可以采用,但是如果我们需要一个功能相对较完整的数字键盘,如需具有0-9,小数点、空格键、TAB键、ENTER键、SHIFT键、退格键等等时,如果采用上一篇试用报告讲解的方法,就有点儿太浪费GPIO端口了,在工业应用中,GPIO口是非常宝贵的资源,我们在设计输入输出时,尽量会留一些备用接口,以备工业调试现场时一些为规划功能的扩展需要。我们这里采用单片机通常都会采用的行X列矩阵键盘,由行触发的中断,调用列的扫描方式,测试用户到底按下了哪个键。

如果在单片机的应用中,实现4X4矩阵键盘的功能就比较简单了,而对于Linux系统下的4X4键盘,我们必须为其编写符合Linux Input规范的驱动程序。鉴与TL-4379运行的是QT5.4.1版本的GUI,而有别于QT4,       QT5的软键盘开发变化较大,我暂时还没有搞定QT5的软键盘移植,但是又想有输入,并且满足特殊行业禁止使用触摸屏的要求,我们这里就以4X4矩阵键盘为例,开发其在Linux下的Input驱动程序,并在QT GUI界面测试编写的矩阵键盘驱动程序的正确性。

1. 硬件搭建

为了完成本次测试,并为今后的开发板功能开发提供便利,特意从某宝上买了一块4X4键盘,尽量减少开发过程中繁琐的连线。4X4矩阵键盘如图所示:


硬件原理图,及我对每个按键的功能规划如图所示:


从AM4379参考手册中,GPIO用作矩阵键盘是,触发中断的引脚需接上拉电阻,如图所示:


矩阵键盘与TL-4379开发板的连接如图所示:


这里特别强调一下,AM437X与AM335X芯片在引脚定义上有较大不同,在AM335X中,每个GPIO都与一个特殊功能一一对应,而在AM437X中,一个特殊功能引脚可以对应对个GPIO,如下图所示:


对于主功能为spi2_sclk,其对应gpio3_24和gpio0_22,至于spi2_sclk对应哪个GPIO,则需要我们设置0x7或0x9模式,而对于spi4_d1其只对应一个GPIO,即gpio5_6。对应一个主功能引脚只对应一个GPIO的情况,我们可以通过Linux系统下的gpio_request等申请资源,设置输入或输出,而对于一个主功能对应两个GPIO的情况,我们必须通过重映射该引脚的寄存器设置。简便起见,我这里主要选择了一个主功能只对应一个GPIO引脚的I/O口。

硬件连接效果如图所示:


2. 4X4矩阵键盘原理

4X4矩阵键盘的原理如下:

以上述原理图为例,我们设置L1-L4对应的I/O为输入,并且配置为带中断的I/O口,R1-R4对应的I/O口为输出。在键值开始扫描之前,设置R1-R4输出为低电平,当用户按下某个键后,对对应的行中断线被拉低,触发下降沿中断,在中断处理函数中,依次设置R1为0,看看这一列中的哪一行为0输入,如果没有0输入,机械R2输出0,看看R2这一列哪一行输入为0,依次类推,直到确定触发中断的那个按钮所在的行和列,这样我们就可计算该键的键值KEB-VAL=4*ROW + COL。

3. 驱动程序开发

3.1 头文件

除了包含一般的头文件外,还要包含input.h头文件,该文件中包含了每个键盘按键对应的键码值。

#include<linux/module.h>

#include<linux/kernel.h>

#include<linux/fs.h>

#include<linux/init.h>

#include<linux/delay.h>

#include<linux/cdev.h>

#include<linux/miscdevice.h>

#include<linux/sched.h>

#include<linux/pm.h>

#include<linux/sysctl.h>

#include<linux/proc_fs.h>

#include<linux/irq.h>

#include<linux/device.h>

#include<linux/interrupt.h>

#include<linux/semaphore.h>

#include<linux/signal.h>

#include<linux/platform_device.h>

#include<linux/input.h>

#include<linux/gpio.h>

#include<asm/uaccess.h>

#include<linux/irq.h>

#include<asm/irq.h>

#include<linux/poll.h>

#include<linux/timer.h>

3.2 按键编号及中断编号定义

#defineGPIO_L1_PIN_NUM (3*32 + 14) /* gpio 3_14 */

#defineGPIO_L2_PIN_NUM (3*32 + 16) /* gpio 3_16 */

#defineGPIO_L3_PIN_NUM (4*32 + 3) /* gpio 4_3 */

#defineGPIO_L4_PIN_NUM (4*32 + 2) /* gpio 4_2 */

#defineGPIO_R1_PIN_NUM (3*32 + 17) /* gpio 3_17 */

#defineGPIO_R2_PIN_NUM (5*32 + 6) /* gpio 5_6 */

#defineGPIO_R3_PIN_NUM (5*32 + 4) /* gpio 5_4 */

#defineGPIO_R4_PIN_NUM (0*32 + 5) /* gpio 0_5 */

#defineL1_IRQ  gpio_to_irq(GPIO_L1_PIN_NUM)   /*KEY1对应的中断号*/

#defineL2_IRQ  gpio_to_irq(GPIO_L2_PIN_NUM)   /*KEY2对应的中断号*/

#defineL3_IRQ  gpio_to_irq(GPIO_L3_PIN_NUM)   /*KEY3对应的中断号*/

#defineL4_IRQ  gpio_to_irq(GPIO_L4_PIN_NUM)   /*KEY4对应的中断号*/

3.3 关键结构体及初始化

staticstruct input_dev *buttons_dev;  /*输入设备结构体指针,作为全局变量为其他函数使用*/

staticstruct timer_list buttons_timer; /*定义消抖定时器*/

structbutton_irq_desc *button_irq_g; /*保存触发中断的那个结构体*/

unsignedint key_value = KEY_0;          //全局按键码

structbutton_irq_desc {

    int irq;

    int pin;

    int number;

    char *name;

};

/*********************************************************

*行I/O端口定义

********************************************************/

staticunsigned long row_table [] = {

    GPIO_R1_PIN_NUM,

    GPIO_R2_PIN_NUM,

    GPIO_R3_PIN_NUM ,

    GPIO_R4_PIN_NUM,

};

/*********************************************************

*列I/O端口定义

********************************************************/

staticstruct button_irq_desc button_irqs [] = {

    {-1, GPIO_L1_PIN_NUM, 0, "KEY1"},

    {-1, GPIO_L2_PIN_NUM, 1, "KEY2"},

    {-1,GPIO_L3_PIN_NUM, 2, "KEY3"},

    {-1, GPIO_L4_PIN_NUM, 3, "KEY4"},

  

};

staticvolatile int ev_key = 0;

structtimer_list mytimer;

3.4 使能、禁止中断

在按键的扫描过程中,需要反复的拉低中断输入引脚,而这时我们只想读取引脚的状态,而无需让下降沿触发中断,所以在扫描码值时,我们关闭中断,扫描结束后,再次使能中断,为下一次按键读取做准备。

static voiddisable_irqs(void)

{

    disable_irq(L1_IRQ);

    disable_irq(L2_IRQ);

    disable_irq(L3_IRQ);

    disable_irq(L4_IRQ);

}

staticvoid enable_irqs(void)

{

    enable_irq(L1_IRQ);

    enable_irq(L2_IRQ);

    enable_irq(L3_IRQ);

    enable_irq(L4_IRQ);

}

3.5 按键扫描程序

根据第二节介绍的原理,形成按键扫描算法:

依次拉低某列的输入值

staticvoid buttons_kscan_reset(int row)

{

    int i;

    for(i=0; i < 4; i++)

      {

        if(i == row)

            gpio_set_value(row_table,0);     //行电平设置,第row列置低,其他置高

        else

            gpio_set_value(row_table, 1);

    }

}

扫描实体:

static intbuttons_scan(void)

{

    int i,j,k=0;

    int column = 0;      //列

    int row = 0;         //行

      

   //printk("11111111111111111111111111111111111111/n");

    disable_irqs();

    for (i = 0; i < 4; i++)    //列线置低

      {            

             gpio_set_value(row_table, 0);

    }

      

    for (j = 0; j < 4; j++)

    {

        if(gpio_get_value(button_irqs[j].pin)== 0 )       //若某一列变低电平则保存该列值

        {

            column = j;

            break;

        }

    }   

    if(j<4)

    {

        for ( j = 0; j < 4; j++)

        {

            buttons_kscan_reset(j);      //0111 1011 1101 1110

            ndelay(100);

           if(gpio_get_value(button_irqs[column].pin) == 0)     //扫描行

            {

                row = j;

                break;

            }

        }

            

             k = column * 4 + row;

            

        ev_key = k;              //第k个按键被按下

    }

    for (i = 0; i < 4; i++)   //列线置低

    {              

             gpio_set_value(row_table, 0);

    }

      

      //发送按键码

      /* 松开 : 最后一个参数: 0-松开, 1-按下 */

      switch(ev_key)

      {

             case 0:

                    key_value = KEY_1;

                    break;

             case 1:

                    key_value = KEY_2;

                    break;

             case 2:

                    key_value = KEY_3;

                    break;

             case 3:

                    key_value = KEY_BACKSPACE;

                    break;

             case 4:

                    key_value = KEY_4;

                    break;

             case 5:

                    key_value = KEY_5;

                    break;

             case 6:

                    key_value = KEY_6;

                    break;

             case 7:

                    key_value = KEY_TAB;

                    break;

             case 8:

                    key_value = KEY_7;

                    break;

             case 9:

                    key_value = KEY_8;

                    break;

             case 10:

                    key_value = KEY_9;

                    break;

             case 11:

                    key_value = KEY_LEFTSHIFT;

                    break;

             case 12:

                    key_value = KEY_0;

                    break;

             case 13:

                    key_value = KEY_DOT;

                    break;

             case 14:

                    key_value = KEY_SPACE;

                    break;

             case 15:

                    key_value = KEY_ENTER;

                    break;

      }

      input_event(buttons_dev, EV_KEY,key_value, 1);

      input_sync(buttons_dev);

  

    del_timer(&mytimer);      

   

    enable_irqs();

    return 0;

}

voidset_timer(void)

{

    init_timer(&mytimer);

    mytimer.expires = jiffies + 10;

    mytimer.function = buttons_scan;

    add_timer(&mytimer);

}

3.6 中断处理程序及定时器消抖

由于机械式按键的固有特性,我们必须通过硬件或软件的方式对按键抖动进行过滤处理,我们这里采用定时器避开按键10-20ms的抖动窗口前期,按键的扫描触发实际上是在消抖定时器的回调函数中执行的。

/*********************************************************

*中断程序,在其中调用定时器,

*进行定时及扫描。

********************************************************/

staticirqreturn_t buttons_interrupt(int irq, void *dev_id)

{

      button_irq_g = (struct button_irq_desc*)dev_id;

      mod_timer(&buttons_timer,jiffies+HZ/8);  //设置去抖时间

      

      return IRQ_RETVAL(IRQ_HANDLED);

      

   

}

staticvoid buttons_timer_function(unsigned long data)

{

      int down;

    disable_irqs();

    down =!gpio_get_value(button_irq_g->pin);

    if(down)             //按下

    {

        set_timer();

    }

      else               //松开

      {

             /* 松开 : 最后一个参数: 0-松开, 1-按下 */

             unsigned int key_value = KEY_0;

             switch(ev_key)

             {

                    case 0:

                           key_value = KEY_1;

                           break;

                    case 1:

                           key_value = KEY_2;

                           break;

                    case 2:

                           key_value = KEY_3;

                           break;

                    case 3:

                           key_value =KEY_BACKSPACE;

                           break;

                    case 4:

                           key_value = KEY_4;

                           break;

                    case 5:

                           key_value = KEY_5;

                           break;

                    case 6:

                           key_value = KEY_6;

                           break;

                    case 7:

                           key_value = KEY_TAB;

                           break;

                    case 8:

                           key_value = KEY_7;

                           break;

                    case 9:

                           key_value = KEY_8;

                           break;

                    case 10:

                           key_value = KEY_9;

                           break;

                    case 11:

                           key_value =KEY_LEFTSHIFT;

                           break;

                    case 12:

                           key_value = KEY_0;

                           break;

                    case 13:

                           key_value = KEY_DOT;

                           break;

                    case 14:

                           key_value =KEY_SPACE;

                           break;

                    case 15:

                           key_value =KEY_ENTER;

                           break;

             }

             input_event(buttons_dev, EV_KEY,key_value, 0);

             input_sync(buttons_dev);

      }

   

    enable_irqs();

}

3.7 初始化驱动模块

在驱动的初始化模块中,我们首先申请GPIO资源,设置行中断输入,列GPIO输出,然后动态分配Input设备描述结构体,设置该输入设备可向系统提交的按键值,最后注册该输入结构体。

static int__init dev_init(void)

{

    int ret;

      int i;

      int err = 0;

      

      /*设置中断号*/

      button_irqs [0].irq = L1_IRQ;

      button_irqs [1].irq = L2_IRQ;

      button_irqs [2].irq = L3_IRQ;

      button_irqs [3].irq = L4_IRQ;

      

      /*申请中断引脚对应的GPIO*/

      ret = gpio_request_one(button_irqs[0].pin,GPIOF_IN, "L1 IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for L1\n");

      }

      

      ret = gpio_request_one(button_irqs[1].pin,GPIOF_IN, "L2 IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed to requestGPIO for L2\n");

      }

      ret = gpio_request_one(button_irqs[2].pin,GPIOF_IN, "L3 IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for L3\n");

      }

      ret = gpio_request_one(button_irqs[3].pin,GPIOF_IN, "L4 IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for L4\n");

      }

      

      /*申请列引脚对应的GPIO*/

      ret = gpio_request(row_table[0], "R1IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for R1\n");

      }

      

      ret = gpio_request(row_table[1], "R2IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for R2\n");

      }

      ret = gpio_request(row_table[2], "R3IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for R3\n");

      }

      ret = gpio_request(row_table[3], "R4IRQ"); /* 申请 IO ,为输入*/

      if (ret < 0) {

             printk(KERN_ERR "Failed torequest GPIO for R4\n");

      }

      

      /* 设置中断引脚为输入 */

      for(i=0;i<4;i++)

             gpio_direction_input(button_irqs.pin);

      

      /* 设置列引脚为输出 */

      for(i=0;i<4;i++)

      {

             gpio_direction_output(row_table,0);

      }

      

      /* 为GPIO3_14,GPIO3_16,GPIO4_2,GPIO4_3申请中断 */

      if(request_irq(button_irqs [0].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K1", &button_irqs[0]))

      {

             printk(KERN_ERR "Failed torequest IRQ for KEY1\n");

      }

      

      if(request_irq(button_irqs [1].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K2", &button_irqs[1]))

      {

             printk(KERN_ERR "Failed torequest IRQ for KEY2\n");

      }

      if(request_irq(button_irqs [2].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K3", &button_irqs[2]))

      {

             printk(KERN_ERR "Failed torequest IRQ for KEY3\n");

      }

      if(request_irq(button_irqs [3].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K4", &button_irqs[3]))

      {

             printk(KERN_ERR "Failed torequest IRQ for KEY4\n");

      }

      

      /* 1. 分配一个input_dev结构体 */

      buttons_dev = input_allocate_device();;

      /* 2. 设置 */

      /* 2.1 能产生哪类事件 */

      set_bit(EV_KEY, buttons_dev->evbit);

      set_bit(EV_REP, buttons_dev->evbit);

      

      /* 2.2 能产生这类操作里的哪些事件: 0,1,TAB,ENTER */

      set_bit(KEY_0, buttons_dev->keybit);

      set_bit(KEY_1, buttons_dev->keybit);

      set_bit(KEY_2, buttons_dev->keybit);

      set_bit(KEY_3, buttons_dev->keybit);

      set_bit(KEY_4, buttons_dev->keybit);

      set_bit(KEY_5, buttons_dev->keybit);

      set_bit(KEY_6, buttons_dev->keybit);

      set_bit(KEY_7, buttons_dev->keybit);

      set_bit(KEY_8, buttons_dev->keybit);

      set_bit(KEY_9, buttons_dev->keybit);

      set_bit(KEY_BACKSPACE,buttons_dev->keybit);

      set_bit(KEY_TAB, buttons_dev->keybit);

      set_bit(KEY_LEFTSHIFT,buttons_dev->keybit);

      set_bit(KEY_ENTER,buttons_dev->keybit);

      set_bit(KEY_SPACE,buttons_dev->keybit);

      set_bit(KEY_DOT, buttons_dev->keybit);

      /* 3. 注册 */

      input_register_device(buttons_dev);

      

      /*消抖定时器*/

      init_timer(&buttons_timer);

      buttons_timer.function =buttons_timer_function;

      add_timer(&buttons_timer);

      

    return ret;

}

3.8 驱动模块退出函数

释放GPIO资源,释放申请的中断,及注销Input设备。

staticvoid __exit dev_exit(void)

{

      int i;

      /*卸载模块时,释放中断*/

      for (i = 0; i <sizeof(button_irqs)/sizeof(button_irqs[0]); i++)

      {

        if (button_irqs.irq < 0)

             {

            continue;

        }

        free_irq(button_irqs.irq, (void*)&button_irqs);

    }

      

      /*卸载模块时,释放GPIO资源*/

      for (i = 0; i < 4; i++)

      {

             gpio_free(button_irqs.pin);

      }

      

      for (i = 0; i < 4; i++)

      {

             gpio_free(row_table);

      }

      

      del_timer(&buttons_timer);

      input_unregister_device(buttons_dev);

      input_free_device(buttons_dev);   

}

3.9 其他代码

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("LZP");

3.10 编译驱动模块

执行命令:

makeARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

编译结果如图所示:


将驱动模块key_board_have_input.ko拷贝到NFS共享目录。

4. QT GUI测试

QT GUI程序我们还采用上一篇试用报告中的加法运算界面。

启动TL-4379开发板,执行如下命令:

mount -tnfs 192.168.1.103:/nfsshare /mnt/ -o nolock

cd /mnt

insmodkey_board_have_input.ko

加载驱动后,执行结果如图所示:


执行命令,启动QT GUI测试程序:

./qt_keyboard_test  -plugin tslib:/dev/input/touchscreen0  

执行结果如图所示:


在第一个输入框,通过4X4矩阵键盘,输入58,如图所示:


按TAB键,光标跳转到第二个输入框,输入97,如图所示:


按TAB键,是光标跳转到“=”按钮,按钮边沿出现一个黑色的矩形框,如图所示:


按下ENTER键,即第16个键,计算加法结果如图所示:


按下TAB键,光标跳转到第一个输入框,如图所示:


58被选中,我们按下右上角的BACKSPACE键,即退格键,将58这个数字删除,如图所示:


我们再次在第一输入框内输入小数点,和空格键,效果如图所示:


5. 小结

我们在上一篇试用报告的基础上,结合矩阵键盘的扫描原理,编写了4X4矩阵键盘的驱动程序,通过在QT GUI上做功能测试,基本达到了预期效果,实现了0-9,小数点、退格键、空格键、ENTER键和SHIFT键功能,对于我在上一篇试用报告中提到的那个煤矿瓦斯浓度检测仪表,我们采用TL-4379和4X4矩阵键盘,完全可以实现客户提出的功能要求。通过开发板的试用,挖掘开发板的各项功能的过程中,我也在不断的学习,这些看似基本的功能实现,对我实际工作中的方案制定有很多帮助,使我的方案设计中不再仅仅限于单片机,或Cortex-M3、Cortex-M4的这些中低端应用了,今天就写这么多,下篇报告再见!


大写的赞

谢谢关注,多多交流

小编 想请教个问题 请问你的上层qt应用程序是如何使用这个外接矩阵键盘的  是通过其他自带的键盘事件直接响应的吗?
方便的话 可以留下qq聊聊 谢谢  我的qq:312855150

下载我提供的驱动和QT程序源码,里面注释的比较详细

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top