Linux嵌入式驱动开发17——输入子系统

文章目录

  • 全系列传送门
  • 什么是输入子系统
  • 代码分析
  • 结果验证
  • 应用程序
  • 完整代码

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

什么是输入子系统

Linux嵌入式驱动开发17——输入子系统_第1张图片
Linux嵌入式驱动开发17——输入子系统_第2张图片
Linux嵌入式驱动开发17——输入子系统_第3张图片
Linux嵌入式驱动开发17——输入子系统_第4张图片

input事件结构体的定义如下,在Linux内核的include\linux\input.h文件中

struct timeval {
	__kernel_time_t		tv_sec;		/* seconds,32bit */
	__kernel_suseconds_t	tv_usec;	/* microseconds,32bit */
};

struct input_event {
	struct timeval time; //时间
	__u16 type; //事件类型
	__u16 code; //事件键值
	__s32 value; //值
};

可以看到,每一个input事件都有一个时间,包含32位的秒,32位的微妙

此外还有16位的type(事件类型),16位的code(事件键值),32位的value(值)

好,现在又有疑惑,type、code、value具体是什么?

type

type是指事件类型,在include\linux\input.h文件中定义了一系列的事件类型

#define EV_SYN			0x00 //同步事件,用于分隔事件
#define EV_KEY			0x01 //按键事件,例如按键、鼠标按键、触摸屏按键
#define EV_REL			0x02 //相对位置事件,常见于鼠标坐标
#define EV_ABS			0x03 //绝对位置事件,常见于触摸屏坐标

一个设备可以有多种类型的事件,例如鼠标点击按键时会上报按键事件,移动时会上报相对位置事件

code

code指事件的键值,在事件类型中的子事件,每一个事件类型都有其对应的一系列键值

例如按键事件,那么你这个按键表示按键1还是按键2还是鼠标左键

例如绝对位置事件,那么你上报的这个事件是指X轴还是Y轴

在include\linux\input.h文件中定义了一系列的事件键值

按键事件的键值

#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
...
#define KEY_A			30
#define KEY_S			31
#define KEY_D			32
...

相对位置事件的键值

#define REL_X			0x00 //x轴
#define REL_Y			0x01 //y轴
#define REL_Z			0x02 //z轴
#define REL_RX			0x03
#define REL_RY			0x04
#define REL_RZ			0x05
#define REL_HWHEEL		0x06
#define REL_DIAL		0x07
#define REL_WHEEL		0x08
#define REL_MISC		0x09

绝对位置事件

#define ABS_X			0x00
#define ABS_Y			0x01
#define ABS_Z			0x02
...
#define ABS_PRESSURE		0x18
...

代码分析

用到的重点就是input_dev结构体,先找到结构体所在的目录

/include/linux/input.c

Linux嵌入式驱动开发17——输入子系统_第5张图片
核心代码就是

   test_dev = input_allocate_device();

    test_dev->name = "test_key";

    __set_bit(EV_KEY, test_dev->evbit);//支持按键
    __set_bit(KEY_1, test_dev->keybit);//支持哪些按键

    /*注册输入设备*/
    ret = input_register_device(test_dev);
/*超时处理函数*/
static void timer_function(unsigned long data)
{
    int value;
    value = !gpio_get_value(gpio_name);                                          //获取gpio值
    input_report_key(test_dev, KEY_1, value);                                   //上报按键数据
    input_sync(test_dev);                                                       //上报一个同步事件

}

结果验证

没有安装模块前查看信息

ls /dev/input/

在这里插入图片描述

cat /proc/bus/input/devices

Linux嵌入式驱动开发17——输入子系统_第6张图片
然后开始安装模块
Linux嵌入式驱动开发17——输入子系统_第7张图片
安装成功之后,查看设备的信息

 cat /proc/bus/input/devices

在输入系统的设备驱动中,看到了我们注册的信息
Linux嵌入式驱动开发17——输入子系统_第8张图片

ls /dev/input/

设备驱动也可以看到多出来了一个event1,这就是我们添加的输入设备
在这里插入图片描述
然后来验证输入设备是否工作正常

hexdump /dev/input/event1

Linux嵌入式驱动开发17——输入子系统_第9张图片

应用程序

#include 
#include 
#include 
#include 
#include 
#include 

#include 

int main(int argc, char *argv[])
{
    int fd;

    int value;

    struct input_event test_event;

    fd = open("/dev/event1", O_RDWR);       // 打开节点时候触发open函数

    if(fd < 0){

        perror("open error\n");                 // perror在应用中打印
        return fd;
    }
    while(1){
        read(fd, &test_event, sizeof(test_event));
        
        if(test_event.type == EV_KEY){
            printf("type is %#x\n", test_event.type);
            printf("code is %#x\n", test_event.code);
            printf("value is %#x\n", test_event.value);
        }
    }

    return 0;
}

但是在板子上运行后,出现错误,显示open error
在这里插入图片描述
回头看app的代码,发现open路径出现错误,正确的应该是

/dev/input/event1

Linux嵌入式驱动开发17——输入子系统_第10张图片
修复之后运行,按键可以正常响应
Linux嵌入式驱动开发17——输入子系统_第11张图片

完整代码

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 

struct input_dev *test_dev;
struct device_node  *test_device_node;

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号

static void timer_function(unsigned long data);
/* 使用DEFINE_TIMER宏
 * 第一个参数:变量名
 * 第二个参数:超时处理函数
 * 第三个参数:传递给超时处理函数的参数
 * 第四个参数:到点时间,一般在启动定时器前需要重新初始化
 * */
DEFINE_TIMER(test_timer, timer_function, 0, 0);

/*超时处理函数*/
static void timer_function(unsigned long data)
{
    int value;
    value = !gpio_get_value(gpio_name);                                          //获取gpio值
    input_report_key(test_dev, KEY_1, value);                                   //上报按键数据
    input_sync(test_dev);                                                       //上报一个同步事件

}

/*中断处理函数*/
irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    printk("test_key_handle ok!!!\n");

    test_timer.expires = jiffies + msecs_to_jiffies(20);                        // 设置定时时间定时20ms
    add_timer(&test_timer);                                                     // 启动定时器

    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}


/*probe函数*/
int beep_probe(struct platform_device *pdev)
{
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");                       // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);                //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    gpio_direction_input(gpio_name);                                            // 因为是模拟按键,方向设置成输入模式
   
    // irq = gpio_to_irq(gpio_name);                                            // 通过gpio函数获取中断号,参数是gpio编号
   
    /* irq_of_parse_and_map,通过设备树中interrupts获取中断号
     * 第一个参数:设备树节点
     * 第二个参数:索引值,这里只有一个,所以是0
     * */
    irq = irq_of_parse_and_map(test_device_node, 0);
   
    printk("irq is %d\n", irq);
    /* request_irq
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,现在按键要检测按下和弹起的状态,所以双边沿触发
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }

    test_dev = input_allocate_device();

    test_dev->name = "test_key";

    __set_bit(EV_KEY, test_dev->evbit);//支持按键
    __set_bit(KEY_1, test_dev->keybit);//支持哪些按键

    /*注册输入设备*/
    ret = input_register_device(test_dev);
    if(ret < 0) {
        printk("input_register_device failed!!!\n");
        goto error_input_register;                                              // 失败时候应该是直接释放
    }else{
        printk("input_register_device successful!!!\n");
    }

    return 0;

error_input_register:
    input_unregister_device(test_dev);

}

const struct platform_device_id beep_id_table = {
    .name = "keys",
};

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "keys",
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);
    del_timer(&test_timer);
    input_unregister_device(test_dev);
    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

你可能感兴趣的:(i.MX6,linux,嵌入式,输入子系统,飞凌)