Zephyr输入子系统-2实现原理

Creative Commons
本作品采用知识共享署名

本文说明Zephyr输入子系统的实现原理。

Zephyr输入子系统提供了输入事件监听机制和长按键机制,本文分别说明其如何实现

实现原理

输入事件监听

使用INPUT_LISTENER_CB_DEFINE注册输入事件监听器,就是将struct input_listener结构体变量放入到input_listener段内

1
2
3
4
5
6
7
8
9
10
11
12
13
struct input_listener {
/** @ref device pointer or NULL. */
const struct device *dev;
/** The callback function. */
void (*callback)(struct input_event *evt);
};
//生成一个struct input_listener结构体变量,并用STRUCT_SECTION_ITERABLE放入input_listener段
#define INPUT_LISTENER_CB_DEFINE(_dev, _callback) \
static const STRUCT_SECTION_ITERABLE(input_listener, \
_input_listener__##_callback) = { \
.dev = _dev, \
.callback = _callback, \
}

当输入设备使用input_report报告event时,会调用到input_processinput_listener段内的变量进行遍历安装dev进行callback

1
2
3
4
5
6
7
8
static void input_process(struct input_event *evt)
{
STRUCT_SECTION_FOREACH(input_listener, listener) {
if (listener->dev == NULL || listener->dev == evt->dev) {
listener->callback(evt);
}
}
}

默认情况下输入子系统是开启了CONFIG_INPUT_MODE_THREAD=y,输入子系统会通过msgq发送到input_thread,在该thread内调用input_process,线程和msgq相关配置的默认值如下:

1
2
3
4
CONFIG_INPUT_MODE_THREAD=y
CONFIG_INPUT_THREAD_PRIORITY_OVERRIDE=0;
CONFIG_INPUT_QUEUE_MAX_MSGS=16
CONFIG_INPUT_THREAD_STACK_SIZE=256

当开启CONFIG_INPUT_MODE_SYNCHRONOUS=y时输入子系统直接在input_report内调用input_process

长按键

输入子系统提供了长按键支持,长按键建立一个长按键设备,该设备向指定的device注册监听器,监听器用于判断是否是长按键, 下面是一个长按键的设备树

1
2
3
4
5
6
7
8
longpress {
input = <&buttons>;
compatible = "zephyr,input-longpress";
input-codes = <INPUT_KEY_0>, <INPUT_KEY_1>;
short-codes = <INPUT_KEY_A>, <INPUT_KEY_B>;
long-codes = <INPUT_KEY_X>, <INPUT_KEY_Y>;
long-delay-ms = <1000>;
};

长按按键被转换为特定的KEY,监听buttons输入设备的指定按键是否发生长按键:

  • buttons设备发生了INPUT_KEY_0时且低于1s,longpress设备回报INPUT_KEY_A
  • buttons设备发生了INPUT_KEY_0时且大于1s,longpress设备回报INPUT_KEY_X
  • buttons设备发生了INPUT_KEY_1时且低于1s,longpress设备回报INPUT_KEY_B
  • buttons设备发生了INPUT_KEY_1时且大于1s,longpress设备回报INPUT_KEY_Y
    当按键被短按时,按键设备按下和释放事件发生,长按设备的短按按下和释放事件发生
    1
    2
    3
    4
    input event: dev=buttons          SYN type= 1 code= 11 value=1
    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

当按键被长按时,按键设备按下事件发生,长按设备的长按按下事件发生,按键设备事件事件发生,长按设备的长按释放事件发生

1
2
3
4
input event: dev=buttons          SYN type= 1 code= 11 value=1
input event: dev=longpress SYN type= 1 code= 45 value=1
input event: dev=buttons SYN type= 1 code= 11 value=0
input event: dev=longpress SYN type= 1 code= 45 value=0

上面的设备树指定长按键的输入设备是buttons,当不指定input时,长按键设备会监听将所有的输入设备。
默认情况下长按键支持是被打开的CONFIG_INPUT_LONGPRESS=y,如果不需要该功能可以配置关闭,节省空间.
长按键实现在zephyr/subsys/input/input_longpress.c内由宏INPUT_LONGPRESS_DEFINE遍历设备树longpress生成如下内容, 会将input event的映射建立为全局素组,然后对Input device注册longpress_cb,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void longpress_cb_0(struct input_event *evt)
{
longpress_cb(DEVICE_DT_INST_GET(inst), evt);
}
INPUT_LISTENER_CB_DEFINE(DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, input)),
longpress_cb_0);
static const uint16_t longpress_input_codes_0[] = {INPUT_KEY_0, INPUT_KEY_1};
static const uint16_t longpress_short_codes_0[] = {INPUT_KEY_A, INPUT_KEY_B};
static const uint16_t longpress_long_codes_0[] = {INPUT_KEY_X, INPUT_KEY_Y};
static const struct longpress_config longpress_config_0 = {
.input_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, input)),
.input_codes = longpress_input_codes_0,
.short_codes = longpress_short_codes_0,
.long_codes = longpress_long_codes_0,
.num_codes = 2,
.long_delays_ms = 100,
};
static struct longpress_data_entry longpress_data_entries_0[2];
static struct longpress_data longpress_data_0 = {
.entries = longpress_data_entries_0,
};
DEVICE_DT_INST_DEFINE(inst, longpress_init, NULL,
&longpress_data_0, &longpress_config_0,
APPLICATION, CONFIG_INPUT_INIT_PRIORITY, NULL);

系统初始化时会调用longpress_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int longpress_init(const struct device *dev)
{
const struct longpress_config *cfg = dev->config;
struct longpress_data *data = dev->data;

if (cfg->input_dev && !device_is_ready(cfg->input_dev)) {
LOG_ERR("input device not ready");
return -ENODEV;
}

//对entry进行初始化,建立long press判断的workq:longpress_deferred
for (int i = 0; i < cfg->num_codes; i++) {
struct longpress_data_entry *entry = &data->entries[i];

entry->dev = dev;
entry->index = i;
k_work_init_delayable(&entry->work, longpress_deferred);
}

return 0;
}

当buttons有按键按下时,longpress_cb_0->longpress_cb被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static void longpress_cb(const struct device *dev, struct input_event *evt)
{
const struct longpress_config *cfg = dev->config;
struct longpress_data *data = dev->data;
struct longpress_data_entry *entry;
int i;

//只有按键类型支持长按
if (evt->type != INPUT_EV_KEY) {
return;
}

//发生的按键匹配input-code
for (i = 0; i < cfg->num_codes; i++) {
if (evt->code == cfg->input_codes[i]) {
break;
}
}
if (i == cfg->num_codes) {
LOG_DBG("ignored code %d", evt->code);
return;
}

//找到匹配的入口
entry = &data->entries[i];

if (evt->value) {
//当按键按下时,按照long_delays_ms启动delay work
entry->long_fired = false;
k_work_schedule(&entry->work, K_MSEC(cfg->long_delays_ms));
} else {
//按键松开时,取消delay work
//如果按键在ong_delays_ms后松开就会触发调用到longpress_deferred,在其中将long_fired设置为true
k_work_cancel_delayable(&entry->work);
if (entry->long_fired) {
//有长按发生,发送长按释放事件
input_report_key(dev, cfg->long_codes[i], 0, true, K_FOREVER);
} else {
//没有长按发送,发送短按按下和释放事件
input_report_key(dev, cfg->short_codes[i], 1, true, K_FOREVER);
input_report_key(dev, cfg->short_codes[i], 0, true, K_FOREVER);
}
}
}

//按键按下后没有释放的情况下到了long_delays_ms后启动delay work,执行longpress_deferred
static void longpress_deferred(struct k_work *work)
{
struct longpress_data_entry *entry = CONTAINER_OF(
work, struct longpress_data_entry, work);
const struct device *dev = entry->dev;
const struct longpress_config *cfg = dev->config;
uint16_t code;

//查询到长按按键
code = cfg->long_codes[entry->index];

//发送长按按下事件
input_report_key(dev, code, 1, true, K_FOREVER);

//标记长按被触发
entry->long_fired = true;
}

参考

https://docs.zephyrproject.org/3.4.0/services/input/index.html