当前位置: 首页 > news >正文

【嵌入式Linux - 应用开发】输入设备

【目录】

【参考资料】

一、【理论】什么是输入系统

二、【理论】输入系统框架

三、【总结】Linux 输入事件处理流程

四、【理解】Linux 开发输入设备知识

(一) 【内核态】内核如何表示一个输入设备(struct input_dev)

1. 基本信息字段

2. 属性位图

3. 事件支持位图

(二) 【用户态】应用程序可以得到什么样的数据?(struct input_event)

(三) 【用户态】同步事件:类似数据包包尾,实现传输不定长数据(X、Y、Z、压力……)

五、【开发】调试方法

(一) 确定设备信息

1. 查看设备节点(ls /dev/input/* -l)

2. 查看设备节点对应的硬件信息(cat /proc/bus/input/devices)

【高阶】分析对应的硬件信息——devices字段含义解释(I、N、P、S、U、H、B)

(二) 使用命令读取数据(hexdump /dev/input/event*)

六、【开发】APP 访问(输入设备)硬件驱动的四种方式(查询/休眠唤醒/poll 方式/异步 IO)

【补充】ioctl 系统调用

函数原型

request 参数格式:_IOC 宏定义

位域布局

参数含义

(一) 【核心】输入设备的 ioctl 命令

(二) 【核心】读取输入设备上报的事件(struct input_event)

【开发方式】查询方式(非阻塞 IO,O_NONBLOCK)

【开发方式】休眠-唤醒方式(阻塞 IO)

【开发方式】POLL/SELECT方式

1. POLL 方式

2. SELECT 方式

【开发方式】异步通知方式(异步 IO):当输入设备的fd,发生事件/可操作时,内核通过SIGIO信号通知进程,进程进行信号处理


【参考资料】

  • 主要看韦东山、正点原子

一、【理论】什么是输入系统

  • 输入设备
    常见的有键盘、鼠标、遥控器、书写板、触摸屏等。用户通过这些设备与 Linux 系统进行数据交换。
  • 输入系统的作用
    • 输入设备种类繁多,如果每种设备都用不同的接口,开发和使用都会很复杂。
    • Linux 提供了一套 统一的输入系统框架,用来兼容和管理所有输入设备。
    • 驱动开发者:基于该框架开发驱动程序。
    • 应用开发者:通过统一的 API 使用各种输入设备,而不必关心底层差异。

👉 简单来说:Linux 的输入系统就是一个 抽象层/中间层,它屏蔽了不同输入设备的差异,让驱动和应用都能用统一的方式交互。

二、【理论】输入系统框架

Linux 输入子系统通过 统一框架 将不同输入设备抽象为统一接口:

  • 事件抽象struct input_event (time, type, code, value)
  • 用户层:APP 通过 /dev/input/eventX 或库函数读取事件
    • 同步机制EV_SYN 确保 APP 能识别一组完整的数据
  • 核心层:统一管理 (input_dev + handler)
  • 驱动层:负责采集硬件事件

三、【总结】Linux 输入事件处理流程

  • 用户操作
    用户通过键盘、鼠标、触摸屏等输入设备产生事件 → 触发硬件中断。
  • 驱动层
    • 驱动程序捕获中断,读取数据帧。
    • 将事件传递给 输入子系统核心层struct input_dev 结构体管理)。
    • 核心层将事件分发给对应的 事件处理 handler(如 evdev_handlerkbd_handlerjoydev_handler 等)。
  • 用户空间接口
    • 每个 handler 都会对应一个设备节点(如 /dev/input/event0)。
    • 应用程序(APP)可以直接读取这些设备节点获取事件。
  • 应用层获取方式
    • 直接读取:APP 直接访问 /dev/input/eventX
    • 通过库:使用 tsliblibinput 等库,这些库会封装底层设备节点的访问,提供统一接口,屏蔽不同设备差异。

👉 总结一句话:

Linux 输入系统通过 驱动 → 核心层 → handler → 设备节点 → APP 的链路,把底层硬件事件统一抽象出来,应用程序既可以直接读设备节点,也可以通过库获得统一接口。

四、【理解】Linux 开发输入设备知识

(一) 【内核态】内核如何表示一个输入设备(struct input_dev

1. 基本信息字段

  • const char *name;
    设备名称(字符串描述,例如 "Logitech USB Mouse")。
  • const char *phys;
    物理路径(如 "usb-0000:00:14.0-1/input0")。
  • const char *uniq;
    唯一标识符(通常是序列号)。
  • struct input_id id;
    设备 ID(包含总线类型、厂商 ID、产品 ID、版本号)。

2. 属性位图

  • unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    输入设备属性(如是否是指点设备、是否是直接输入设备等)。

3. 事件支持位图

  • evbit → 支持的事件类型(如 EV_KEY, EV_REL, EV_ABS)。
  • keybit → 支持的按键集合(如 KEY_A, KEY_ENTER)。
  • relbit → 支持的相对坐标事件(如 REL_X, REL_Y,典型于鼠标)。
  • absbit → 支持的绝对坐标事件(如 ABS_X, ABS_Y,典型于触摸屏/手柄摇杆)。
  • mscbit → 支持的杂项事件(如 MSC_SCAN)。
  • ledbit → 支持的 LED 指示灯(如 LED_CAPSL,键盘大写锁定灯)。
  • sndbit → 支持的声音事件(如蜂鸣器)。
  • ffbit → 支持的力反馈(Force Feedback)功能。
  • swbit → 支持的开关事件(如 SW_LID,笔记本合盖检测)。

📌 总结
struct input_dev 是 Linux 输入子系统的核心数据结构,用来描述一个输入设备的能力。通过这些位图,内核和用户空间可以快速判断设备支持哪些事件类型和功能。

(二) 【用户态】应用程序可以得到什么样的数据?(struct input_event

驱动层上报的数据

  • timeval:事件发生时间(自系统启动以来的秒/微秒)
  • type:事件类型
    • EV_KEY→ 按键
    • EV_REL→ 相对位移(鼠标移动)
    • EV_ABS→ 绝对位置(触摸屏)

  • code:具体事件编号(如哪个键、哪个坐标轴)
    • 对于按键类事件,它表示键盘。键盘上有很多按键

    • 对于触摸屏事件,它提供的是绝对位置信息,有 X 方向、 Y 方向,还有压力值

  • value:事件值(按下/释放/坐标/压力等)
    • 对于按键,它的 value 可以是 0(表示按键被按下)、 1(表示按键被松开)、2(表示长按);
    • 对于触摸屏,它的 value 就是坐标值、压力值。

(三) 【用户态】同步事件:类似数据包包尾,实现传输不定长数据(X、Y、Z、压力……)

  • 问题
    • 一个输入操作可能包含多个数据,如不同输入设备:
      • 触摸屏 A 会上报:X、Y 坐标和压力值
      • 触摸屏 B 会上报:X、Y 坐标
    • APP 如何知道一组数据是否完整?(ACK 机制)
  • 解决方案
    • 使用 同步事件 (EV_SYN) 表示一次数据上报的结束
    • 当 APP 读取到 EV_SYN 时,说明当前这组数据已完整,可以处理
struct input_event xxx;
.type = 0; (EV_SYN == 0)
.code = 0;
.value = 0;
  • 说明
    • 同步事件本质上也是一个 input_event
    • 具有 typecodevalue 三个字段
    • 常见定义:EV_SYN = 0x00

五、【开发】调试方法

(一) 确定设备信息

1. 查看设备节点(ls /dev/input/* -l

要确定输入设备的设备节点,其名称通常为/dev/input/eventX(或/dev/eventX),其中X代表数字0、1、2等。

可以通过执行以下命令来查看设备节点:

ls /dev/input/* -l
ls /dev/event* -l

2. 查看设备节点对应的硬件信息(cat /proc/bus/input/devices

cat /proc/bus/input/devices

这条指令的含义是获取与event对应的相关设备信息。


执行cat /proc/bus/input/devices命令后的输出示例,列出了两个输入设备的详细信息:

// 设备 1: Goodix Capacitive TouchScreen (触摸屏)
I: Bus=0018 Vendor=0416 Product=038f Version=1060
N: Name="Goodix Capacitive TouchScreen" (名称为"Goodix 电容触摸屏")
P: Phys=input/ts
S: Sysfs=/devices/platform/soc/5c002000.i2c/i2c-2/2-005d/input/input0
U: Uniq=
H: Handlers=kbd event0 (设备节点为/dev/input/event0)
B: PROP=2
B: EV=b
B: KEY=400 0 0 0 0 0 0 20000000 0 0 0
B: ABS=2658000 3// 设备 2: Joystick (GPIO按键)
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="Joystick" (名称为"操纵杆")
P: Phys=gpio-keys/input0 (物理路径为GPIO按键)
S: Sysfs=/devices/platform/joystick/input/input1
U: Uniq=
H: Handlers=kbd event1 (设备节点为/dev/input/event1)
B: PROP=0
B: EV=3
B: KEY=50000000

【高阶】分析对应的硬件信息——devices字段含义解释(INPSUHB

cat /proc/bus/input/devices的输出中,INPSUHB对应的每一行都有特定的含义:


设备 ID (I)

该参数由结构体struct input_id来描述:

// include/uapi/linux/input.h
struct input_id {__u16 bustype;__u16 vendor;__u16 product;__u16 version;
};

这个结构体包含了总线类型(bustype)、供应商ID(vendor)、产品ID(product)和版本号(version)。


其他字段含义

  • N: Name (设备名称)
  • P: Phys (物理路径)
  • S: Sysfs (sysfs路径)
  • U: Uniq (唯一标识符)
  • H: Handlers (处理程序和对应的设备节点)
  • B: PROP, EV, KEY, ABS (设备支持的属性、事件类型、按键和绝对坐标等信息)

位图解释(EV位图示例)

B位图,如"B: EV=b",用于指示设备支持哪些类型的输入事件。

B: EV=b																	// 设备支持哪些类型的输入事件
B: KEY=400 0 0 0 0 0 0 20000000 0 0 0		// 支持的KEY类型事件中的哪些具体事件
B: ABS=2658000 3												// 支持的ABS类型事件中的哪些具体事件

'b'的二进制表示为1011

bit0bit1bit31,表示设备支持这三种类型的事件:

  • EV_SYN == 0
  • EV_KEY == 1
  • EV_ABS == 3

【EV_ABS】ABS 位图示例(绝对位置)

另一个例子是"B: ABS=2658000 3"。

这表示设备在EV_ABS事件类别中支持哪些特定事件。

这是两个32位数字:0x26580000x3

  • 高位在前,低位在后,形成64位数字:"0x2658000,00000003"。
  • 值为1的位位于位置:014748505354
  • 这些对应以下十六进制值:010x2f0x300x320x350x36

绝对位置事件宏定义

// include/uapi/linux/input.h
#define ABS_X                   0x00
#define ABS_Y                   0x01#define ABS_MT_SLOT             0x2f    /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR      0x30    /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR      0x31    /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR      0x32    /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR      0x33    /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION      0x34    /* Ellipse orientation */
#define ABS_MT_POSITION_X       0x35    /* Center X touch position */
#define ABS_MT_POSITION_Y       0x36    /* Center Y touch position */

支持的绝对位置事件

该输入设备支持上述列出的绝对位置事件:ABS_XABS_YABS_MT_SLOTABS_MT_TOUCH_MAJORABS_MT_WIDTH_MAJORABS_MT_POSITION_XABS_MT_POSITION_Y

它们的具体含义将在后续讨论电容屏时详细阐述。

(二) 使用命令读取数据(hexdump /dev/input/event*

hexdump /dev/input/event*

【自己实测的触摸屏】

六、【开发】APP 访问(输入设备)硬件驱动的四种方式(查询/休眠唤醒/poll 方式/异步 IO)

【补充】ioctl 系统调用

函数原型

int ioctl(int fd, unsigned long request, ...);

其中:

  • fd:设备的文件描述符
  • request:请求参数,对于某些驱动程序有特定的格式要求
  • ...:可变参数,用于传递数据

request 参数格式:_IOC 宏定义

request参数的格式由_IOC宏定义,该宏在include/uapi/asm-generic/ioctl.h头文件中定义:

#define _IOC(dir,type,nr,size) \(((dir)  << _IOC_DIRSHIFT) | \		// _IOC_DIRSHIFT == bit29((type) << _IOC_TYPESHIFT) | \		// bit8((nr)   << _IOC_NRSHIFT) | \		// bit0((size) << _IOC_SIZESHIFT))		// bit16

位域布局

  • dir:被左移到第29位 (_IOC_DIRSHIFT)
  • type:被左移到第8位 (_IOC_TYPESHIFT)
  • nr:被左移到第0位 (_IOC_NRSHIFT)
  • size:被左移到第16位 (_IOC_SIZESHIFT)

这个宏将四个部分组合成一个unsigned long类型的request值:

参数含义

  • dir (方向):表示数据传输的方向
    • _IOC_NONE (0):无数据传输
    • _IOC_WRITE (1):应用程序要向设备写入数据
    • _IOC_READ (2):应用程序要从设备读取数据
    • _IOC_READ|_IOC_WRITE (3):双向数据传输
  • type (类型):表示设备类型或命令组,通常用单个字符表示
  • nr (编号):表示具体的命令编号
  • size (大小):表示这个ioctl能传输数据的最大字节数

(一) 【核心】输入设备的 ioctl 命令

// include/uapi/linux/input.h// 获取设备信息
#define EVIOCGID		_IOR('E', 0x02, struct input_id)	/* get device ID */err = ioctl(fd, EVIOCGID, &id);
if (err == 0) {printf("bustype = 0x%x\n", id.bustype);printf("vendor	= 0x%x\n", id.vendor);printf("product = 0x%x\n", id.product);printf("version = 0x%x\n", id.version);
}// 获取事件位图
#define EVIOCGBIT(ev,len)	_IOC(_IOC_READ, 'E', 0x20 + (ev), len)len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if (len > 0 && len <= sizeof(evbit)) {printf("support ev type: ");for (i = 0; i < len; i++) {byte = ((unsigned char*)evbit)[i];for (bit = 0; bit < 8; bit++) {if (byte & (1 << bit)) {printf("%s ", ev_names[i * 8 + bit]);}}}printf("\n");
}// 获取设备名称
#define EVIOCGNAME(len)		_IOC(_IOC_READ, 'E', 0x06, len)// 获取物理路径
#define EVIOCGPHYS(len)		_IOC(_IOC_READ, 'E', 0x07, len)// 获取唯一标识符
#define EVIOCGUNIQ(len)		_IOC(_IOC_READ, 'E', 0x08, len)// 获取设备属性
#define EVIOCGPROP(len)		_IOC(_IOC_READ, 'E', 0x09, len)// 获取全局按键状态
#define EVIOCGKEY(len)		_IOC(_IOC_READ, 'E', 0x18, len)// 获取所有LED状态
#define EVIOCGLED(len)		_IOC(_IOC_READ, 'E', 0x19, len)// 获取所有声音状态
#define EVIOCGSND(len)		_IOC(_IOC_READ, 'E', 0x1a, len)// 获取所有开关状态
#define EVIOCGSW(len)		_IOC(_IOC_READ, 'E', 0x1b, len)

(二) 【核心】读取输入设备上报的事件(struct input_event

struct input_event event;
while (read(fd, &event, sizeof(event)) == sizeof(event))
{printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}

【开发方式】查询方式(非阻塞 IO,O_NONBLOCK

fd = open(argv[1], O_RDWR | O_NONBLOCK);

当APP调用read函数读取数据时:

  • 如果驱动程序中有数据,read函数会立即返回数据
  • 否则,read函数会立刻返回错误,不会等待数据

【开发方式】休眠-唤醒方式(阻塞 IO)

fd = open(argv[1], O_RDWR);

当APP调用read函数读取数据时:

  • 如果驱动程序中有数据,read函数会立即返回数据
  • 否则,APP会进入内核态休眠【进程会进入休眠状态】
  • 当有数据可用时,驱动程序会唤醒APP
  • read函数会恢复执行并返回数据给APP

【开发方式】POLL/SELECT方式

1. POLL 方式

int fd;
int ret;
int bit;
struct input_event event;
struct input_id id;struct pollfd fds[1];
nfds_t nfds = 1;fd = open(argv[1], O_RDWR | O_NONBLOCK);while (1)
{fds[0].fd = fd;fds[0].events  = POLLIN;fds[0].revents = 0;ret = poll(fds, nfds, 5000);if (ret > 0){if (fds[0].revents & POLLIN){while (read(fd, &event, sizeof(event)) == sizeof(event)){printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);}}}else if (ret == 0){printf("time out\n");}else{printf("poll err\n");}
}

2. SELECT 方式

int fd;
int err;
int len;
int ret;
int i;
unsigned char byte;
int bit;
struct input_id id;
unsigned int evbit[2];
struct input_event event;
int nfds;
struct timeval tv;
fd_set readfds;while (1)
{/* 设置超时时间 */tv.tv_sec  = 5;tv.tv_usec = 0;/* 想监测哪些文件? */FD_ZERO(&readfds);    /* 先全部清零 */	FD_SET(fd, &readfds); /* 想监测fd *//* 函数原型为:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);* 我们为了"read"而监测, 所以只需要提供readfds*/nfds = fd + 1; /* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */ ret = select(nfds, &readfds, NULL, NULL, &tv);if (ret > 0)  /* 有文件可以提供数据了 */{/* 再次确认fd有数据 */if (FD_ISSET(fd, &readfds)){while (read(fd, &event, sizeof(event)) == sizeof(event)){printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);}}}else if (ret == 0)  /* 超时 */{printf("time out\n");}else   /* -1: error */{printf("select err\n");}
}

【开发方式】异步通知方式(异步 IO):当输入设备的fd,发生事件/可操作时,内核通过SIGIO信号通知进程,进程进行信号处理

int fd;// 当输入设备的fd,发生事件/可操作时,内核通过SIGIO信号
void my_sig_handler(int sig)
{if (sig != SIGIO)return ;struct input_event event;while (read(fd, &event, sizeof(event)) == sizeof(event))	// 操作输入设备fd{printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);		}
}int main(int argc, char **argv)
{/* 注册信号处理函数 */signal(SIGIO, my_sig_handler);/* 打开驱动程序 */fd = open(argv[1], O_RDWR | O_NONBLOCK);/* 把APP的进程号告诉驱动程序 */fcntl(fd, F_SETOWN, getpid());/* 使能"异步通知" */flags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, flags | FASYNC);while (1) {printf("main loop count = %d\n", count++);sleep(2);}...
}
http://www.dtcms.com/a/443873.html

相关文章:

  • Hadess入门到实战(6) - 如何管理Helm制品
  • wordpress建淘宝客网站苏州工业园区职业技术学院
  • linode wordpress建站上海注册设计公司网站
  • 太仓网站建设教程西安微信小程序制作公司
  • 中韩双语网站制作价格网站开发的软件介绍
  • 做网站中心网站建设心得8000字
  • 带产品展示的个人网站模板网站平面模板
  • 赣州做网站公司哪家好wordpress 主题 模板 区别
  • 长春二道网站建设html网页设计模板和源代码
  • 阿里巴巴网wordpress优化速度
  • 采集wordpress文章上传seo推广排名重要吗
  • 上海网站备案网站怎样设计一个网页页面
  • 国外订房网站怎么和做创意网站建设设计
  • 湖南网站设计外包费用吉林移动网站
  • 陈村网站设计龙岩iot开发福建小程序建设
  • 网站服务器租用价格商城页面
  • 手机网站打开微信号开发网站如何选需要注意什么问题
  • 宣传片制作网站微信小程序第三方免费制作平台
  • 自己做的网站怎么被搜录阜阳恒亮做网站多少钱
  • dede手机网站标签wordpress 标题字体大小
  • 引导型网站设计做拍卖的网站有哪些
  • 宝安做网站的公司昆山住房和城乡建设局网站
  • 亚马逊品牌网站怎么做校园网站建设提升
  • 分析递归的过程
  • 做购物网站公司网站关键词添加后的后果
  • 微官网 手机网站网站优化图片链接怎么做
  • 石家庄建设路网站怎么分析竞争对手网站
  • 预瞄控制中的相关信息
  • 网站离线浏览器 怎么做景泰做网站
  • 秀洲住房与建设局网站地方生活门户网站名称