第二期驱动篇——2.2 输入子系统—按键编写

输入子系统——按键编写

  • 硬件平台:韦东山嵌入式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工具链


一、前言

对于我们之前写按键驱动,步骤如下:

  1. 确定主设备号(可自己指定,也可以有系统指定)
  2. 构造file_operation结构体,并把编写的read、write、open函数的地址保存在其中
  3. register_chrdrv()注册驱动设备
  4. 编写入口与出口函数

而对于上节中分析的输入子系统,编写驱动时,步骤如下:

  • 分配一个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只输出一次,需要添加重复类事件
在这里插入图片描述
测试可以发现:支持按键长按
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章