Zephyr输入子系统-1接口和使用

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

本文说明Zephyr输入子系统的接口和使用。

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
2
input_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
      21
      INPUT_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
      6
      INPUT_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
      6
      INPUT_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
    6
    static 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
    6
    static 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
    6
    static 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
    15
    struct 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
2
INPUT_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_A

1
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