Zephyr在v3.4.0中release输入子系统,它提供了处理输入事件(如键/按钮被按下,触摸屏被按下等)的高级别的抽象。这使得GUI和硬件底层输入解耦,让GUI开发更为容易。输入设备通过过输入子系统将标准的event送到应用程序。
导入输入子系统的原因: 没有通用的输入子系统,只有一个kscan最为接近,但kscan有诸多限制同时又被滥用
- kscan只支持单个监听器
- 设计初衷是用于按键矩阵
- 被滥用到触摸屏
- 一些设备没有模型,例如旋转编码器
Zephyr的输入子系统设计需求: - 支持多对多模型,即多个输入设备可以将事件发送到许多不同的侦听器
- 可以支持任何输入设备(按钮、键盘、鼠标、触摸屏等…),而不会导致 API 臃肿(无专门功能)
- 作为当前 kscan API 的替代品,提供向后兼容层,可在当前驱动程序和当前应用程序中使用
- 提供一种以通用方式扩展基本功能(长按、双击…)的方法
最后Zephyr 实现在概念上类似于 Linux,并进行简化。更多详细内容可以查看https://github.com/zephyrproject-rtos/zephyr/issues/54622
基本架构
输入子系统和相关设施如下:
- 提供输入子系统
zephyr/subsys/input/
- 提供输入设备驱动, 这些驱动使用input子系统上报事件
zephyr/drivers/input
- 将输入设备驱动转化为kscan,
zephyr/drivers/kscan/kscan_input.c
输入设备调用输入子系统的API将event送到输入子系统,输入子系统将event分发给APP.
kscan_input将输入子系统包装为kscan接口,向前兼容现有的kscan API.
接口
发送事件
设备驱动或者模块调用该input_report
发送产生的输入事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
@brief 报告一个新的输入事件。
这将触发指定设备的所有监听器,可以通过同步或通过输入线程实现。
@param dev 产生事件的设备或 NULL。
@param type 事件类型 (参见 @ref INPUT_EV_CODES)。
@param code 事件代码 (参见 @ref INPUT_KEY_CODES、@ref INPUT_BTN_CODES、 @ref INPUT_ABS_CODES、@ref INPUT_REL_CODES、@ref INPUT_MSC_CODES)。
@param value 事件值。
@param sync 设置事件的同步位。
@param timeout 报告事件的超时时间,如果使用 @kconfig{CONFIG_INPUT_MODE_SYNCHRONOUS} 将被忽略。
@retval 0 如果消息已被处理。
@retval 负数 如果启用了 @kconfig{CONFIG_INPUT_MODE_THREAD},并且消息未能进入队列。
*/
int input_report(const struct device *dev,
uint8_t type, uint16_t code, int32_t value, bool sync,
k_timeout_t timeout);
例如当触摸屏被按下时,触摸屏驱动按如下调用通知又触摸事件发生1
2input_report(touch_dev, INPUT_EV_ABS, INPUT_ABS_X, 120, true);
input_report(touch_dev, INPUT_EV_ABS, INPUT_ABS_Y, 200, true);
- dev是要发送input事件的设备驱动的device handle
type 事件类型
-1
2
3
4
5
6#define INPUT_EV_KEY 0x01 //输入设备(如键盘)上的按键按下和释放的状态,提供的值是一个有符号整数。如果该值为1,则表示按键被按下;如果该值为0,则表示按键被释放。
#define INPUT_EV_REL 0x02 //输入设备(如鼠标)的相对变化,提供的值是一个有符号整数。例如,在鼠标上向上滚动会产生一个EV_REL事件,值为正整数,而向下滚动则会产生一个EV_REL事件,值为负整数。
#define INPUT_EV_ABS 0x03 //输入设备(如触摸屏)的绝对坐标位置,提供的值是一个无符号整数。例如,在触摸屏上滑动手指时,会产生一系列EV_ABS事件,每个事件都表示触摸点的坐标。
#define INPUT_EV_MSC 0x04 //输入设备的其他杂项事件,提供的值是一个有符号整数。例如,EV_MSC事件可以用于描述输入设备的电池电量、信号强度等信息。
#define INPUT_EV_VENDOR_START 0xf0 //Vendor自定义类型,范围的起始值
#define INPUT_EV_VENDOR_STOP 0xff //Vendor自定义类型,范围的结束值code 事件码
- INPUT_KEY_CODES:0~9, A~Z, 音量+/-
INPUT_BTN_CODES:
-1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21INPUT_BTN_DPAD_DOWN:方向键向下
INPUT_BTN_DPAD_LEFT:方向键向左
INPUT_BTN_DPAD_RIGHT:方向键向右
INPUT_BTN_DPAD_UP:方向键向上
INPUT_BTN_EAST:东部按钮
INPUT_BTN_LEFT:左按钮
INPUT_BTN_MIDDLE:中间按钮
INPUT_BTN_MODE:模式按钮
INPUT_BTN_NORTH:北部按钮
INPUT_BTN_RIGHT:右按钮
INPUT_BTN_SELECT:选择按钮
INPUT_BTN_SOUTH:南部按钮
INPUT_BTN_START:开始按钮
INPUT_BTN_THUMBL:左拇指按钮
INPUT_BTN_THUMBR:右拇指按钮
INPUT_BTN_TL:左肩按钮
INPUT_BTN_TL2:左次级肩按钮
INPUT_BTN_TOUCH:触摸按钮
INPUT_BTN_TR:右肩按钮
INPUT_BTN_TR2:右次级肩按钮
INPUT_BTN_WEST:西部按钮INPUT_ABS_CODES
-1
2
3
4
5
6INPUT_ABS_X表示输入设备的X轴绝对坐标;
INPUT_ABS_Y表示输入设备的Y轴绝对坐标;
INPUT_ABS_Z表示输入设备的Z轴绝对坐标;
INPUT_ABS_RX表示输入设备的X轴旋转角度的绝对值;
INPUT_ABS_RY表示输入设备的Y轴旋转角度的绝对值;
INPUT_ABS_RZ表示输入设备的Z轴旋转角度的绝对值INPUT_REL_CODES
-1
2
3
4
5
6INPUT_REL_X表示输入设备的X轴相对坐标;
INPUT_REL_Y表示输入设备的Y轴相对坐标;
INPUT_REL_Z表示输入设备的Z轴相对坐标;
INPUT_REL_RX表示输入设备的X轴旋转角度的相对值;
INPUT_REL_RY表示输入设备的Y轴旋转角度的相对值;
INPUT_REL_RZ表示输入设备的Z轴旋转角度的相对值。
为了方便使用对一些指定类型进行了API封装
INPUT_EV_KEY
-1
2
3
4
5
6static inline int input_report_key(const struct device *dev,
uint16_t code, int32_t value, bool sync,
k_timeout_t timeout)
{
return input_report(dev, INPUT_EV_KEY, code, !!value, sync, timeout);
}INPUT_EV_REL
-1
2
3
4
5
6static inline int input_report_rel(const struct device *dev,
uint16_t code, int32_t value, bool sync,
k_timeout_t timeout)
{
return input_report(dev, INPUT_EV_REL, code, value, sync, timeout);
}INPUT_EV_ABS
-1
2
3
4
5
6static inline int input_report_abs(const struct device *dev,
uint16_t code, int32_t value, bool sync,
k_timeout_t timeout)
{
return input_report(dev, INPUT_EV_ABS, code, value, sync, timeout);
}
监听事件
应用通过INPUT_LISTENER_CB_DEFINE
向输入子系统注册callback,当input事件发生时进行callback, 如果input设备给NULL,所有input事件都会被callback响应
-
1
#define INPUT_LISTENER_CB_DEFINE(_dev, _callback)
- callback原型
void (*callback)(struct input_event *evt);
- callback输入参数
input_report
一一对应
-1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct input_event {
/** Device generating the event or NULL. */
const struct device *dev;
/** Sync flag. */
uint8_t sync;
/** Event type (see @ref INPUT_EV_CODES). */
uint8_t type;
/**
* Event code (see @ref INPUT_KEY_CODES, @ref INPUT_BTN_CODES,
* @ref INPUT_ABS_CODES, @ref INPUT_REL_CODES, @ref INPUT_MSC_CODES).
*/
uint16_t code;
/** Event value. */
int32_t value;
};
例如,当输入设备有event发生时就会呼叫input_cb
1
2INPUT_LISTENER_CB_DEFINE(NULL, input_cb); //监听所有输入设备
INPUT_LISTENER_CB_DEFINE(touch_dev, input_cb); //监听指定输入设备
查询事件
当配置为CONFIG_INPUT_MODE_THREAD=y
时输入子系统会内建msgq缓存输入事件,可以通过下面接口查询还有多少输入事件没有被处理1
bool input_queue_empty(void);
示例
以下示例在esp32c3_devkitm中使用输入子系统。
设备树中增加以下内容,gpio09作为按键,对应的event为INPUT_KEY_0
,启用长按按键,当长按该按键超过1s时发送INPUT_KEY_X,否则发送INPUT_KEY_A1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#include <dt-bindings/input/input-event-codes.h>
buttons: buttons {
compatible = "zephyr,gpio-keys";
button0: button_0 {
gpios = <&gpio0 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "User SW1";
zephyr,code = <INPUT_KEY_0>;
};
};
longpress {
input = <&buttons>;
compatible = "zephyr,input-longpress";
input-codes = <INPUT_KEY_0>;
short-codes = <INPUT_KEY_A>;
long-codes = <INPUT_KEY_X>;
long-delay-ms = <1000>;
};
程序中进行监控1
2
3
4
5
6
7
8
9
10
11
12#include <zephyr/input/input.h>
static void input_cb(struct input_event *evt)
{
printk("input event: dev=%-16s %3s type=%2x code=%3d value=%d\n",
evt->dev ? evt->dev->name : "NULL",
evt->sync ? "SYN" : "",
evt->type,
evt->code,
evt->value);
}
INPUT_LISTENER_CB_DEFINE(NULL, input_cb);
只需要有按键发生就会调用到input_cb。
按下时:
input event: dev=buttons SYN type= 1 code= 11 value=1
1s内松开
input event: dev=buttons SYN type= 1 code= 11 value=0
input event: dev=longpress SYN type= 1 code= 30 value=1
input event: dev=longpress SYN type= 1 code= 30 value=0
保持1s后
input event: dev=longpress SYN type= 1 code= 45 value=1
保持1s后再松开
input event: dev=buttons SYN type= 1 code= 11 value=0
input event: dev=longpress SYN type= 1 code= 45 value=0
参考
https://docs.zephyrproject.org/3.4.0/services/input/index.html