完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
【创龙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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 L1n"); } ret = gpio_request_one(button_irqs[1].pin,GPIOF_IN, "L2 IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed to requestGPIO for L2n"); } ret = gpio_request_one(button_irqs[2].pin,GPIOF_IN, "L3 IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for L3n"); } ret = gpio_request_one(button_irqs[3].pin,GPIOF_IN, "L4 IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for L4n"); } /*申请列引脚对应的GPIO*/ ret = gpio_request(row_table[0], "R1IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for R1n"); } ret = gpio_request(row_table[1], "R2IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for R2n"); } ret = gpio_request(row_table[2], "R3IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for R3n"); } ret = gpio_request(row_table[3], "R4IRQ"); /* 申请 IO ,为输入*/ if (ret < 0) { printk(KERN_ERR "Failed torequest GPIO for R4n"); } /* 设置中断引脚为输入 */ 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 KEY1n"); } if(request_irq(button_irqs [1].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K2", &button_irqs[1])) { printk(KERN_ERR "Failed torequest IRQ for KEY2n"); } if(request_irq(button_irqs [2].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K3", &button_irqs[2])) { printk(KERN_ERR "Failed torequest IRQ for KEY3n"); } if(request_irq(button_irqs [3].irq,buttons_interrupt, IRQ_TYPE_EDGE_BOTH, "K4", &button_irqs[3])) { printk(KERN_ERR "Failed torequest IRQ for KEY4n"); } /* 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 { 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的这些中低端应用了,今天就写这么多,下篇报告再见! |
|
相关推荐
7 个讨论
|
|
|
|
|
|
|
|
|
|
|
|
|
|
你正在撰写讨论
如果你是对讨论或其他讨论精选点评或询问,请使用“评论”功能。
260 浏览 0 评论
864 浏览 0 评论
1614 浏览 1 评论
飞凌嵌入式ElfBoard ELF 1板卡-spi编程示例之spi编写程序
1007 浏览 0 评论
飞凌嵌入式-ELFBOARD 4.3吋电容触摸液晶模块简介第三期 :机械机构说明
1157 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-15 06:56 , Processed in 0.720775 second(s), Total 49, Slave 42 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号