输入子系统——按键编写
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
- 开发环境:Linux 2.6.22.6 内核、arm-linux-gcc-3.4.5-glibc-2.3.6工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-2
目录
-
输入子系统——按键编写
一、前言
对于我们之前写按键驱动,步骤如下:
- 确定主设备号(可自己指定,也可以有系统指定)
- 构造
file_operation
结构体,并把编写的read、write、open
等函数的地址保存在其中 register_chrdrv()
注册驱动设备- 编写入口与出口函数
而对于上节中分析的输入子系统,编写驱动时,步骤如下:
- 分配一个
input_dev
结构体 - 设置可触发哪类事件
input_register_device
注册设备- 进行硬件相关操作
可以看到,在利用输入子系统编写按键驱动时,并没有进行相关read、write等函数编写,至于为什么下面会进行分析,下面正式开始在输入子系统的基础上编写按键驱动。
二、buttons.c文件编写
- 参考内核程序为:
drivers/input/keyboard/gpio_keys.c
- 实现功能:开发板上的四个按键,各自代表键盘的:L、S、ENTER、LEFTSHIF
1、大致程序框架
其中头文件参考drivers/input/keyboard/gpio_keys.c
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <linux/gpio_keys.h>
#include <asm/arch/regs-gpio.h>
/* 入口函数 */
static int buttons_init(void)
{
/* 1、分配一个input_dev结构体 */
/* 2、设置 */
/* 3、注册: 会建立连接 */
/* 4、硬件相关操作 */
}
/* 出口函数 */
static void buttons_exit(void)
{
}
/* 修饰 */
module_init(buttons_init);
module_exit(buttons_exit);
/* 协议 */
MODULE_LICENSE("GPL");
2、入口函数buttons_init()
编写
2.1 分配一个input_dev结构体
分析drivers/input/keyboard/gpio_keys.c
文件的入口函数可以得到下图
对于其中的注册平台设备函数我们先不关心。
分配input_dev
结构体方法如下:
static struct input_dev *buttons_inputdev;
buttons_inputdev = input_allocate_device();
if (!buttons_inputdev)
return -ENOMEM;
2.2 设置事件
追踪input_dev
结构体的原型:可以知道需要设置为哪一类事件,这类事件的哪些事件。
参考drivers/input/keyboard/gpio_keys.c
这里我们直接采用set_bit()
函数
/* 2、设置 */
/* 2.1 设置可以产生按键类事件 */
set_bit(EV_KEY, buttons_inputdev->evbit);
/* 2.2 设置这类操作里产生哪些事件:L, S,ENTER,SHIFT */
set_bit(KEY_L, buttons_inputdev->keybit);
set_bit(KEY_S, buttons_inputdev->keybit);
set_bit(KEY_ENTER, buttons_inputdev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_inputdev->keybit);
2.3 input_register_device()
注册设备
/* 3、注册: 会建立连接 */
error = input_register_device(buttons_inputdev);
if (error) {
printk(KERN_ERR "Unable to register buttons input device\n");
goto fail;
}
2.4 硬件相关操作
对于硬件操作,无论是自己编写驱动还是使用输入子系统,硬件操作都是一致。
/* 按键信息 */
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
/* 存储4个按键的信息 */
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11,"S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19,"S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer; //定义一个定时器
/* 4、硬件相关操作 */
/* 4.1 初始化定时器相关操作 */
init_timer(&buttons_timer); //定时器初始化
buttons_timer.function = buttons_timer_function; //定时器处理函数
add_timer(&buttons_timer);
/* 4.2 注册中断 */
for (i = 0; i < 4; i++) {
error = request_irq(pins_desc[i].irq, button_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "buttons: unable to claim irq error %d\n", error);
goto fail;
}
}
return error;
fail:
for (i = i - 1; i >= 0; i--)
free_irq(pins_desc[i].irq, &pins_desc[i]));
input_free_device(buttons_inputdev);
return error;
}
3、 中断相关函数编写
分析drivers/input/keyboard/gpio_keys.c
文件的中断服务函数可以知道:需要在中断函数中进行下列两个事件上报
追踪input_sync()
源码可知
- input_sync()同步用于告诉子系统报告结束
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
3.1 按键中断函数buttons_irq()
/* 设置按键中断函数 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + HZ / 100);
return IRQ_RETVAL(IRQ_HANDLED);
}
3.2 定时器中断函数buttons_timer_function()
/* 定时器中断服务函数:防抖动
* 采用输入子系统,当有数据时则调用input_event
*/
static void buttons_timer_function(unsigned long data)
{
struct pin_desc *pindesc = irq_pd;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin); //读取IO口电平
/* 最后一个参数:0-松开,1-按下 */
if(pinval){
/* 松开上报 并 上报一个event事件与同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_inputdev);
}else{
/* 按下上报 并 上报一个event事件与同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_inputdev);
}
}
4、出口函数buttons_exit()
编写
/* 出口函数 */
static void buttons_exit(void)
{
int i;
/* 释放中断 */
for (i = 0; i < 4; i++) {
free_irq(pins_desc[i].irq, &pins_desc[i]));
}
/* 从系统的定时器管理队列中摘除一个定时器对象 */
del_timer(&buttons_timer);
/* 注销输入设备结构体 */
input_unregister_device(buttons_inputdev);
/* 释放分配的空间 */
input_free_device(buttons_inputdev);
}
5、完整buttons.c文件
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <linux/gpio_keys.h>
#include <asm/arch/regs-gpio.h>
/* 按键信息 */
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
/* 存储4个按键的信息 */
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11,"S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19,"S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_inputdev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
/* 设置按键中断函数 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + HZ / 100);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器中断服务函数:防抖动
* 采用输入子系统,当有数据时则调用input_event
*/
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin); //读取IO口电平
/* 最后一个参数:0-松开,1-按下 */
if(pinval) {
/* 松开上报 并 上报一个event事件与同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_inputdev);
}else {
/* 按下上报 并 上报一个event事件与同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_inputdev);
}
}
/* 入口函数 */
static int buttons_init(void)
{
int error, i;
i = 0;
/* 1、分配一个input_dev结构体 */
buttons_inputdev = input_allocate_device();
if (!buttons_inputdev)
return -ENOMEM;
/* 2、设置 */
/* 2.1 设置可以产生按键类事件 */
set_bit(EV_KEY, buttons_inputdev->evbit);
/* 2.2 设置这类操作里产生哪些事件:L, S,ENTER,SHIFT */
set_bit(KEY_L, buttons_inputdev->keybit);
set_bit(KEY_S, buttons_inputdev->keybit);
set_bit(KEY_ENTER, buttons_inputdev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_inputdev->keybit);
/* 3、注册: 会建立连接 */
error = input_register_device(buttons_inputdev);
if (error) {
printk(KERN_ERR "Unable to register gpio-keys input device\n");
goto fail;
}
/* 4、硬件相关操作 */
/* 4.1 初始化定时器相关操作 */
init_timer(&buttons_timer); //定时器初始化
buttons_timer.function = buttons_timer_function; //定时器处理函数
add_timer(&buttons_timer);
/* 4.2 注册中断 */
for (i = 0; i < 4; i++) {
error = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "buttons: unable to claim irq error %d\n", error);
goto fail;
}
}
return error;
fail:
for (i = i - 1; i >= 0; i--)
free_irq(pins_desc[i].irq, &pins_desc[i]);
input_free_device(buttons_inputdev);
return error;
}
/* 出口函数 */
static void buttons_exit(void)
{
int i;
/* 释放中断 */
for (i = 0; i < 4; i++) {
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
/* 从系统的定时器管理队列中摘除一个定时器对象 */
del_timer(&buttons_timer);
/* 注销输入设备结构体 */
input_unregister_device(buttons_inputdev);
/* 释放分配的空间 */
input_free_device(buttons_inputdev);
}
/* 修饰 */
module_init(buttons_init);
module_exit(buttons_exit);
/* 协议 */
MODULE_LICENSE("GPL");
三、建立连接流程梳理
四、编译与烧写分析
1、分析设备号,查看evdev_handler是否支持buttons_inputdev
编译成.ko
文件并加载后查看其属性:
-
加载前:
-
加载后:
可以知道其主设备号为13,次设备号为65,设备名字为event1
这个时候查看evdev.c的源码分析如下图: -
建立连接时,会调用
.connect()
函数创建设备节点,分配主次设备号,其中主设备号:13,次设备号:从64开始递加分配
根据buttons.ko
的主次设备号与名字,可知二者成功连接。
2、测试
-
第一次测试的时候出现如下问题:按下按键后乱码
-
采用hexdump
查看得到如下结果:
通过分析可知,测试结果是正确的,可是为什么会出现乱码呢?
通过老师介绍知道,有Qt进程在运作,在测试时需要杀掉,但是实际在我开发版上的查看进程时发现并没有Qt程序在运行。 -
第二次测试
有两种测试方法:
①、采用exec命令:exec 0</dev/tty1
②、采用cat /dev/tty1
此时需要按下回车按键后才可以看到ls
五、改进:添加重复类事件
在之前的测试中发现,长按代表l的按键,l只输出一次,需要添加重复类事件
测试可以发现:支持按键长按