复合功能主机方案

概述

USB 复合设备 (Composite Device) 架构允许单一物理 USB 设备通过配置多个接口描述符 (Interface Descriptors) 或接口关联描述符 (IAD),在逻辑上呈现多种独立的功能。

Ameba 平台的 USB 主机协议栈已全面支持复合设备的枚举与驱动加载。系统能够智能解析设备的配置结构,并为不同的接口加载对应的类驱动程序。这使得 Ameba 能够通过一个 USB 端口,同时并发处理多种不同类型的业务流。

../../../_images/usb_host_composite_overview.svg

特性

  • 支持以下功能组合:

    • CDC ACM + CDC ECM

    • HID + UAC

  • 支持热插拔

  • 自动解析描述符,自适应速度模式

应用场景

作为 USB 主机,Ameba 可通过复合类驱动同时支持多种接口类的组合,能够与市面上复杂的 USB 复合设备进行交互,适用于多种物联网与多媒体应用场景,例如:

  • 蜂窝网络接入 (CDC ECM + CDC ACM):这是 4G/LTE Cat.1/Cat.4 模组及 USB Dongle 最常见的复合模式。Ameba 利用 CDC ECM (以太网) 接口进行高速互联网接入,广泛应用于工业 IoT 网关的数据回传或车载终端的联网功能;同时 CDC ACM (虚拟串口) 接口并行用于传输 AT 指令,实现对模组信号状态的实时监控和远程控制。

  • 交互式音频设备 (UAC + HID):这是带有物理控制按键的 USB 耳机、智能音箱或游戏手柄的典型应用。Ameba 利用 UAC (音频设备类) 接口处理高品质的语音通话、背景音乐或游戏音效流,同时通过 HID (人机接口设备) 接口实时响应设备上的物理按键(如音量调节、静音、媒体播放控制或手柄输入),实现音频体验与用户交互的无缝融合。

协议简介

USB 复合设备 (USB Composite Device) 规范定义了在 USB 规范框架下,通过单一物理接口同时承载多个独立功能接口的设备架构。 该机制允许主机将单一物理设备识别并枚举为多个逻辑功能的集合,实现不同功能类(如 HID、MSC、CDC 等)的并行处理与独立控制。 遵循该架构的常见设备包括无线键鼠接收器、带声卡的 USB 耳机及集成了存储与调试功能的工业设备等。

复合驱动仅负责顶层管理,具体子功能(如 HID 的报文格式、CDC 的通信协议)的数据处理需遵守其 对应的类协议规范,请参阅相关章节。

描述符结构

复合设备在遵循标准 USB 描述符(设备描述符、配置描述符)的基础上,通过在配置描述符集合中聚合多个接口描述符来实现多功能的定义。

单接口功能类

如果一个接口就代表了一个独立的功能,复合设备在聚合这种功能类的时候直接拼接使用。典型的单接口功能类有 HID 和 MSC。

HID 键盘 + HID 鼠标 复合设备为例,其描述符拓扑结构如下所示:

Device Descriptor
|  bDeviceClass: 0xEF (Miscellaneous)
|  bDeviceSubClass: 0x02 (Common Class)
|  bDeviceProtocol: 0x01 (Interface Association Descriptor)
│
└── Configuration Descriptor
   |   bNumInterfaces: 2  (2 Interfaces)
   │   ...
   ├── Interface Descriptor 0 (HID Keyboard)
   │   bInterfaceNumber: 0
   │   bInterfaceClass: 0x03 (HID)
   │   bInterfaceSubClass: 0x01 (Boot Interface)
   │   bInterfaceProtocol: 0x01 (Keyboard)
   │
   └── Interface Descriptor 1 (HID Mouse)
       bInterfaceNumber: 1
       bInterfaceClass: 0x03 (HID)
       bInterfaceSubClass: 0x01 (Boot Interface)
       bInterfaceProtocol: 0x02 (Mouse)

HID 键盘 + HID 鼠标 复合设备和单独的键盘/鼠标设备对比,描述符特征如下:

描述符层级

普通设备(Single Function)

复合设备(Composite Device)

设备描述符

bDeviceClass 定义具体类型

bDeviceClass 通常为 0xEF (Misc) 或 0x00 (由接口定义)

配置描述符

1 个配置

1 个配置

接口描述符

1 个接口

有 2 个接口

端点描述符

归属于该接口

每个接口拥有独立的端点

备注

bDeviceClass = 0xEF:这是复合设备的标准标志,会触发主机驱动解析 IAD (可选)和配置描述符中的多个接口描述符,拆分出不同的设备功能,并创建这些逻辑子设备。 然后分别为 Interface 0 加载键盘驱动,为 Interface 1 加载鼠标驱动。

多接口功能类

如果一个逻辑功能需要占用多个接口才能完成,在使用这种功能类的时候必须加入 接口关联描述符 (Interface Association Descriptor, IAD) 将这些接口关联起来。

典型的多接口功能类

  • CDC (通信设备类): 通常需要 1 个控制接口 (CCI) + 1 个数据接口 (DCI)。

  • UVC (USB 视频类): 需要 1 个视频控制接口 (VC) + 1 个或多个视频流接口 (VS)。

  • UAC (USB 音频类): 需要 1 个音频控制接口 (AC) + 1 个或多个音频流接口 (AS)。

接口关联描述符(IAD)

IAD 向主机声明紧随其后的一组连续接口属于同一个功能,应由同一个驱动程序加载和管理。如果不加 IAD,主机有时候会把它们识别为两个独立的设备,或者驱动加载失败。

Interface Association Descriptor (IAD)
├── bLength                 : 1 byte  → 0x08 (Fixed Length)
├── bDescriptorType         : 1 byte  → 0x0B (IAD type code)
├── bFirstInterface         : 1 byte  → First Interface Number
├── bInterfaceCount         : 1 bytes → Count of associated interfaces
├── bFunctionClass          : 1 byte  → Function Class Code
├── bFunctionSubClass       : 1 byte  → Function SubClass Code
├── bFunctionProtocol       : 1 byte  → Function Protocol Code
└── iFunction               : 1 byte  → Function String Descriptor Index

IAD 使用示例

../../../_images/usb_iad_descriptor.svg
  • 功能 1:设备类占用两个接口,为了让主机将其识别为单一的逻辑功能,必须使用 IAD 将这两个接口进行关联。

  • 功能 2:设备类使用一个独立的接口,不需要 IAD 关联。

类特定请求

USB 协议规范中并没有定义专属的“复合类请求”,其核心逻辑在于通过 USB 标准请求中的寻址机制,将 类特定请求 (Class-Specific Requests) 精确指向指定的接口。

在 SETUP 包的 bmRequestType 字段中,低 5 位(Bits 0..4)表示 Recipient(接收者)。

  • 普通单功能设备:控制请求通常是发给“整个设备”的,即将 Recipient 设置为 Device (00000)

  • 复合设备:大量的控制请求必须精确地发给“特定的接口”,即将 Recipient 设置为 Interface (00001)

以下是复合设备在控制请求处理上最显著的几个关键点:

字段

含义

复合设备中的体现

bmRequestType

0x21 / 0xA1

Class Request, Recipient=Interface

最常见。例如 CDC 设置波特率、HID 点亮键盘灯

wIndex

Interface Number

接口编号

当 Recipient 是接口时,wIndex 必须填入具体的接口号(如 0, 1, 2), 驱动必须根据这个值把请求分发给对应接口功能驱动处理

类驱动

复合设备(Composite Device)是指在一个物理 USB 设备中包含多个独立功能(Function)的设备。 复合主机驱动的主要职责是正确解析设备的配置描述符,并为每个功能加载相应的子驱动程序,将单一物理设备枚举为多个逻辑功能的集合。 各功能独立互不干扰(例如:在传输网络数据的同时发送 AT 命令,或在播放音乐的同时接收按键输入)。

本节详细分析如何设计并实现一个 USB 复合主机类驱动。复合类驱动充当“父类”角色,本身负责管理多个“子类”(如 CDC ECM、HID、UAC 等)的资源调度、请求分发和数据处理。

../../../_images/usb_host_composite_driver_arch_zh.png

复合类驱动在系统架构中起着承上启下的作用,其核心交互逻辑主要围绕以下三个接口展开:

  • 主机类驱动回调 API:复合类驱动通过注册标准的 usbh_class_driver_t 结构体,实现与底层 USB Core 进行交互。

  • 面向应用的回调 API:通过应用层在初始化时注册的 usbh_composite_cb_tusbh_user_cb_t 和子功能驱动的回调函数 usbh_composite_function_class_xx_usr_cb_t ,向上层应用提供异步事件通知(如连接、断开、数据接收)。

  • 面向应用的 API:提供给应用层调用的功能接口。例如驱动加载/卸载,进行数据传输的调度等。

应用层回调 API

驱动为应用层提供了回调函数接口定义,以便应用代码能响应 USB 事件并处理业务逻辑,由应用层实现。

  • Host 应用层回调 API

    usbh_user_cb_t 是针对整个主机的回调。

  • Composite 应用层回调 API

    usbh_composite_cb_t 是针对顶层复合驱动的回调。

  • 子功能类应用层回调 API

    这是每个子功能自定义的回调,一般可选择实现:

    typedef struct {
        int(* init)(void);
        int(* deinit)(void);
        int(* attach)(void);
        int(* detach)(void);
        int(* setup)(void);
        int(* received)(u8 *buf, u32 len);
        void(* transmitted)(u8 status);
    } usbh_composite_function_class_xx_usr_cb_t;
    

    API

    描述

    init

    在类驱动初始化时被调用,用于初始化应用相关的资源

    deinit

    在类驱动注销时被调用,用于注销应用相关的资源

    attach

    在类驱动执行attach回调时被调用,用于应用层处理设备连接事件

    detach

    在类驱动执行detach回调时被调用,用于应用层处理设备断开事件

    setup

    在类驱动执行setup回调时被调用,用于指示应用层类驱动已准备好进行数据传输

    receive

    在类驱动收到IN数据时被调用,用于应用层处理设备上报的数据

    transmit

    在类驱动OUT数据传输完成时被调用,用于应用层获取OUT传输状态

下面以 HID + UAC 为例介绍自定义的子功能类应用层回调:

 /* HID user Callback Interface. */
 typedef struct {
     int(* init)(void);                               /**< Callback when HID driver is initialized */
     int(* deinit)(void);                             /**< Callback when HID driver is de-initialized */
     int(* attach)(void);                             /**< Callback when a HID device is attached */
     int(* detach)(void);                             /**< Callback when a HID device is detached */
     int(* setup)(void);                              /**< Callback during the Setup stage of a control transfer */
     int(* report)(usbh_composite_hid_event_t *event);/**< Callback when a HID event report is received */
 } usbh_composite_hid_usr_cb_t;

/* UAC User Callback Interface. */
 typedef struct {
     int(* init)(void);                                /**< Callback when UAC driver is initialized */
     int(* deinit)(void);                              /**< Callback when UAC driver is de-initialized */
     int(* attach)(void);                              /**< Callback when a UAC device is attached */
     int(* detach)(void);                              /**< Callback when a UAC device is detached */
     int(* setup)(void);                               /**< Callback during the Setup stage of a control transfer */
     int(* isoc_transmitted)(usbh_urb_state_t state);  /**< Callback when isochronous OUT (Play) transfer completes */
     int(* isoc_received)(u8 *buf, u32 len);           /**< Callback when isochronous IN (Record) data is received */
 } usbh_composite_uac_usr_cb_t;

类驱动实现

主要是定义复合类主机和类驱动回调函数实现。

  • 子功能类驱动

    每个子功能(如 CDC ACM, UAC, HID)都是一个独立的类驱动,详细请参考子功能方案的对应章节。

    • 独立的驱动结构: 每个子驱动定义一个标准的 usbh_class_driver_t 结构体,实现自己的业务逻辑。

    • 独立的资源管理: 每个子驱动负责解析对应接口,管理自己的通道、数据缓冲区以及数据收发处理等。

  • 复合类驱动

    • 复合类驱动需要定义一个标准的 usbh_class_driver_t 结构体,作为统一的入口注册到 USB Core 中,复合主机驱动会 遍历 每个子功能类驱动对应的回调函数。

    • 复合类驱动定义一个标准的 usbh_composite_host_t 结构体,这是复合主机实例的核心,用于管理所有子功能类驱动。

      /* Composite Device */
      static usbh_composite_host_t usbh_composite_host;
      
      /* Composite Class Driver */
      static const usbh_class_driver_t usbh_composite_driver = {
          .id_table = composite_devs,                 /* 支持的设备 ID 列表,核心层使用此表与插入的设备进行匹配,以决定是否加载此驱动 */
          .attach = usbh_composite_cb_attach,         /* 设备连接并匹配成功后调用 */
          .detach = usbh_composite_cb_detach,         /* 设备断开时调用 */
          .setup = usbh_composite_cb_setup,           /* 枚举完成进入类请求阶段,用于发送类特定的标准控制请求,完成设备进入数据传输状态前的必要配置 */
          .process = usbh_composite_cb_process,       /* 类驱动驱动就绪后的核心状态机处理函数 */
          .sof = usbh_composite_cb_sof,               /* SOF 中断时被调用,用于时序要求严格的处理逻辑 */
          .completed = usbh_composite_cb_completed,   /* 当通道上的传输完成时调用 */
      };
      

下面以 HID + UAC 为例介绍主机类驱动实例实现:

/* Composite Host structure. */
 typedef struct {
     usbh_class_driver_t *hid;
     usbh_class_driver_t *uac;
     usbh_composite_cb_t *cb;
     usb_host_t *host;
 } usbh_composite_host_t;

 /* Composite Host */
 static usbh_composite_host_t usbh_composite_host;

 /* Composite Device ID */
 static const usbh_dev_id_t composite_devs[] = {
     {
         .mMatchFlags = USBH_DEV_ID_MATCH_ITF_INFO,
         .bInterfaceClass = USB_UAC1_CLASS_CODE,
         .bInterfaceSubClass = USB_UAC1_SUBCLASS_AUDIOSTREAMING,
         .bInterfaceProtocol = 0x00,
     },
     {
     },
 };

 /* Composite Class Driver */
 static usbh_class_driver_t usbh_composite_driver = {
     .id_table = composite_devs,
     .attach = usbh_composite_hid_uac_cb_attach,
     .detach = usbh_composite_hid_uac_cb_detach,
     .setup = usbh_composite_hid_uac_cb_setup,
     .process = usbh_composite_hid_uac_cb_process,
     .sof = usbh_composite_hid_uac_cb_sof,
     .completed = usbh_composite_hid_uac_cb_completed,
 };

/********************** Function 1: HID Class *********************/
/* HID host structure. */
typedef struct {
   usbh_composite_host_t *driver;        /**< Pointer to the parent composite host structure. */
   usbh_composite_hid_usr_cb_t *cb;      /**< Pointer to the user-registered callback structure. */
   usbh_pipe_t pipe;                     /**< Interrupt IN pipe handler. */
 //....
} usbh_composite_hid_t;
/* HID Host */
static usbh_composite_hid_t usbh_composite_hid;

/* HID Class Driver */
 const usbh_class_driver_t usbh_composite_hid_driver = {
     .attach = usbh_composite_hid_cb_attach,
     .detach = usbh_composite_hid_cb_detach,
     .setup = usbh_composite_hid_cb_setup,
     .process = usbh_composite_hid_cb_process,
     .sof = usbh_composite_hid_cb_sof,
 };

/********************** Function 2: UAC Class *********************/
/* UAC host structure. */
 typedef struct {
     usbh_uac_ac_itf_info_t ac_isoc_desc;  /**< Audio Control interface topology (Feature Units + Terminals) */
     usbh_uac_channel_t isoc_out;          /**< OUT channel state (Playback/Speaker) */
     usbh_uac_channel_t isoc_in;           /**< IN channel state (Record/Microphone) */
     usbh_composite_uac_usr_cb_t *cb;      /**< User callback structure registered via usbh_composite_uac_init */
     usbh_composite_host_t *driver;        /**< Pointer to the parent USB Host Composite handle */
     //...
 } usbh_composite_uac_t;
/* UAC Host */
static usbh_composite_uac_t usbh_composite_uac;

/* UAC Class Driver */
 const usbh_class_driver_t usbh_composite_uac_driver = {
     .attach = usbh_composite_uac_cb_attach,
     .detach = usbh_composite_uac_cb_detach,
     .setup = usbh_composite_uac_cb_setup,
     .process = usbh_composite_uac_cb_process,
     .sof = usbh_composite_uac_cb_sof,
     .completed = usbh_composite_uac_cb_completed,
 };

加载与卸载类驱动

应用层通过下面两个主要函数来控制整个 Composite 类驱动的生命周期:

HID + UAC 示例

/**
* @brief  Init composite class
* @param  cb: User callback
* @retval Status
*/
int usbh_composite_init(usbh_composite_hid_usr_cb_t *hid_cb, usbh_composite_uac_usr_cb_t *uac_cb)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((hid_cb == NULL) || (uac_cb == NULL)) {
        ret = HAL_ERR_PARA;
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user CB\n");
        return ret;
    }

    ret = usbh_composite_hid_init(chost, hid_cb);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init HID itf fail: %d\n", ret);
        return ret;
    }
    chost->hid = (usbh_class_driver_t *)&usbh_composite_hid_driver;

    ret = usbh_composite_uac_init(chost, uac_cb);
    if (ret != HAL_OK) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "Init UAC itf fail: %d\n", ret);
        usbh_composite_hid_deinit();
        return ret;
    }
    chost->uac = (usbh_class_driver_t *)&usbh_composite_uac_driver;
    usbh_register_class(&usbh_composite_driver);

    return HAL_OK;
}

/**
* @brief  Deinit composite class
* @retval Status
*/
int usbh_composite_deinit(void)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    usbh_unregister_class(&usbh_composite_driver);

    usbh_composite_deinit_uac_class();
    usbh_composite_deinit_hid_class();

    chost->cb = NULL;

    return HAL_OK;
}

连接与断连处理

设备的连接与断开由 USB Core 自动检测,并调用类驱动的对应回调进行资源管理。

  • 设备连接

    当 Core 层枚举到匹配 usbh_dev_id_t 的 Composite 设备时, attach 回调被调用,内部遍历每个子功能的 attach 回调函数。

    每个子功能驱动的主要职责是:

    • 匹配子功能的 usbh_dev_id_t, 获得并解析对应的接口描述符。

    • 根据端点描述符,通过 usbh_open_pipe() 打开对应通道。

    • 初始化每个通道对应的传输。

    • 调用子功能驱动的用户 attach 回调,告知应用层。

  • 设备断开

    当设备拔出时, detach 回调被调用,内部遍历每个子功能的 attach 回调函数。

    每个子功能驱动的主要职责是:

    • 重置子功能驱动状态机的状态为 IDLE。

    • 调用 usbh_close_pipe() 关闭所有已打开的通道。

    • 调用子功能驱动的用户 detach 回调,告知应用层。

HID + UAC 示例

/**
* @brief  Attach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_attach(usb_host_t *host)
{
    int ret;
    usbh_composite_host_t *chost = &usbh_composite_host;

    chost->host = host;

    if ((chost->hid != NULL) && (chost->hid->attach != NULL)) {
        ret = chost->hid->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_hid_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support hid\n");
        }
    }

    if ((chost->uac != NULL) && (chost->uac->attach)) {
        ret = chost->uac->attach(host);
        if (ret != HAL_OK) {
            usbh_composite_deinit_uac_class();
            RTK_LOGS(TAG, RTK_LOG_WARN, "Can not support uac\n");
        }
    }

    return HAL_OK;
}

/**
* @brief  Detach callback.
* @param  host: Host handle
* @retval Status
*/
static int usbh_composite_hid_uac_cb_detach(usb_host_t *host)
{
    usbh_composite_host_t *chost = &usbh_composite_host;

    if ((chost->hid != NULL) && (chost->hid->detach != NULL)) {
        chost->hid->detach(host);
    }

    if ((chost->uac != NULL) && (chost->uac->detach)) {
        chost->uac->detach(host);
    }

    return HAL_OK;
}

类驱动状态机

与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。 USB 主机类驱动采用状态机来管理数据传输处理,驱动由 API 调用和底层中断反馈更新状态。 process 回调函数是主机端复合类驱动的核心状态机处理函数,内部遍历每个子功能驱动的 process 回调函数,每个子功能独立维护自己的状态机。

HID + UAC 示例

/**
* @brief  State machine handling callback
* @param  host: Host handle
* @param  msg: Event message
* @retval Status
*/
static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
{
    usbh_composite_host_t *chost = &usbh_composite_host;
    int ret = HAL_BUSY;

    /* If the pocess has handle the msg, it return HAL_OK, else return HAL_BUSY */
    if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
        ret = chost->hid->process(host, msg);
    }

    if ((ret != HAL_OK) && (chost->uac != NULL) && (chost->uac->process != NULL)) {
        ret = chost->uac->process(host, msg);
    }

    return ret;
}

数据传输处理

数据传输由各子功能类驱动负责调度处理,详细的数据收发过程请参考子功能方案的对应章节。

HID + UAC 示例

  • 音频播放:请参考音频主机方案的类驱动章节。

  • 按钮事件上报:

     /*************** HID + UAC Composite Class Driver *********************/
     /* USB Standard Host driver */
     static usbh_class_driver_t usbh_composite_driver = {
         .process = usbh_composite_hid_uac_cb_process,
     };
    
     static int usbh_composite_hid_uac_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_host_t *chost = &usbh_composite_host;
         int ret = HAL_BUSY;
    
          //1. HID process
         if ((chost->hid != NULL) && (chost->hid->process != NULL)) {
             ret = chost->hid->process(host, msg);
         }
    
         //2. UAC process
         return ret;
     }
    
     /*************** HID Sub-funtion Class Driver *************************/
     /* HID user Callback Interface. */
     typedef struct {
         int(* report)(usbh_composite_hid_event_t *event);
     } usbh_composite_hid_usr_cb_t;
    
     /* USB Standard Class Driver */
     const usbh_class_driver_t usbh_composite_hid_driver = {
         .process = usbh_composite_hid_cb_process,
         .sof = usbh_composite_hid_cb_sof,
     };
    
     static int usbh_composite_hid_cb_sof(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
    
         //Regular reporting
         if (usbh_get_elapsed_ticks(host, pipe->tick) > UBSH_COMPOSITE_HID_TRIGGER_MAX_CNT) {
             usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
         }
    
         return HAL_OK;
     }
     /* Phase 1: Callback Process */
     static int usbh_composite_hid_cb_process(usb_host_t *host, u32 msg)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_event_t event;
         event.d32 = msg;
    
         // 1. Check if the event belongs to the HID pipe
         if ((hid->hid_ctrl_buf) && (event.msg.pipe_num == pipe->pipe_num)) {
             // 2. Clear transfer flag
             hid->next_xfer = 0;
             // 3. Handle Interrupt IN transfer state machine
             usbh_composite_hid_in_process(host);
             // 4. If transfer started/updated, notify the main host driver
             if (hid->next_xfer) {
                 usbh_notify_composite_class_state_change(host, pipe->pipe_num, USBH_COMPOSITE_HID_EVENT);
             }
             return HAL_OK;
         }
         return HAL_BUSY;
     }
    
     /* Phase 2: IN Transfer Process (Producer) */
     static void usbh_composite_hid_in_process(usb_host_t *host)
     {
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usbh_pipe_t *pipe = &(hid->pipe);
         usbh_urb_state_t urb_state = USBH_URB_IDLE;
         u32 len;
    
         /* Handle Transfer State Machine */
         switch (pipe->xfer_state) {
         // State A: Ready to start new transfer
         case USBH_EP_XFER_START:
             if (usbh_get_elapsed_ticks(host, pipe->tick) > pipe->ep_interval) {
                 pipe->tick = usbh_get_tick(host);
                 pipe->xfer_state = USBH_EP_XFER_BUSY;
                 pipe->xfer_len = pipe->ep_mps;
                 usbh_transfer_data(host, pipe);//Start USB Hardware transfer
                 hid->next_xfer = 1;            //Flag to notify main state machine
             }
             break;
         // State B: Waiting for transfer completion
         case USBH_EP_XFER_BUSY:
             urb_state = usbh_get_urb_state(host, pipe);
             /* Check if hardware finished transfer */
             if (urb_state == USBH_URB_DONE) {
                 len = usbh_get_last_transfer_size(host, pipe);
                 /* Save raw HID data to ring buffer */
                 usb_ringbuf_add_tail(&(hid->report_msg), pipe->xfer_buf, len, 1);
                 pipe->xfer_state = USBH_EP_XFER_START;  //Set for next transfer
             }
             break;
         default:
             break;
         }
     }
    
     /* Phase 3: Parsing Thread (Consumer) */
     static void usbh_composite_hid_msg_parse_thread(void *param)
     {
         UNUSED(param);
         usbh_composite_hid_t *hid = &usbh_composite_hid;
         usb_ringbuf_manager_t *handle = &(hid->report_msg);
         usbh_composite_hid_event_t *event = &(hid->report_event);
         u8 report_msg[10];
         u32 read_cnt;
    
         while (hid->parse_task_exit == 0) {
             // 1. Try to read data from ring buffer
             read_cnt = usb_ringbuf_remove_head(handle, report_msg, 10, NULL);
             if (read_cnt) {
                 if (hid->hid_ctrl) {
                     // 2. Parse the raw bytes into a meaningful event
                     ret = usbh_composite_hid_parse_hid_report(report_msg, len, &(hid->vol_caps));
                     if (ret != HAL_OK) {
                         return;
                     }
    
                     // 3. Trigger User Callback
                     if ((hid->cb != NULL) && (hid->cb->report != NULL)) {
                         hid->cb->report(event);
                     }
    
                 }
             } else {
                 rtos_time_delay_ms(50);
             }
         }
     }
    
    /****************** Application *************************/
     static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
         .report = usbh_hid_cb_report,
     };
    
     static int usbh_hid_cb_report(usbh_composite_hid_event_t *event)
     {
         switch (event->type) {
         case VOLUME_EVENT_CONSUMER_UP:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Up (Consumer) ===\n");
             break;
         case VOLUME_EVENT_CONSUMER_DOWN:
             RTK_LOGS(NOTAG, RTK_LOG_INFO, "=== Executing Volume Down (Consumer) ===\n");
             break;
         //Other event
         default:
             break;
         }
         return HAL_OK;
     }
    

API 说明

驱动 API

应用示例

本节以 USB Composite (HID + UAC) 为例,介绍完整的应用实现和运行测试的方式。

应用设计

本节详细介绍复合主机驱动的完整开发设计流程,涵盖加载驱动、热插拔管理以及资源释放等核心环节。

加载驱动

使用 Composite 驱动前,需定义配置结构体并注册回调函数,随后调用初始化接口加载 USB 主机核心驱动及 Composite 类驱动。

步骤说明:

  • 硬件配置:配置 USB 速度模式(High Speed/Full Speed)及中断优先级等。

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

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

  • 加载类驱动:调用 usbh_composite_init() 加载 Composite 类驱动。

HID + UAC 示例

static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_FULL,
    .ext_intr_enable = USBH_SOF_INTR,
    .isr_priority = INT_PRI_MIDDLE,
    .main_task_priority = USBH_UAC_MAIN_THREAD_PRIORITY,
    .tick_source = USBH_SOF_TICK,
};

static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .init = usbh_uac_cb_init,
    .deinit = usbh_uac_cb_deinit,
    .attach = usbh_uac_cb_attach,
    .detach = usbh_uac_cb_detach,
    .setup = usbh_uac_cb_setup,
    .isoc_transmitted = usbh_uac_cb_isoc_transmitted,
    .isoc_in_frm_cnt = USBH_UAC_FRAME_CNT,
    .isoc_out_frm_cnt = USBH_UAC_FRAME_CNT,
};

static usbh_composite_hid_usr_cb_t usbh_hid_cfg = {
    .report = usbh_hid_cb_report,
};

static usbh_user_cb_t usbh_usr_cb = {
    .process = usbh_uac_cb_process
};

int ret = 0;

/* Initialize USB host core driver with configuration. */
ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
if (ret != HAL_OK) {
    return;
}

/* Initialize class driver with application callback handler. */
ret = usbh_composite_init(&usbh_hid_cfg, &usbh_uac_cfg);
if (ret != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbh_deinit();
    return;
}

热插拔事件处理

作为主机,系统必须能够健壮地处理 USB 设备的动态插入与移除,SDK 默认支持热插拔机制。

处理逻辑:

  • 设备插入 (Attach):USB 核心检测到设备,重新执行枚举和驱动加载流程。

  • 设备拔出 (Detach):触发回调释放信号量,应用线程捕获后执行反初始化,并释放堆内存。

HID + UAC 示例

static rtos_sema_t usbh_uac_detach_sema;

/* USB detach callback */
static usbh_composite_uac_usr_cb_t usbh_uac_cfg = {
    .detach = usbh_uac_detach_sema,
};

/* Callback executed in ISR context */
static int usbh_uac_cb_detach(void)
{
    RTK_LOGS(TAG, RTK_LOG_INFO, "UAC DETACH\n");
    rtos_sema_give(usbh_uac_detach_sema);
    return HAL_OK;
}

static void usbh_uac_hotplug_thread(void *param)
{
    int ret = 0;

    UNUSED(param);

    for (;;) {
        if (rtos_sema_take(usbh_uac_detach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
            rtos_time_delay_ms(100);//make sure disconnect handle finish before deinit.

            /* 1. Clean up resources */
            usbh_composite_deinit();
            /* 2. De-initialize USB core */
            usbh_deinit();
            rtos_time_delay_ms(10);
            RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());
            /* 3. Re-initialize for next connection */
            ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
            if (ret != HAL_OK) {
                break;
            }

            ret = usbh_composite_init(&usbh_hid_cfg, &usbh_uac_cfg);
            if (ret < 0) {
                usbh_deinit();
                break;
            }
        }
    }
    RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
    rtos_task_delete(NULL);
}

数据收发处理

应用层在设备连接后,可调用面向应用的 API,进行参数的配置、数据传输的调度等。 通过应用层在初始化时注册的 usbh_composite_cb_tusbh_user_cb_t 和子功能驱动的回调函数 usbh_composite_function_class_xx_usr_cb_t 来获得设备状态和传输结果。 详细的数据收发过程请参考子功能方案的对应章节。

卸载驱动

在不再需要 USB 功能或系统关机时,按加载的反序释放资源。

HID + UAC 示例

/* Deinitialize Vendor class driver. */
usbd_composite_deinit();

/* Deinitialize USB host core driver. */
usbd_deinit();

运行方式

该示例演示了如何通过复合主机协议栈,将 Ameba 开发板配置为同时具备 USB HID (人机接口设备) 与 UAC (音频设备类) 功能的主机。

当开发板连接至标准 USB 耳机时,系统将识别出两个独立的逻辑设备。开发板会将音频数据发送到耳机,如果耳机上的按钮被按下,主机将识别该命令。

该示例路径: {SDK}/example/usb/usbh_composite_hid_uac ,可为开发者设计自定义 Composite 产品提供完整的参考方案。

配置与编译

  • 编译与烧录

    在 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 usbh_composite_hid_uac -p
    
  • Menuconfig 配置确认

    若编译失败,请执行 ameba.py menuconfig,确认已选择 USBH Composite HID + UAC 1.0

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] Composite
                Select Composite Class (HID + UAC)  --->
                    (X) HID + UAC
                        Select UAC Version (UAC 1.0)  --->
    

结果验证

  • 启动设备

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

    [COMP-I] USBH UAC&HID composite demo start
    
  • 连接主机

    使用 USB 线缆将开发板连接 USB Type-C 有线耳机。

  • 自动播放测试

    Ameba 开发板将识别出耳机,并自动进行音频播放测试。

  • 录音测试

    若耳机带有麦克风,将扬声器连接至开发板音频输出。对麦克风讲话,采集到的数据将通过扬声器实时播放。

  • 音量控制测试

    如果耳机还带有音量控制按钮,当按下按钮时,串口上将输出音量变化信息:

    ```
    Volume Up
    
    Volume Down
    ```