linux驱动篇-Input-button

Input-button

本篇是linux下input子系统下的按键(button/key)驱动,一起来动手吧。下面的话,老鸟可以跳过了直接从《需求描述》章节看起,新手可以试着看看。

 

前言

在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

 

也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

 

针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

 

最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

 

总体目标

本篇文章的目标是介绍如何利用linux下的input子系统自顶向下从零编写按键(button/key)驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

 

总体思路

总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

 

中年润在写代码的的总体思路如下:

需求描述—能够详细完整的描述一个需求。

需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

Makefile—用来编译驱动代码。

目录结构—用来说明当完成编码后的结果。

测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

执行结果—观察执行结果是否符合预期。

结果总结—回顾本节的思路,知识点,api,结构体。

实战目标—说明如何根据本文档训练。

请大家尽量按照自顶向下的学习思路来学习和实战,因为我们所有工作的动力都是我们心中的需求。这些步骤仅仅是我们达到目标所要走过的路。目录起到提纲挈领的重要作用,写的时候要实时看下提纲,看有没有偏离自己的方向。

 

需求描述

使用input子系统接口,编写一个按键驱动,能够让四个按键分别表达L,S,ENTER(回车换行),LEFTSHIFT(左shift)。

 

需求分析

根据《需求描述》,从宏观上提取可供实现的功能,我们需要做以下几件工作。

1需要使用input子系统接口

2需要能够检测按键的状态来判断是哪个按键按下或者松开

3需要通过四个按键的按下来上报L,S,ENTER,LEFTSHIFT4个事件

 

需求分解

根据《需求分析》的结果,将宏观确定的功能拆分成小的可单独实现的功能,我们需要做以下几件工作。

1需要注册一个input dev结构

2需要注册检测按键状态的中断函数

3需要在按下时,利用input api将按键的事件上报

4按键按下时一般会有抖动,需要通过定时器来延时消抖

 

编写思路

编写思路主要用来搭建代码框架,解决在宏观上如何用代码实现驱动的功能。

input驱动的核心是注册input设备,并注册检测按键状态的中断处理函数。

 

0搭建基础框架

0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE

0.2构造和定义基础数据结构

0.2.1构造引脚描述符数据结构

0.2.2重新定义引脚号码

0.2.3定义引脚描述符

 

在入口函数中所做的工作如下

1入口函数

1.1 分配一个input_dev 结构体

1.2 设置

1.2.1 设置能产生哪类事件

1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIT

1.3 注册input_dev

1.4 硬件相关操作

1.4.1 注册一个定时器

1.4.2 注册中断及中断处理函数

 

在出口函数中所作的工作如下

2出口函数

2.1 释放中断

2.2 删除定时器

2.3 卸载input设备

2.4 释放input设备

 

详细步骤

详细步骤主要用来在代码框架里填充必要的细节代码,解决在微观上如何用代码实现驱动各个小功能。

Input按键驱动的总体核心是注册input设备,并注册按键中断处理函数。

 

0搭建基础框架

0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE

0.2构造和定义基础数据结构

0.2.1构造引脚描述符数据结构

0.2.2重新定义引脚号码,就是GPIO口

0.2.3定义引脚描述符,定义按键所需要的资源

 

在入口函数中所做的工作如下:

1入口函数

1.1 分配一个input_dev 结构体

1.2 设置

1.2.1 设置能产生哪类事件

1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIT

1.3 注册input_dev

1.4 硬件相关操作

1.4.1 注册一个定时器

1.4.1.1编写定时器处理函数

1.4.2 注册中断及中断处理函数

1.4.2.1编写中断处理函数

 

在出口函数中所作的工作如下

2出口函数

2.1 释放中断

2.2 删除定时器

2.3 卸载input设备

2.4 释放input设备

 

编写框架

根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

 

 /* 本文件名字为input_button_skel.c*/
/* 本文件是依照input 驱动<编写思路>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0.1编写代码框架,头文件,出入口函数,声明LICENSE */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>
#include <linux/input.h>
/* 本文件的编写思路如下,请参考中年润所写的input驱动文档 */
/* 需求分析 --- 需求分解 --- 编写框架*/
 
/* 0.2构造和定义基础数据结构 */
/* 0.2.1构造引脚描述符数据结构 */
/* 引脚描述符 */
struct pin_desc{
       int irqnum;
       char *name;
       int pin;
       int key_val;
};
 
/*按引脚的不同定义不同的值,在中断中进行处理
*要自己重新定义下面几个宏,配合s3c2410_gpio_getpin函数使用
*/
/* 0.2.2重新定义引脚号码 */
#define S3C2410_GPF4 S3C2410_GPF(4)
#define S3C2410_GPF5 S3C2410_GPF(5)
#define S3C2410_GPF6 S3C2410_GPF(6)
#define S3C2410_GPF7 S3C2410_GPF(7)
 
static struct timer_list button_timer;
static struct pin_desc *irq_pindesc;
static struct input_dev * button_inputdev;
 
/* 0.2.3定义引脚描述符 */
/* 定义按键所需要的中断号,名字,gpio引脚,按键值*/
struct pin_desc pin_desc[4] =
{
 
};
 
/* 定时器函数 */
void button_timer_func(unsigned long data)
{
 
}
 
/* 按键中断函数 */
irqreturn_t button_irq(int irq, void * devid)
{
       return IRQ_RETVAL(IRQ_HANDLED);
}
 
/* 1入口函数 */
static int my_button_init(void)
{
       int ret = 0;
       int i = 0;
       /* 1.1 分配一个input_dev 结构体 */
       button_inputdev = input_allocate_device();
       /* 1.2 设置*/
       /* 1.2.1 设置能产生哪类事件 EV_KEY*/
       set_bit(EV_KEY,button_inputdev->evbit);
       set_bit(EV_REP,button_inputdev->evbit);
       /* 1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIFT */
       set_bit(KEY_L,button_inputdev->keybit);
       set_bit(KEY_S,button_inputdev->keybit);
       set_bit(KEY_ENTER,button_inputdev->keybit);
       set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
       /* 1.3 注册input_dev */
       ret = input_register_device(button_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
       /* 1.4 硬件相关操作 */
       /* 1.4.1 定时器相关操作 */
       init_timer(&button_timer);
       //buttons_timer.expires  = 0;
       button_timer.function = button_timer_func;
       add_timer(&button_timer);
       /* 1.4.2 注册中断及中断处理函数 */
       for (i = 0; i < 4; i ++){ 
              ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);
              if (ret != 0)
              {
                     printk("request button irq failed error code %d\n",ret);
              }
       }
 
       return ret;
}
 
/* 2出口函数 */
static void my_button_exit(void)
{
       int i = 0;
       /* 2.1 释放中断 */
       for (i = 0; i < 4; i ++){
              free_irq(pin_desc[i].irqnum,&pin_desc[i]);
       }
       /* 2.2 删除定时器 */
       del_timer(&button_timer);
       /* 2.3 卸载input设备 */
       input_unregister_device(button_inputdev);
       /* 2.4 释放input设备 */
       input_free_device(button_inputdev);
       return;
}
 
/* 0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

 

驱动代码

/* 本文件名字为input_button.c*/
/* 本文件是依照input 驱动<详细步骤>章节编写,本文件
  * 的目的是编写具体代码,不介绍框架
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 0.1编写代码框架,头文件,出入口函数,声明LICENSE */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>
#include <linux/input.h>
/* 本文件的编写思路如下,请参考中年润所写的input驱动文档 */
/* 需求分析 --- 需求分解 --- 编写框架--编写代码*/
 
/* 0.2构造和定义基础数据结构 */
/* 0.2.1构造引脚描述符数据结构 */
/* 引脚描述符 */
struct pin_desc{
       int irqnum;
       char *name;
       int pin;
       int key_val;
};
 
/*按引脚的不同定义不同的值,在中断中进行处理
*要自己重新定义下面几个宏,配合s3c2410_gpio_getpin函数使用
*/
/* 0.2.2重新定义引脚号码,就是GPIO口 */
#define S3C2410_GPF4 S3C2410_GPF(4)
#define S3C2410_GPF5 S3C2410_GPF(5)
#define S3C2410_GPF6 S3C2410_GPF(6)
#define S3C2410_GPF7 S3C2410_GPF(7)
 
static struct timer_list button_timer;
static struct pin_desc *irq_pindesc;
static struct input_dev * button_inputdev;
 
/* 0.2.3定义引脚描述符,定义按键所需要的资源 */
/* 定义按键所需要的中断号,名字,gpio引脚,按键值*/
struct pin_desc pin_desc[4] =
{
       {IRQ_EINT4,"S1",S3C2410_GPF4,KEY_L},
       {IRQ_EINT5,"S2",S3C2410_GPF5,KEY_S},
 
       {IRQ_EINT6,"S3",S3C2410_GPF6,KEY_ENTER},
       {IRQ_EINT7,"S4",S3C2410_GPF7,KEY_LEFTSHIFT},
};
 
/* 1.4.1.1编写定时器处理函数 */
/* 定时器函数 */
void button_timer_func(unsigned long data)
{
       int pin_val;
       struct pin_desc * pin_readed;
       pin_readed = irq_pindesc;
       if (!pin_readed)
              return;
      
       /*获取某个引脚是高还是低,需要配合上面定义的宏使用*/
       pin_val = s3c2410_gpio_getpin(pin_readed->pin);
       if (pin_val) /*松开是高电平*/
       {
              /*上报事件,0表示松开,最后一个参数: 0-松开, 1-按下*/
              input_event(button_inputdev, EV_KEY, pin_readed->key_val, 0);
       }
       else        /* 按下为低电平*/
       {
              /*上报事件,1表示按下,最后一个参数: 0-松开, 1-按下*/
              input_event(button_inputdev, EV_KEY, pin_readed->key_val, 1);
       }
       /* 上报同步事件*/
       input_sync(button_inputdev);     
 
}
 
/* 1.4.2.1编写中断处理函数 */
/* 按键中断函数 */
irqreturn_t button_irq(int irq, void * devid)
{
       /*记录传入的参数值,利用定时器延时,10ms后调用定时器中断处理函数
       *如果有按键抖动会多次进入按键中断函数,会多次修改调用定时器中断
       *的时机,最后的效果就是只调用了一次定时器中断处理函数
       *HZ代表1s,HZ/100是10ms
       *mod_timer第二个参数应该是jiffies+你想要延时的时间
       */
       irq_pindesc = (struct pin_desc *)devid;
       mod_timer(&button_timer,jiffies + HZ / 100);
       return IRQ_RETVAL(IRQ_HANDLED);
}
 
/* 1入口函数 */
static int my_button_init(void)
{
       int ret = 0;
       int i = 0;
       /* 1.1 分配一个input_dev 结构体 */
       button_inputdev = input_allocate_device();
       /* 1.2 设置*/
       /* 1.2.1 设置能产生哪类事件 EV_KEY*/
       set_bit(EV_KEY,button_inputdev->evbit);
       set_bit(EV_REP,button_inputdev->evbit);
       /* 1.2.2 设置能产生这类事件的哪些事件: L,S,ENTER,LEFTSHIFT */
       set_bit(KEY_L,button_inputdev->keybit);
       set_bit(KEY_S,button_inputdev->keybit);
       set_bit(KEY_ENTER,button_inputdev->keybit);
       set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
       /* 1.3 注册input_dev */
       ret = input_register_device(button_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
       /* 1.4 硬件相关操作 */
       /* 1.4.1 定时器相关操作 */
       init_timer(&button_timer);
       //buttons_timer.expires  = 0;
       button_timer.function = button_timer_func;
       add_timer(&button_timer);
       /* 1.4.2 注册中断及中断处理函数 */
       for (i = 0; i < 4; i ++){ 
              ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);           
              if (ret != 0)
              {
                     printk("request button irq failed error code %d\n",ret);
              }
       }
 
       return ret;
}
 
/* 2出口函数 */
static void my_button_exit(void)
{
       int i = 0;
       /* 2.1 释放中断 */
       for (i = 0; i < 4; i ++){
              free_irq(pin_desc[i].irqnum,&pin_desc[i]);
       }
       /* 2.2 删除定时器 */
       del_timer(&button_timer);
       /* 2.3 卸载input设备 */
       input_unregister_device(button_inputdev);
       /* 2.4 释放input设备 */
       input_free_device(button_inputdev);
       return;
}
 
/* 0.1编写代码框架,头文件,修饰出口函数,修饰入口函数,声明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

 

测试代码

本示例无需测试代码,可以通过相关命令测试。

 

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
all:
       make -C $(KERN_DIR) M=`pwd` modules
clean:
       make -C $(KERN_DIR) M=`pwd` modules clean
       rm -rf modules.order
obj-m += input_button.o

如果名字不同,请更换obj-m后面.o的名字。

 

目录结构

代码编写完成的目录结构如下所示。直接执行make即可生成.ko文件。

.
├── input_button.c
├── input_button_skel.c
└── Makefile

 

测试步骤

0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(arm-angstrom-linux-gnueabi-)

1 在menuconfig中配置好内核源码的目标系统为s3c2440

2 在pc上将驱动程序编译生成.ko,命令:make

3 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

4 insmod input_button.ko

input: Unspecified device as /class/input/input0

5 cat /dev/tty1

6依次按下四个按键,观察现象

 

执行结果

[root@TX2440A 8input-button]# cat /dev/tty1

lllllss

ls

ls

LS

ls

LS

llllllllllllllllll

ssssssssssss

单次按下,会有输出l,s,ls,配合板子的SHIFT键,会有输出L,S,LS

按着不动,会重复输出l或者s

 

[root@TX2440A 8input-button]# ls /dev/event*

/dev/event0

event0代表着input设备

 

结果总结

在本篇文章中,中年润跟读者分享了input按键驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构。它们分别是:

struct input_dev

struct timer_list

s3c2410_gpio_getpin

input_event

input_sync

input_allocate_device

set_bit

input_register_device

init_timer

request_irq

free_irq

del_timer

input_unregister_device

input_free_device

宏S3C2410_GPF()

请读者尽力去了解这些函数的作用,入参,返回值。

 

实战目标

1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

3请读者根据编写思路,独立写出编写框架。

4请读者根据详细步骤,独立编写驱动代码和测试代码。

5请读者根据《Makefile》章节,独立编写Makefile。

6请读者根据《测试步骤》章节,独立进行测试。

7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

 

参考资料

《linux设备驱动开发祥解》

《TX2440开发手册及代码》

《韦东山嵌入式教程》

《鱼树驱动笔记》

《s3c2440a》芯片手册英文版和中文版

 

 

致谢

感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

 

为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

 

联系方式

微信群:见文章最底部,因微信群有效期只有7天,感兴趣的同学可以加下。微信群里主要是为初学者答疑解惑,也可以进行技术和非技术的交流。如果微信群失效了,大家可以加qq群,我会在qq群里分享微信群的二维码。同时也欢迎和中年润志同道合的中年人尤其是中年码农的加入。

微信订阅号:自顶向下学嵌入式

公众号微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客网址:https://blog.csdn.net/chichi123137

QQ邮箱:834759803@qq.com

QQ群:766756075

更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎大家选购哦。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值