RK3568驱动指南|第十三篇 输入子系统-第144章 继续完善设备驱动层代码

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十三篇 输入子系统_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第144章 继续完善设备驱动层代码

在编写最简单的设备驱动层代码中,为了便于理解只填充了以下四个步骤:1.创建输入设备结构体变量、2.初始化输入设备结构体变量、3.注册输入设备结构体变量、5.注销和释放输入设备结构体变量,而在本章节将会对设备驱动层代码要填充的第四个步骤上报事件进行讲解和实验。

144.1 上报事件

上报事件是指在设备驱动层中,当输入设备产生事件时,将该事件通知给输入子系统。在上报事件之前,首先要确定要上报的事件类型。事件类型可以是按键事件、相对位置事件、绝对位置事件等,取决于输入设备的特性和能力。在Linux内核中,事件类型由预定义的常量表示,如EV_KEY表示按键事件,EV_REL表示相对位置事件,EV_ABS表示绝对位置事件等。在前面编写的最简单的设备驱动层代码的第二个步骤中已经对事件类型和具体事件进行了确认。 ​ 而在确定事件类型之后,就需要使用相应的上报函数将事件数据传递给输入子系统。常用的上报函数包括:

input_report_key():上报按键事件,用于通知按键的按下和释放状态。

input_report_rel():上报相对位置事件,用于通知设备的相对移动量,如鼠标的移动。

input_report_abs():上报绝对位置事件,用于通知设备的绝对位置,如触摸屏的坐标。

144.2 上报函数

当在设备驱动层中使用上报函数时,这些函数负责将事件数据传递给输入子系统,以便将事件信息传递给应用程序或系统组件进行响应和处理。下面逐个详细讲解每个上报函数的作用和参数:

函数1:input_report_key函数

函数原型:input_report_key(struct input_dev *dev, unsigned int code, int value):

功能:上报按键事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

code:按键码,表示按下或释放的具体按键。

value:按键状态,0 表示按键释放,非零值表示按键按下。

函数2:input_report_rel函数

函数原型:input_report_rel(struct input_dev *dev, unsigned int code, int value):

功能:上报相对位置事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

code:位置码,表示相对位置的具体类型。

value:位置偏移量,表示设备相对于先前位置的移动量。

函数3:input_report_key函数

函数原型:input_report_abs(struct input_dev *dev, unsigned int code, int value):

功能:上报绝对位置事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

code:位置码,表示绝对位置的具体类型。

value:位置值,表示设备的绝对位置。

函数4:input_report_key函数

input_report_ff_status(struct input_dev *dev, unsigned int code, int value):

功能:上报力反馈状态事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

code:力反馈码,表示力反馈的具体类型。

value:力反馈状态,0 表示停止,非零值表示运行中。

函数5:input_report_key函数

函数原型:input_report_switch(struct input_dev *dev, unsigned int code, int value):

功能:上报开关事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

code:开关码,表示开关的具体类型。

value:开关状态,0 表示关闭,非零值表示打开。

函数6:input_sync函数

函数原型:input_sync(struct input_dev *dev):

功能:同步事件。

参数:

dev:输入设备结构体指针,表示要发送事件的输入设备。

每个上报函数都是内联函数,通过调用input_event()函数将事件数据添加到输入事件队列中。这些函数的参数中包含了事件类型(如EV_KEY、EV_REL等)、事件码(如按键码、位置码等)以及事件的具体值(如按键状态、位置偏移量等)。这些参数用于构造输入事件,并将其添加到输入事件队列中,以便后续的处理。

在使用上报函数之后,通常会调用input_sync()函数进行同步。同步事件的目的是告知输入子系统事件的结束,以便子系统可以将事件传递给相应的应用程序或系统组件进行处理。同步事件的调用可以防止事件数据的丢失或混乱。

144.3 驱动程序的编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\92_myinput_dev_02\02_module

由于RK3568开发板上的按键是ADC按键,并不是普通的按键,所以在本章节编写的驱动程序中并不通过按键的按下和弹起来进行事件的上报,而是采用定时器的方式对数据进行循环上报,当后面讲解到ADC相关的章节时会对本章内容进行回顾。

编写完成的myinput_dev.c代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/timer.h>

struct input_dev *myinput_dev; // 输入设备结构体指针

static void timer_function(struct timer_list *t);

DEFINE_TIMER(test_timer, timer_function); // 定义定时器

static void timer_function(struct timer_list *t) {
    static int value = 0; // 静态变量用于切换键值
    value = value ? 0 : 1;

    input_event(myinput_dev, EV_KEY, KEY_1, value); // 发送按键事件
    input_event(myinput_dev, EV_SYN, SYN_REPORT, 0); // 发送同步事件

    mod_timer(&test_timer, jiffies + msecs_to_jiffies(1000)); // 更新定时器
}

static int myinput_dev_init(void) {
    int ret;

    myinput_dev = input_allocate_device(); // 分配输入设备
    if (myinput_dev == NULL) {
        printk("input_allocate_device error\n");
        return -ENOMEM;
    }

    myinput_dev->name = "myinput_dev"; // 设置设备名
    set_bit(EV_KEY, myinput_dev->evbit); // 设置支持的事件类型:按键事件
    set_bit(EV_SYN, myinput_dev->evbit); // 设置支持的事件类型:同步事件
    set_bit(KEY_1, myinput_dev->keybit); // 设置支持的按键键值

    ret = input_register_device(myinput_dev); // 注册输入设备
    if (ret < 0) {
        printk("input_register_device error\n");
        goto error;
    }

    mod_timer(&test_timer, jiffies + msecs_to_jiffies(1000)); // 启动定时器

    return 0;

error:
    input_free_device(myinput_dev);
    return ret;
}

static void myinput_dev_exit(void) {
    del_timer(&test_timer); // 删除定时器
    input_unregister_device(myinput_dev); // 取消注册输入设备
    input_free_device(myinput_dev); // 释放输入设备内存
}

module_init(myinput_dev_init);
module_exit(myinput_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

144.4 运行测试

144.4.1 编译驱动程序

在上一小节中的myinput_dev.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += myinput_dev.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
        make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
        make -C $(KDIR) M=$(PWD) clean    #make clean操作    

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放myinput_dev.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成myinput_dev.ko目标文件,如下图所示:

至此驱动模块就编译成功了。

144.4.2 运行测试

首先启动开发板,进入系统之后如下所示:

然后将上一小节编译完成的myinput_dev.ko驱动文件拷贝到开发板上,拷贝完成如下所示:

在加载驱动之前首先使用以下命令查看当前的输入设备,如下所示:

 ls /dev/input

可以看到目前有4个输入设备的设备节点,然后使用以下命令进行驱动的加载,如下图所示:

 insmod myinput_dev.ko

然后重新查看设备节点,可以看到多出来了一个event4节点,如下图所示: 然后使用以下命令查看设备节点的输出信息,如下图所示:

 hexdump /dev/input/event4

可以看到,每隔一秒钟就会打印一次数据,至此,关于在最简单的设备驱动程序中完善上报事件的实验就完成了。

144.5 编写应用获取上报数据

在上一个小节的实验中,我们是通过hexdump命令从输入设备的设备节点获取的16进制数据,那要如何通过编写上层应用程序获取到上述相应的数据呢?

如果要读取某个输入设备的数据对应的流程如下所示:

每一步骤实现的具体代码,在之后的实验小节中会进行演示。

在第二步中,通过read函数进行读取输入设备数据的读取,每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,struct input_event 是 Linux 内核中用于描述输入事件的结构体类型。它定义在 <linux/input.h> 头文件中,用于在内核和用户空间之间传递输入事件的信息。在用户空间中,应用程序通过读取输入设备文件获得 struct input_event 数据,并解析其中的事件类型、事件码和事件值等信息,以进行相应的处理。结构体struct input_event 定义如下:

 struct input_event {
	struct timeval time;
	__u16 type;// 类型
	__u16 code;// 具体事件
	__s32 value;// 对应的取值
};

下面对struct input_event结构体的三个字段及其相应的宏进行详细的讲解:

type:type 用于描述发生了哪一种类型的事件(对事件的分类),Linux 系统所支持的输入事件类型如下所示:

#define EV_SYN          0x00	//同步类事件,用于同步事件
#define EV_KEY          0x01	//按键类事件
#define EV_REL          0x02	//相对位移类事件(譬如鼠标)
#define EV_ABS          0x03	//绝对位移类事件(譬如触摸屏)
#define EV_MSC          0x04	//其它杂类事件
#define EV_SW           0x05
#define EV_LED          0x11
#define EV_SND          0x12
#define EV_REP          0x14
#define EV_FF           0x15
#define EV_PWR          0x16
#define EV_FF_STATUS        0x17
#define EV_MAX          0x1f
#define EV_CNT          (EV_MAX+1)

以上这些宏定义同样在<linux/input.h>头文件中,所以在应用程序中需要包含该头文件;一种输入设备 通常可以产生多种不同类型的事件,譬如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键类事件,移动鼠标时则会上报相对位移类事件。

code:code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,以按键类事件为例,对应的具体事件如下所示:

#define KEY_RESERVED        0
#define KEY_ESC         1	//ESC 键
#define KEY_1           2	//数字 1 键
#define KEY_2           3	//数字 2 键
#define KEY_3           4	//数字 3 键
#define KEY_4           5	//数字 4 键
#define KEY_5           6	//数字 5 键
#define KEY_6           7	//数字 6 键
#define KEY_7           8	//数字 7 键
#define KEY_8           9	//数字 8 键
#define KEY_9           10	//数字 9 键
#define KEY_0           11	//数字 0 键
#define KEY_MINUS       12	//减号键
#define KEY_EQUAL       13	//加号键
#define KEY_BACKSPACE       14	//回退键
 ................................

对于其他输入事件的code值,可以查看input-event-codes.h 头文件(该头文件被<linux/input.h>所包含)。

value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件来说,如果 value 等于 1,则表示按键按下;value 等于 0 表示按键松开,如果 value 等于 2则表示按键长按。而在绝对位移事件中(type=3),如果 code=0(触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值;如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的 Y 轴坐标值。

接下来根据上面讲解的内容编写最简单的获取上报数据的应用程序,编写完成的应用程序存放路径为“iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\93_input_03,编写完成的应用程序代码如下所示:

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

int main() {
    int fd;  // 文件描述符
    int ret;  // 返回值
    struct input_event event;  // 输入事件结构体

    fd = open("/dev/input/event4", O_RDWR);  // 打开输入设备文件
    if (fd < 0) {
        printf("打开错误\n");
        return -1;
    }

    while (1) {
        ret = read(fd, &event, sizeof(struct input_event));  // 读取输入事件
        if (ret < 0) {
            printf("读取错误\n");
            return -2;
        }

        if (event.type == EV_KEY) {  // 判断事件类型为键盘事件
            if (event.code == KEY_1) {  // 判断键码为1
                if (event.value == 1)
                    printf("值为1\n");
                else if (event.value == 0)
                    printf("值为0\n");
            }
        }
    }

    return 0;
}

这个应用程序的主要功能是打开名为"/dev/input/event4"的输入设备文件,并读取输入事件。在读取事件后,它会检查事件的类型和代码,如果类型为EV_KEY且键值为KEY_1,则根据值的不同输出不同的消息。

144.6 应用程序测试

首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:

aarch64-linux-gnu-gcc app.c -o app

然后将编译完成的可执行程序拷贝到开发板上,拷贝完成如下所示:

 然后输入"./app"命令运行该应用程序,运行结果如下所示:

可以看到0和1的值每个一秒钟被交替打印,至此关于应用程序的测试就完成了,但对于通过hexdump命令获取得到的一系列16进制数据所表示的含义此时还并不清楚,会在下个章节中对得到的16进制数进行分析。

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值