fl2440——内核自带按键驱动的移植与测试

时间:2021-06-08 16:32:14

      陌上人如玉,公子世无双。雨虽大,但心境不能乱,是时候静下心来好好整理一篇博客了!

==========================================================================

主机操作系统:Centos 6.7 
交叉编译器环境:arm-linux-gcc-4.5.4 (可通过命令/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc -v查询)
开发板平台: fl2440 
Linux内核版本: linux-3.0 .54

==========================================================================

       linux内核本身就带有按键驱动,只需要我们对其进行添加即可。那么linux是如何实现按键功能的。这里主要涉及到两方面的内容:linux的中断处理和输入子系统
      Linux中断分为两个部分:前半部和后半部。前半部是实际响应中断的函数,需要用request_irq注册;后半部是由前半部调度,并在一个更安全的时间来执行的函数,该函数就是具体负责执行中断的内容。前半部不允许被其他中断干扰,因此它的内容短小,而执行后半部时可以响应其他中断。这种机制的好处是可以迅速的响应中断。

      因为有了子系统的概念,所以在Linux内核自带驱动程序中,我们往往会发现一个驱动程序所定义的结构体以及函数被放在不同的文件夹下,子系统包括三个层次:事件处理层(Event Handler)核心层(Input Core)驱动层(Input Driver)在led程序中已经对子系统做过简单介绍,在这就不加赘诉了。

      该驱动程序gpio_keys.c在目录fl2440/kernel/linux-3.0.54/drivers/input/keyboard 中可以找到。

一、修改mach-smdk2440.c:

       根据驱动程序的.name,我们可以找到相关的设备文件信息存放在哪里,然后将相关设备信息添加到我们的mach-smdk2440.c中去。

[yangni@yangni linux-3.0.54]$ grep gpio-keys -r *       // gpio-keys 为匹配的name名

fl2440——内核自带按键驱动的移植与测试

找到以后,将button的相关设备信息添加到我们的mach-smdk2440.c中去。

以下是修改后 mach-smdk2440.c文件的补丁文件:

      我们开发板上只有4个按键,所以我们只需要四个成员,删除多余的。并且把4个gpio管脚设置分别为0 ,2 ,3,4,并且修改按键code的值。相关诸如KEY_1,KEY_2等在头文件linux-3.0.54/include/linux/input.h中定义

fl2440——内核自带按键驱动的移植与测试

+++ mach-smdk2440.c2017-04-22 15:36:58.078925861 -0800
@@ -52,6 +52,15 @@
#include <plat/common-smdk.h>

#include <linux/dm9000.h> //添加DM9000网卡的头文件
+
+//add for bubtton
+#include <linux/input.h> //添加输入子系统头文件
+

static struct map_desc smdk2440_iodesc[] __initdata = {
/* ISA IO Space map (memory space selected by A24) */
@@ -231,13 +240,102 @@
.id= 0,
};

+#if 1
+//add for button
+
+ static struct gpio_keys_button rx1950_gpio_keys_table[] = {
+ {
+ .code = KEY_1,
+ .gpio = S3C2410_GPF(0),
+ .active_low = 1,
+ .desc = "Button_1",
+ .wakeup = 1,
+ },
+ {
+ .code = KEY_2,
+ .gpio = S3C2410_GPF(2),
+ .active_low = 1,
+ .desc = "Button_2",
+ },
+ {
+ .code = KEY_3,
+ .gpio = S3C2410_GPF(3),
+ .active_low = 1,
+ .desc = "Button_3",
+ },
+ {
+ .code = KEY_4,
+ .gpio = S3C2410_GPF(4),
+ .active_low = 1,
+ .desc = "Button_4",
+ },
+};
+
+static struct gpio_keys_platform_data rx1950_gpio_keys_data = {
+ .buttons = rx1950_gpio_keys_table,
+ .nbuttons = ARRAY_SIZE(rx1950_gpio_keys_table),
+};
+
+static struct platform_device rx1950_device_gpiokeys = {
+ .name = "gpio-keys",
+ .dev.platform_data = &rx1950_gpio_keys_data,
+};

+#endif

static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_ohci,
@@ -253,6 +351,16 @@
//add for beeper
&s3c_device_timer,
&smdk2440_beeper_device,

+ //add for button
+ &rx1950_device_gpiokeys,
+
};

static void __init smdk2440_map_io(void)
修改完后直接make,默认配置上选上支持按键的,所以无需修改配置,将生成的内核加载到开发板上。


二、开发板上测试:

(1)查看相关信息:

ls -l /proc/bus/input 可查看inptu下handler,它是用来事件的具体处理,它为输入设备的功能实现了一个接口

fl2440——内核自带按键驱动的移植与测试

cat devices 查看设备信息,我们可以看到为按键事件分配了一个设备几点,用于处理按键事件。

fl2440——内核自带按键驱动的移植与测试

(2)简单的测试:

我们可以用hexdump命令(查看”二进制“文件的十六进制编码)对按键进行有效测试:

fl2440——内核自带按键驱动的移植与测试

第2~5列:输入事件时间戳,即结构体中的time。

第6列:输入事件类型,即结构体中的type。

第7列:按键的键值,即结构体中的code。

第8列:按键的状态,即结构体中的value,1表示按下,0表示松开




        上面是依次按下4个按键所得到的结果,每按一个键,会出现4行数据,这是因为每按一次键包括键的按下和键的抬起两个动作,而每个动作结束后还会有一个同步事件发生,因此会出现4行数据。

        每行的倒数第四个数据到倒数第二数据分别对应input_event数据结构(同样定义在linux-3.0.54/include/linux/input.h)中的type ,code ,value

struct input_event {

   struct timeval time;

   __u16 type; //按键类型

   __u16 code; //按键代码

    __s32 value; //按键的值

};

  

       倒数第三行是我们的按键代码,每个按键代码是唯一的,所以我们可以,经测试得到每个按键代码值为:

KEY_1:   2

KEY_2:   3

KEY_3:   4

KEY_4:   5


接下来我们就可以根据code的值来判断是哪个按键按下,分别用来控制led灯。


三、测试程序:

    下面我们来写linux内核按键与led结合的测试程序:

/*********************************************************************************
* Copyright: (C) 2017 qicheng
* All rights reserved.
*
* Filename: led.c
* Description: This file
*
* Version: 1.0.0(04/30/2017)
* Author: yangni <497049229@qq.com>
* ChangeLog: 1, Release initial version on "04/30/2017 11:44:03 AM"
*
********************************************************************************/

#include <stdio.h>
#include<stdint.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<linux/input.h>
#include<unistd.h>


#include <stdlib.h>

#include <linux/input.h>

#define EV_PRESS 1
#define EV_RELEASE 0
int main(void)

{
char buf[50];
int fd_button;
int fd_led[4];
int i;

struct input_event ev_key;

fd_button= open("/dev/event0", 666);

if(fd_button < 0)

{

perror("open device buttons");

exit(1);

}

for(i=1;i<=4;i++)
{
snprintf((char *)buf,sizeof(buf),"/sys/class/leds/led%d/brightness",i);
fd_led[i] = open(buf, O_RDWR);
if(fd_led[i]<0)
{
printf("can't open the file led%d",i);
return -1;
}
}

while(1)
{
read(fd_button,&ev_key,sizeof(struct input_event));
if(EV_KEY==ev_key.type && EV_PRESS==ev_key.value) //value=1 表示现在是按下
{
switch(ev_key.code) //通过对code传值来确定是哪个按键
{ static int count_key1 = 0; //定义一个静态变量count_key1 ,来计算按键按下次数
case KEY_1:
if(count_key1%2==0)
{
write(fd_led[1],"1",1);
count_key1++;
}
else
{
write(fd_led[1],"0",1);
count_key1++;
}
break;
case KEY_2:
write(fd_led[2],"1",1);
sleep(1);
write(fd_led[2],"0",1);
break;
case KEY_3:
write(fd_led[3],"1",1);
sleep(1);
write(fd_led[3],"0",1);
break;
case KEY_4:
write(fd_led[4],"1",1);
sleep(1);
write(fd_led[4],"0",1);
break;
default:
break;

}
}
else if(EV_KEY==ev_key.type && EV_RELEASE==ev_key.value) //value=0表示现在按键释放
{
printf("relase the key!\n");
}

}

for(i=1;i<=4;i++)
{
close(fd_led[i]);
}


close(fd_button);
return 0;

}

       这里用for循环语句打开依次多个设备,用到了snprintf()函数。

int snprintf ( char *restrict buf , size_t n ,  const char * restrict  format, ...);
      函数说明:最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。

以上面测试程序为例子:

      循环打开led1,led2,led3,led4:

char buf[50];
int led[4];

for(i=1;i<=4;i++)
{
snprintf((char *)buf,sizeof(buf),"/sys/class/leds/led%d/brightness",i);
fd_led[i] = open(buf, O_RDWR);
if(fd_led[i]<0)
{
printf("can't open the file led%d",i);
return -1;
}
}
    注意,因为我们要打开的是led1,led2,led3,led4,所以%d直接跟在led后面。