HID 设备方案

概述

USB 人机接口设备 (Human Interface Device, HID) 协议是 USB-IF 定义的一套用于连接人机交互设备(如键盘、鼠标、游戏控制器、触摸屏等)的标准接口规范。

Ameba 平台基于 USB-IF 官方标准,实现了功能完备的 USB HID 设备端协议栈。该方案能够为系统提供稳定、低延迟的人机交互接口,并支持高度灵活的自定义配置。

Ameba USB HID 设备

特性

  • 描述符全定制

  • 支持 USB 热插拔

  • 模拟支持基础的键盘按键与鼠标移动事件

  • 支持单/双向通信:

    • 鼠标:支持 HID 数据的单向上报 (Input Report)。

    • 键盘:支持 HID 数据的双向通信 (Input & Output Report),可处理主机下发的 LED 状态控制(如 “Caps Lock” 指示灯)。

  • 支持配置速度模式等参数

应用场景

作为 USB HID 设备,Ameba 可通过 USB 接口向 Host 主机(如 PC、平板电脑、智能电视)发送标准输入报告。结合其内置的 Wi-Fi 和 Bluetooth® 能力,Ameba 能够实现多种跨协议、跨介质的创新应用,例如:

  • 无线外设适配器 (Dongle):Ameba 作为 USB 接收端 (Dongle) 插入主机,通过 Wi-Fi 或蓝牙接收远端无线键盘/鼠标的数据,并将其转换为标准的 USB HID 报文透传给主机,实现无线外设的即插即用。

  • IoT 智能控制网关:Ameba 可作为物理输入与数字控制的桥梁。它读取本地物理按键、旋钮或传感器信号,通过 USB HID 协议将其映射为标准输入指令,或转换为网络控制指令,从而实现对智能家居及工业设备的精准控制。

  • 游戏控制器转接器:支持将非标准接口的游戏控制器(如老式游戏机手柄、自定义街机摇杆)的信号接入 Ameba,通过内部协议栈转换为标准 HID 游戏控制器报告,确保在 PC 或现代游戏主机上的兼容性。

协议简介

HID (Human Interface Device) 协议是 USB 规范框架下,专为实现人机交互设备而定义的标准接口与数据传输规范。该协议定义了主机(Host)与设备(Device)之间的数据交互格式及控制响应机制。

遵循 HID 标准的常见设备包括:

  • 输入设备:键盘、鼠标、游戏手柄、数字转换器(触摸屏/绘图板)。

  • 控制设备:消费类电子控制旋钮(如音量调节)、系统休眠按钮等。

协议文档

USB-IF 官方发布了 HID 核心协议规范及用于定义设备功能的用途表(Usage Tables)。开发者可参考以下核心文档:

规范类型

文档

HID 核心协议

Device Class Definition for HID

HID 用途表

HID Usage Tables

术语定义

本文档涉及的通用 HID 技术术语定义如下:

术语

描述

报告 (Report)

HID 设备与主机之间传输数据的基本单元。数据包的结构由报告描述符定义。

用途 (Usage)

描述数据具体含义的标识符。例如,定义某个数据位代表“左键点击”还是“X 轴坐标”。

集合 (Collection)

将相关的输入/输出数据项分组的逻辑结构。例如,将鼠标的 X、Y 轴和按键组合在一个“鼠标”集。

项目 (Item)

报告描述符的基本组成部分,分为主项目 (Main)、全局项目 (Global) 和局部项目 (Local)。

协议框架

HID 协议栈采用分层架构设计,旨在将底层的 USB 传输细节与上层的应用逻辑解耦。系统架构主要包含以下核心组件:USB 核心传输驱动、HID 类驱动以及特定类型的 HID 应用。

../../../_images/usb_hid_architecture.svg

组件职责

  • HID 应用程序 (HID Application)

    位于架构最上层,负责处理具体的业务逻辑。例如:采集传感器数据并打包成鼠标坐标报告,或接收键盘 LED 控制指令并驱动 GPIO 点亮指示灯。

  • HID 类驱动 (HID Class Driver)

    实现了 Device Class Definition for HID 1.11 规范。其主要职责包括:解析和组织 HID 协议特有的数据包,管理报告描述符(Report Descriptor),并处理类特定请求(Class-Specific Requests)。

  • USB 核心与传输驱动 (USB Core & Transfer Driver)

    负责处理标准的 USB 枚举流程、端点管理及底层的数据包调度,向上层屏蔽硬件控制器的差异。

通信机制

HID 设备通过特定的端点(Endpoint)配置来实现低延迟或低带宽要求的交互。从逻辑功能上,HID 接口主要依赖以下两种传输方式:

控制管道 (Control Pipe)

  • 映射端点: 默认控制端点 0 (Endpoint 0)。

  • 枚举与配置: 传输标准的 USB 描述符(设备、配置、接口描述符)以及 HID 特有的 HID 描述符和报告描述符。

  • 类特定请求: 处理 HID 协议控制命令,例如 Set Idle (设置空闲率)。

  • 低频数据传输: 当设备未配置中断输出端点时,主机通过控制传输(Control Transfer)发送输出报告(Output Report),例如设置键盘的大小写锁定灯状态。

中断管道 (Interrupt Pipe)

  • 功能描述: 利用 USB 的轮询(Polling)机制,保证数据传输的实时性。

  • 中断输入 (Interrupt IN): 必选。负责将设备产生的异步数据(如按键按下、鼠标位移、触摸屏坐标)实时传输给主机。

  • 中断输出 (Interrupt OUT): 可选。负责主机向设备发送低延迟的下行数据。常见于需要高实时性反馈的场景,如游戏手柄的力反馈震动指令。若未定义此端点,下行数据将回退至控制管道传输。

描述符结构

HID 设备除遵循标准的 USB 描述符外,引入了其独有的描述符体系。最关键的是 HID 描述符(HID Descriptor)和报告描述符 (Report Descriptor)。

HID 描述符拓扑 (Descriptor Topology)

Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        ├── Endpoint Descriptor(Interrupt In)
        └── Endpoint Descriptor(Interrupt Out)

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        ├── Endpoint Descriptor(Interrupt In)
        └── Endpoint Descriptor(Interrupt Out)

HID 描述符 (HID Descriptor)

  • 位于配置描述符集合中,用于告知主机该接口包含哪些下级描述符(通常是报告描述符)及其长度。

HID Descriptor
├── bLength            : 1 byte  → Total descriptor length
├── bDescriptorType    : 1 byte  → 0x21 (HID Descriptor)
├── bcdADC             : 2 bytes → HID Version
├── bCountryCode       : 1 byte  → Numeric expression identifying country code of the localized hardware
├── bNumDescriptors    : 1 byte  → Numeric expression specifying the number of class descriptors
│                        └─ (always at least one i.e. Report descriptor.)
├── bDescriptorType    : 1 byte  → Constant name identifying type of class descriptor.
└── wDescriptorLength  : 2 byte  → Numeric expression that is the total size of the Report descriptor.

报告描述符 (Report Descriptor)

  • 这是一个由特定项目(Item)组成的字符流,而非固定的结构体。

  • 它灵活地定义了设备能够产生或接收的数据格式、长度、用途及物理范围。主机必须解析该描述符才能理解设备发送的数据。

Report Descriptor
├── Usage Page (Generic Desktop)
├── Usage (Keyboard)
├── Collection (Application)
│       ├── Usage Page (Key Codes)
│       ├── Usage Minimum (224)
|       ├── Usage Maximum (231)
│       ├── Logical Minimum (0)
│       ├── Logical Maximum (1)
│       ├── Report Count (8)
│       ├── Report Size (1)
│       ├── Input (Data, Variable, Absolute)   ; Modifier keys (8 bits, 1 per modifier)
│       ├── Report Count (1)
│       ├── Report Size (8)
│       ├── Input (Constant, Variable, Absolute) ; Reserved byte
|       |
│       ├── Usage Page (LEDs)
│       ├── Logical Minimum (0)
│       ├── Logical Maximum (1)
│       ├── Report Count (5)
│       ├── Report Size (1)
│       ├── Usage Minimum (Num Lock)
│       ├── Usage Maximum (Kana)
│       ├── Output (Data, Variable, Absolute)  ; 5 LED control bits
│       ├── Report Count (1)
│       ├── Report Size (3)
│       ├── Output (Constant, Variable, Absolute) ; Padding 3 bits
│       │
│       └── ....... Can configure multiple different setting as needed
│
└── End Collection

备注

HID 报告描述符的编写非常灵活且复杂,建议使用 USB-IF 提供的 HID Descriptor Tool 进行生成和校验。

类特定请求

HID 设备的控制请求分为 标准请求(Standard Requests)类特定请求 (Class-Specific Requests)

本节主要介绍 HID 特有的 类特定请求,这些 HID 类特定请求主要用于管理报告状态和协议行为。不同类型的设备对类特定请求的支持要求如下表所示:

设备类型

Get Report

Set Report

Get Idle

Set Idle

Get Protocol

Set Protocol

Boot Mouse

必选

可选

可选

可选

必选

必选

Non-Boot Mouse

必选

可选

可选

可选

可选

可选

Boot Keyboard

必选

可选

必选

必选

必选

必选

Non-Boot Keyboard

必选

可选

必选

必选

可选

可选

Other Device

必选

可选

可选

可选

可选

可选

数据传输格式

与 UAC (USB Audio Class) 固定的 PCM 流不同,HID 的数据格式完全由 报告描述符 动态定义。

以下为标准键盘的典型数据结构示例:

键盘输入报告 (Keyboard Input Report)

用于向主机发送按键数据。以下为按下 "Left Shift" + "A" 键的数据示例:

Filed

Length(bits)

Offset(bits)

Value

Left Ctrl

1

0

0

Left Shift

1

1

1

Left Alt

1

2

0

Left GUI

1

3

0

Right Ctrl

1

4

0

Right Shift

1

5

0

Right Alt

1

6

0

Right GUI

1

7

0

Pading

8

8

0

Key

48

16

0x04

键盘输出报告 (Keyboard Output Report)

用于主机控制键盘 LED 状态。以下为点亮 "Caps Lock" 灯的数据示例:

Filed

Length(bits)

Offset(bits)

Value

Num Lock

1

0

0

Caps Lock

1

1

1

Scroll Lock

1

2

0

Compose

1

3

0

Kana

1

4

0

备注

报告 ID (Report ID)

如果一个 HID 接口支持多种不同格式的数据包,必须在每个数据包的起始位置添加一个字节的 Report ID 进行区分。如果设备仅定义了一种报告格式,则可以省略 Report ID,直接传输数据载荷。

类驱动

本节详细介绍了 HID 类驱动的内部实现细节,包括描述符的层级结构、类特定请求的支持情况以及端点配置方案。

驱动描述符结构

下文展示了 HID 类驱动中定义的描述符拓扑结构。这些结构体严格遵循 USB 2.0 协议规范与 HID 类定义。

Mouse:
Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
└── Interface Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        └── Endpoint Descriptor(Interrupt In)

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
└── Interface Descriptor (Interface 0)
        ├── Standard Interface Descriptor (Interface 0, Human Interface Device)
        ├── HID Descriptor(HID Version, bNumDescriptors, etc)
        │       └── Report Descriptor()
        └── Endpoint Descriptor(Interrupt In)

类特定请求实现

本驱动栈遵循 USB HID 1.11 规范,已实现了核心 类特定请求 (Class-Specific Requests) 的基础框架。

驱动层已完成底层消息通信、协议解析及数据传输流程。开发者无需关注 USB 协议细节,仅需参考现有源码架构,在预留的接口中实现具体的业务逻辑。源码路径: {SDK}/component/usb/device/hid

类特定请求类型

备注

Get Report

驱动已完成请求响应流程。注意:默认实现返回 0 数据,需在应用层填充具体的 Report 内容。

Set Report

驱动已接收数据包,开发者需实现对主机下发数据(Output/Feature Report)的具体处理逻辑。

Get Idle

标准实现。

Set Idle

标准实现。

Get Protocol

用于查询当前协议模式(Boot/Report)。

Set Protocol

驱动已解析请求,开发者需根据主机下发的协议模式(Boot 或 Report)执行相应的状态切换处理。

端点配置

Mouse:

端点

数量

描述

控制 IN/OUT 端点

1

EP0,用于处理 USB 主机发送的标准枚举请求、描述符获取及 HID 类特定控制请求。

中断 IN 端点

1

用于设备向主机实时上报数据(如鼠标位移、按键键值等)。

API 说明

驱动 API

应用示例

应用设计

本节详细介绍 HID 应用的完整开发流程,涵盖加载驱动、热插拔管理、数据收发机制以及资源释放。

加载驱动

使用 HID 驱动前,需定义配置结构体并注册回调函数,随后调用核心接口加载 USB 设备控制器及 HID 类驱动。步骤说明:

  • 硬件配置:配置 USB 速度模式及中断优先级。

  • 回调注册:定义用户回调结构体 usbd_hid_usr_cb_t,挂载各个阶段的处理函数。

  • 加载核心驱动:调用 usbd_init() 加载 USB 核心驱动。

  • 加载类驱动:调用 usbd_hid_init() 加载 HID 类驱动。

/*
 * 1. Configure USB speed (High Speed or Full Speed) and interrupt priority.
 */
static usbd_config_t hid_cfg = {
    .speed = CONFIG_USBD_HID_SPEED,
    .isr_priority = INT_PRI_MIDDLE,
};

/*
 * 2. Define user callbacks for HID events.
 */
static usbd_hid_usr_cb_t hid_usr_cb = {
    .init = hid_cb_init,                     /* USB init callback */
    .deinit = hid_cb_deinit,                 /* USB deinit callback */
    .setup = hid_cb_setup,                   /* USB setup callback */
    .transmitted = hid_cb_transmitted,       /* Data transmission complet callback */
    .received = hid_cb_received,             /* Data reception callback */
    .status_changed = hid_cb_status_changed, /* Connection status change callbac */
};

int ret = 0;

/**
 * Initialize USB device core driver with configuration.
 */
ret = usbd_init(&hid_cfg);
if (ret != HAL_OK) {
    return;
}

/*
 * 4. Initialize HID class driver. 512 is the transfer buffer size.
 */
ret = usbd_hid_init(512, &hid_usr_cb);
if (ret != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbd_deinit();

    return;
}

热插拔事件处理

为了确保系统的健壮性,建议通过注册 status_changed 回调函数来监听 USB 的连接与断开事件。

备注

不要在中断回调(ISR)中直接执行耗时的资源释放或重新加载操作。建议使用信号量(Semaphore)通知专用的任务线程进行处理。

参考 设备连接状态检测 获取更多细节,下面是示例代码:

/* USB status change callback */
static usbd_hid_usr_cb_t hid_usr_cb = {
    .status_changed = hid_cb_status_changed,
};

/* Callback executed in ISR context */
static void hid_cb_status_changed(u8 old_status, u8 status)
{
    hid_attach_status = status;
    rtos_sema_give(hid_attach_status_changed_sema);
}

/* Thread Context: Handle the state machine */
static void hid_hotplug_thread(void *param)
{
    int ret = 0;
    UNUSED(param);

    for (;;) {
        /* Wait for status change signal */
        if (rtos_sema_take(hid_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            if (hid_attach_status == USBD_ATTACH_STATUS_DETACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");

                /* 1. Clean up HID class resources */
                usbd_hid_deinit();

                /* 2. De-initialize USB core */
                ret = usbd_deinit();
                if (ret != 0) {
                    break;
                }

                /* 3. Re-initialize for next connection */
                ret = usbd_init(&hid_cfg);
                if (ret != 0) {
                    break;
                }
                ret = usbd_hid_init(512, &hid_usr_cb);
                if (ret != 0) {
                    usbd_deinit();
                    break;
                }
            } else if (hid_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
                RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
            } else {
                RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
            }
        }
    }

    rtos_task_delete(NULL);
}

HID 报告发送流程 (Input Report)

当设备作为数据生产者(如鼠标移动、键盘按键)时,需主动向主机发送输入报告。发送流程:

  • 等待设备就绪:监听 hid_cb_setup() 回调触发的信号量,确保主机已完成枚举。

  • 构造报告数据:根据 HID 报告描述符定义的格式填充数据缓冲区。

  • 发送数据:使用 usbd_hid_send_data() 发送数据。

/* Mouse data structure (Defined by Report Descriptor) */
typedef struct {
    u8 left;     //left button. 0: release, 1: press
    u8 right;    //right button. 0: release, 1: press
    u8 middle;   //wheel button. 0: release, 1: press
    char x_axis; //x-axis pixels. relative value from -127 to 127, positive for right and negative for left
    char y_axis; //y-axis pixels. relative value from -127 to 127, positive for up and negative for down
    char wheel;  //scrolling units. relative value from -127 to 127, positive for up and negative for down.
} usbd_hid_mouse_data_t;

/* Mouse moving data */
static usbd_hid_mouse_data_t mdata[] = {
    {0,   0,   0,  50,   0,   0},    //move the cursor 50 pixels to the right
    {0,   0,   0,   0,  50,   0},    //move the cursor down 50 pixels
    {0,   0,   0, -50,   0,   0},    //move the cursor 50 pixels to the left
    {0,   0,   0,   0, -50,   0},    //move the cursor up 50 pixels
    {0,   0,   0,   0,   0,   5},    //scroll up for 5 units
    {0,   0,   0,   0,   0,  -5},    //scroll down for 5 units
    {0,   0,   1,   0,   0,   0},    //middle button pressed
    {0,   0,   0,   0,   0,   0},    //middle button released
    {0,   1,   0,   0,   0,   0},    //right button pressed
    {0,   0,   0,   0,   0,   0},    //right button released
    {0,   0,   0,  -5,   0,   0},    //move the cursor 5 pixels to the left
    {1,   0,   0,   0,   0,   0},    //left button pressed
    {0,   0,   0,   0,   0,   0},    //left button released
};

/* Callback when device is configured by Host */
static void hid_cb_setup(void)
{
    rtos_sema_give(hid_connect_sema);
}

/* Send function */
static void hid_send_device_data(void *pdata)
{
    u8 byte[4];
    usbd_hid_mouse_data_t *data = (usbd_hid_mouse_data_t *)pdata;

    memset(byte, 0, 4);

    if (data->left != 0) {
        byte[0] |= USBD_HID_MOUSE_BUTTON_LEFT;
    }
    if (data->right != 0) {
        byte[0] |= USBD_HID_MOUSE_BUTTON_RIGHT;
    }
    if (data->middle != 0) {
        byte[0] |= USBD_HID_MOUSE_BUTTON_MIDDLE;
    }

    byte[0] |= USBD_HID_MOUSE_BUTTON_RESERVED;
    byte[1] = data->x_axis;
    byte[2] = data->y_axis;
    byte[3] = data->wheel;

    usbd_hid_send_data(byte, 4);
}

static void hid_send_data_func(void)
{
    /* Wait for USB configured */
    rtos_sema_take(hid_connect_sema, RTOS_SEMA_MAX_COUNT);

    u32 array_len = sizeof(mdata) / sizeof(usbd_hid_mouse_data_t);

    do {
        for (i = 0; i < array_len; i++) {
            /* Wait for previous transfer to complete */
            rtos_sema_take(hid_transmit_sema, RTOS_SEMA_MAX_COUNT);

            /* Send the mouse data out */
            hid_send_device_data(&mdata[i]);

            /* Force 1000ms of sleep to slow down movement */
            rtos_time_delay_ms(1000);
        }

        rtos_time_delay_ms(5 * 1000); //next loop
    } while (++loop < CONFIG_USBD_HID_CONSTANT_LOOP);
}

HID 报告接收流程 (Output Report)

当主机向设备发送控制指令(如点亮键盘 CapsLock 灯、手柄震动)时,设备端需通过回调函数处理输出报告。处理流程:

  • 注册回调:在加载驱动时注册 hid_cb_received()

  • 解析数据:在回调函数中读取数据长度及内容,并解析对应的 HID 报告 ID(如果存在)。

  • 执行动作:解析协议载荷并控制相应硬件。

/* Define callbacks for HID receive. */
static usbd_hid_usr_cb_t hid_usr_cb = {
    .received = hid_cb_received,
};

/* Callback for received data (executed in ISR context) */
static void hid_cb_received(u8 *buf, u32 len)
{
    /* Example: Parse Keyboard LED status */
    if (len == 1) {
        u8 led_status = buf[0];
        /* Control GPIO based on led_status bits (NumLock, CapsLock, etc.) */
    }
}

备注

如需测试输出报告功能,请确保在 {SDK}/component/usb/device/hid/usbd_hid.h 中将 USBD_HID_DEVICE_TYPE 配置为支持双向通信的设备类型(如 USBD_HID_KEYBOARD_DEVICE)。

卸载驱动

在系统关机或不再需要 HID 功能时,应按加载的反序释放资源,以防止内存泄漏。

/* 1. Deinitialize HID class driver first */
usbd_hid_deinit();

/* 2. Deinitialize USB device core driver */
usbd_deinit();

运行方式

本节以一个 USB 鼠标模拟器 为例,演示如何将 Ameba 开发板配置为 USB HID 设备。

功能描述

  • 设备类型:标准 USB HID 鼠标。

  • 行为:连接 PC 后,开发板会自动模拟鼠标移动轨迹。

  • 源码路径: {SDK}/example/usb/usbd_hid,可为开发者设计自定义 USB 键鼠、游戏手柄等产品提供完整的参考方案。

配置与编译

  • 编译与烧录

    在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程,然后将生成的 Image 文件烧录至开发板:

    # Initialize environment (required for every new terminal)
    source env.sh or env.bat(Windows system)
    
    # Select Target SoC (replace xxx with your specific SoCs)
    ameba.py soc xxx
    
    ameba.py build -a usbd_hid -p
    
  • Menuconfig 配置认

    若编译失败,请执行 ameba.py menuconfig,确认已选择 USBD HID

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Device)  --->
        [*] HID
    

结果验证

  • 启动设备

    重启开发板,观察串口日志,应显示如下启动信息:

[HID-I] USBD HID demo start
  • 连接主机

    使用 USB 线缆将开发板连接至 PC(如 Windows 电脑)

  • 功能测试

Mouse:

默认示例代码将开发板配置为 USB 鼠标。

  • 观察现象:连接成功后,PC 屏幕上的鼠标光标将自动开始移动(移动轨迹由 Example 代码中的数组定义)。

  • 数据流向:此过程验证了 设备 -> 主机 (Device to Host) 的输入报告(Input Report)传输功能。

备注

若光标未移动,请检查:

  • 硬件连接:确认 USB 线缆已插好,且线缆支持数据传输(非仅充电线)。

  • 设备冲突:检查是否有其他物理鼠标或虚拟鼠标驱动干扰了光标移动。

  • 枚举状态:检查 PC 设备管理器中是否有带黄色感叹号的 USB 设备。

备注

目前仅支持鼠标和键盘两种 HID 设备,若需支持其他设备,需在代码中替换相应的报告描述符(Report Descriptor)。