复合功能设备方案

概述

USB 复合设备(Composite Device)规范定义了通过单一物理 USB 接口承载多种独立功能类(如音频、HID、存储、串口等)的设备架构与枚举标准。

Ameba 基于 USB-IF 官方发布的 USB 规范,实现了灵活的复合设备功能框架,支持通过接口描述符(Interface Descriptor)或接口关联描述符(IAD)聚合多个功能接口,提供多逻辑设备在主机端的并行枚举、独立驱动加载及协同工作能力。

../../../_images/usb_device_composite_overview.svg

特性

  • 支持以下功能组合

    • CDC ACM + HID

    • CDC ACM + MSC

    • CDC ACM + UAC

    • HID + UAC

  • 支持热插拔

  • 支持描述符全定制

  • 支持配置传输缓存大小、速度模式等参数

应用场景

作为 USB 复合设备,Ameba 可通过单个 USB 物理接口同时枚举多种设备类,实现数据传输与控制交互的并行处理,适用于多种复杂的应用场景,例如:

  • 带线控的 USB 音频设备 (UAC + HID):Ameba 在提供 USB 音频输入/输出功能(UAC)的同时,利用 HID 接口实现媒体控制。用户既可以获得高品质的音频流体验(如耳机听音、麦克风录音),又能通过 HID 通道实现音量调节、静音、切歌或 RGB 灯效控制等交互功能。

  • 智能工业与 3D 打印控制 (MSC + CDC ACM):Ameba 结合了 MSC 大容量存储与 CDC 虚拟串口功能。在 3D 打印机或数据记录仪场景中,MSC 接口可模拟为 U 盘用于存储 G-code 切片文件或传感器历史数据,而 CDC 接口则同时作为控制台,供上位机实时发送 AT 指令、监控温度或校准参数。

  • 自动化测试与辅助输入 (HID Mouse + CDC ACM):Ameba 组合了 HID 鼠标与 CDC 串口功能。在此场景下,CDC 接口负责接收来自后台脚本或传感器的原始数据(如坐标指令、头部追踪数据),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), 驱动必须根据这个值把请求分发给对应接口功能驱动处理

类驱动

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

../../../_images/usb_device_composite_driver_arch_zh.png

描述符结构

为了让主机正确识别复合设备及其包含的多种功能,驱动程序必须在运行时动态在组装一套完整、精确的描述符。在生成设备描述符的时候需要注意下面几点:

  • 设备描述符

    bDeviceClass:通常为 0xEF (Miscellaneous) 或 0x00 (由接口定义)。

  • 配置描述符

    这是一个动态生成的“集合体”,包含了所有子功能的描述符。

    • bNumInterfaces:必须是所有子功能接口数量的总和。例如,一个 CDC 功能(占用 2 个接口)和一个 MSC 功能(占用 1 个接口)组成的复合设备,此值应为 3。

    • wTotalLength:必须是所有描述符(配置、IAD、接口、端点)长度的总和。这个值需要在运行时精确计算。

    • 接口关联描述符 (IAD): 如果某个子功能包含多个接口(如 CDC ACM),必须使用 IAD 将这些接口“捆绑”在一起,声明它们同属于一个功能。

  • 端点描述符

    端点的配置必须根据芯片的硬件能力来配置,请参考 硬件配置

    • 端点选择: 必须根据功能需求(IN/OUT)和硬件支持来选择可用的端点。

    • 最大包长 (MPS): 需考虑硬件限制。尤其在使用专用发送缓存时,要确保 IN 端点的发送缓存区大小至少能容纳一个最大数据包。

CDC ACM + MSC 示例

下面以 CDC ACM + MSC 为例介绍复合设备的描述符拓扑结构,这些结构体对应于 USB 协议规范中的标准描述符定义。

  • CDC ACM(虚拟串口):占用两个接口,为了让主机将其识别为单一的逻辑功能,必须使用 IAD 将这两个接口进行关联。

  • MSC(大容量存储):是一个独立的接口,不需要 IAD 关联。

该复合设备共使用 5 个非零端点(不包含默认控制端点 EP0):

接口编号

接口类

端点

说明

Interface 0

CDC Control (ACM)

1x Interrupt IN

用于通知串口状态(Serial State)与管理命令。

Interface 1

CDC Data

1x Bulk OUT, 1x Bulk IN

负责虚拟串口数据的发送(OUT)与接收(IN)。

Interface 2

MSC (Mass Storage)

1x Bulk OUT, 1x Bulk IN

负责大容量存储设备的数据读写(SCSI 命令/数据)。

驱动中一般将各个描述符定义为数组,在获得配置描述符时,按顺序调用每个子功能类驱动的 get_descriptor 回调函数,将它们聚合在一起。

 /* USB Standard Device Descriptor */
 static const u8 usbd_composite_dev_desc[USB_LEN_DEV_DESC] = {
    //...
    0xEF,       /* bDeviceClass: Miscellaneous */
    0x02,       /* bDeviceSubClass: Common Class */
    0x01,       /* bDeviceProtocol: Interface Association Descriptor */
    //...
 };

 /* USB Standard Configuration Descriptor */
 static const u8 usbd_composite_config_desc[USB_LEN_CFG_DESC] = {
    //...
    0x00, 0x00,  /* wTotalLength: calculated at runtime */
    0x03,        /* bNumInterfaces */
    //...
 };

/**
 * @brief  Get descriptor callback
 * @param  dev: USB device instance
 * @param  req: Setup request handle
 * @param  buf: Poniter to Buffer
 * @return Descriptor length
 */
 static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
 {
    usbd_composite_dev_t *cdev = &usbd_composite_dev;
    usb_speed_type_t speed = dev->dev_speed;
    u16 len = 0;
    u16 desc_len;
    u16 total_len = 0;

    switch (USB_HIGH_BYTE(req->wValue)) {
    //...
    case USB_DESC_TYPE_CONFIGURATION:
    case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
       usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
       buf += USB_LEN_CFG_DESC;
       total_len += USB_LEN_CFG_DESC;
       desc_len = cdev->cdc->get_descriptor(dev, req, buf);
       buf += desc_len;
       total_len += desc_len;
       desc_len = cdev->msc->get_descriptor(dev, req, buf);
       total_len += desc_len;
       buf = dev->ep0_in.xfer_buf;
       if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
          buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
       }
       buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
       buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
       len = total_len;
       break;
       }

    return len;
 }

完整的设备描述符拓扑结构如下:

Device Descriptor
|  bDeviceClass: 0xEF (Miscellaneous)
|  bDeviceSubClass: 0x02 (Common Class)
|  bDeviceProtocol: 0x01 (Interface Association Descriptor)
|
└── Configuration descriptor
    |   bNumInterfaces: 3 (3 Interfaces)
    |
    |   /* Function 1: CDC ACM */
    |
    ├── Interface Association Descriptor (IAD)
    |    bFirstInterface: 0
    |    bInterfaceCount: 2 (Associate Interface 0 with Interface 1)
    |    bFunctionClass:  0x02 (CDC Control)
    |    bFunctionSubClass: 0x02 (ACM)
    |
    ├── Interface Descriptor 0 (CDC Control Interface)
    |   |  bInterfaceNumber: 0
    |   |  bInterfaceClass:  0x02 (CDC Control)
    |   |  bInterfaceSubClass: 0x02 (ACM)
    |   |  ...
    |   ├── CDC Class Specific Descriptors (Header, Call Mgmt, ACM...)
    |   └── Endpoint Descriptor (Interrupt IN)
    |
    ├── Interface Descriptor 1 (CDC Data Interface)
    |   |  bInterfaceNumber: 1
    |   |  bInterfaceClass:  0x0A (CDC Data)
    |   |  ...
    |   ├── Endpoint Descriptor (Bulk OUT)
    |   └── Endpoint Descriptor (Bulk IN)
    |
    |   /* Function 2: MSC */
    |
    └── Interface Descriptor 2 (MSC Interface)
        |  bInterfaceNumber: 2
        |  bInterfaceClass:  0x08 (Mass Storage)
        |  bInterfaceSubClass: 0x06 (SCSI Transparent Command Set)
        |  bInterfaceProtocol: 0x50 (Bulk-Only Transport / BBB)
        |   ...
        ├── Endpoint Descriptor (Bulk OUT)
        └── Endpoint Descriptor (Bulk IN)

备注

类驱动实现

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

  • 子功能类驱动

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

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

    • 独立的资源管理: 每个子驱动负责管理自己的端点、数据缓冲区以及数据收发处理等。

  • 复合类驱动

    • 复合类驱动需要定义一个标准的 usbd_class_driver_t 结构体,作为统一的入口注册到 USB Core 中,复合设备驱动会分发或者遍历子功能类驱动回调函数。

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

      /* Composite Device */
      static usbd_composite_dev_t usbd_composite_dev;
      
      /* Composite Class Driver */
      static const usbd_class_driver_t usbd_composite_driver = {
        .get_descriptor = usbd_composite_get_descriptor,       /* 遍历所有子功能类获取聚合后的配置描述符 */
        .set_config     = usbd_composite_set_config,           /* 遍历初始化所有子功能类端点和资源 */
        .clear_config   = usbd_composite_clear_config,         /* 遍历释放所有子功能类端点和资源 */
        .setup          = usbd_composite_setup,                /* 分发类控制请求到不同接口:wIndex = Interface xx */
        .sof            = usbd_composite_sof,                  /* SOF 中断时被调用,用于时序要求严格的处理逻辑 */
        .ep0_data_out   = usbd_composite_handle_ep0_data_out,  /* 在设备准备完成后,分发处理控制 OUT 端点的子功能类请求 */
        .ep0_data_in    = usbd_composite_handle_ep0_data_in,   /* 在设备准备完成后,分发处理控制 IN 端点的子功能类请求结果 */
        .ep_data_in     = usbd_composite_handle_ep_data_in,    /* 分发处理 IN 端点数据,利用 ep_addr (端点地址) 判断数据属于哪个子功能类 */
        .ep_data_out    = usbd_composite_handle_ep_data_out,   /* 分发处理 OUT 端点数据,利用 ep_addr (端点地址) 判断数据属于哪个子功能类 */
        .status_changed = usbd_composite_status_changed,       /* 监控连接状态,必要时通知应用层或所有子功能类状态机 */
      };
      

CDC ACM + MSC 示例

下面以 CDC ACM + MSC 为例介绍复合设备类驱动实例实现:

/* Composite Device structure. */
typedef struct {
   usb_setup_req_t ctrl_req;  /* Control setup request */
   usbd_class_driver_t *cdc;  /* CDC ACM class */
   usbd_class_driver_t *msc;  /* MSC class */
   usbd_composite_cb_t *cb;   /* Composite user callback */
   usb_dev_t *dev;            /* USB device instance */
} usbd_composite_dev_t;
/* Composite Device */
static usbd_composite_dev_t usbd_composite_dev;

/* Composite Class Driver */
static const usbd_class_driver_t usbd_composite_driver = {
   .get_descriptor = usbd_composite_get_descriptor,
   .set_config = usbd_composite_set_config,
   .clear_config = usbd_composite_clear_config,
   .setup = usbd_composite_setup,
   .ep0_data_out = usbd_composite_handle_ep0_data_out,
   .ep_data_in = usbd_composite_handle_ep_data_in,
   .ep_data_out = usbd_composite_handle_ep_data_out,
   .status_changed = usbd_composite_status_changed,
};

/********************** Function 1: CDC ACM class *********************/
/* CDC ACM device structure. */
typedef struct {
   usbd_composite_dev_t *cdev;           /**< Pointer to the parent composite device structure. */
   usbd_composite_cdc_acm_usr_cb_t *cb;  /**< Pointer to the user-registered callback structure. */
   usbd_ep_t ep_bulk_in;                 /**< Bulk IN endpoint handler. */
   usbd_ep_t ep_bulk_out;                /**< Bulk OUT endpoint handler. */
#if CONFIG_COMP_CDC_ACM_NOTIFY
   usbd_ep_t ep_intr_in;                 /**< Interrupt IN endpoint handler (for notifications). */
#endif
} usbd_composite_cdc_acm_dev_t;
/* CDC ACM Device */
static usbd_composite_cdc_acm_dev_t composite_cdc_acm_dev;

/* CDC ACM Class Driver */
const usbd_class_driver_t usbd_composite_cdc_acm_driver = {
   .get_descriptor = composite_cdc_acm_get_descriptor,
   .set_config = composite_cdc_acm_set_config,
   .clear_config = composite_cdc_acm_clear_config,
   .setup = composite_cdc_acm_setup,
   .ep_data_in = composite_cdc_acm_handle_ep_data_in,
   .ep_data_out = composite_cdc_acm_handle_ep_data_out,
   .ep0_data_out = composite_cdc_acm_handle_ep0_data_out,
};

/********************** Function 2: MSC class *********************/
/* MSC device structure.  */
typedef struct {
   usbd_ep_t ep_bulk_in;                 /**< Bulk IN endpoint handler. */
   usbd_ep_t ep_bulk_out;                /**< Bulk OUT endpoint handler. */
   usbd_composite_dev_t *cdev;           /**< Pointer to the parent composite device structure. */
   //...
} usbd_composite_msc_dev_t;
/* MSC Device */
static usbd_composite_msc_dev_t usbd_composite_msc_dev;

/* MSC Class Driver */
const usbd_class_driver_t usbd_composite_msc_driver = {
   .get_descriptor = usbd_composite_msc_get_descriptor,
   .set_config = usbd_composite_msc_set_config,
   .clear_config = usbd_composite_msc_clear_config,
   .setup = usbd_composite_msc_setup,
   .ep_data_in = usbd_composite_msc_handle_ep_data_in,
   .ep_data_out = usbd_composite_msc_handle_ep_data_out,
};

复合类驱动 usbd_composite_driver 的具体实现:

/**
* @brief  Set composite class configuration
* @param  dev: USB device instance
* @param  config: USB configuration index
* @return Status
*/
static int usbd_composite_set_config(usb_dev_t *dev, u8 config)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->dev = dev;

   cdev->cdc->set_config(dev, config);
   cdev->msc->set_config(dev, config);

   return ret;
}

/**
* @brief  Clear composite configuration
* @param  dev: USB device instance
* @param  config: USB configuration index
* @return Status
*/
static int usbd_composite_clear_config(usb_dev_t *dev, u8 config)
{
   int ret = 0U;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->cdc->clear_config(dev, config);
   cdev->msc->clear_config(dev, config);

   return ret;
}

/**
* @brief  Handle class specific control requests
* @param  dev: USB device instance
* @param  req: USB control requests
* @return Status
*/
static int usbd_composite_setup(usb_dev_t *dev, usb_setup_req_t *req)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;
   usbd_ep_t *ep0_in = &dev->ep0_in;
   int ret = HAL_OK;

   switch (req->bmRequestType & USB_REQ_TYPE_MASK) {
   //...
   case USB_REQ_TYPE_CLASS:
      if ((req->wIndex == USBD_COMP_CDC_COM_ITF) || (req->wIndex == USBD_COMP_CDC_DAT_ITF)) {
         ret = cdev->cdc->setup(dev, req);
      } else if (req->wIndex == USBD_COMP_MSC_ITF) {
         ret = cdev->msc->setup(dev, req);
      } else {
         RTK_LOGS(TAG, RTK_LOG_WARN, "Invalid class req\n");
      }
      break;
   }

   return ret;
}

/**
* @brief  Data sent on non-control IN endpoint
* @param  dev: USB device instance
* @param  ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_in(usb_dev_t *dev, u8 ep_addr, u8 status)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if ((ep_addr == USBD_COMP_CDC_BULK_IN_EP) || (ep_addr == USBD_COMP_CDC_INTR_IN_EP)) {
      if (cdev->cdc->ep_data_in != NULL) {
         ret = cdev->cdc->ep_data_in(dev, ep_addr, status);
      }
   } else if (ep_addr == USBD_COMP_MSC_BULK_IN_EP) {
      if (cdev->msc->ep_data_in != NULL) {
         ret = cdev->msc->ep_data_in(dev, ep_addr, status);
      }
   }

   return ret;
}

/**
* @brief  Data received on non-control OUT endpoint
* @param  dev: USB device instance
* @param  ep_addr: endpoint address
* @return Status
*/
static int usbd_composite_handle_ep_data_out(usb_dev_t *dev, u8 ep_addr, u16 len)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if (ep_addr == USBD_COMP_CDC_BULK_OUT_EP) {
      if (cdev->cdc->ep_data_out != NULL) {
         ret = cdev->cdc->ep_data_out(dev, ep_addr, len);
      }
   } else if (ep_addr == USBD_COMP_MSC_BULK_OUT_EP) {
      if (cdev->msc->ep_data_out != NULL) {
         ret = cdev->msc->ep_data_out(dev, ep_addr, len);
      }
   }

   return ret;
}

/**
* @brief  Handle EP0 Rx Ready event
* @param  dev: USB device instance
* @return Status
*/
static int usbd_composite_handle_ep0_data_out(usb_dev_t *dev)
{
   int ret = HAL_OK;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   cdev->cdc->ep0_data_out(dev);

   return ret;
}

/**
* @brief  USB attach status change
* @param  dev: USB device instance
* @param  status: USB attach status
* @return void
*/
static void usbd_composite_status_changed(usb_dev_t *dev, u8 old_status, u8 status)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   UNUSED(dev);

   if (cdev->cb->status_changed) {
      cdev->cb->status_changed(old_status, status);
   }
}

/**
* @brief  Get descriptor callback
* @param  dev: USB device instance
* @param  req: Setup request handle
* @param  buf: Poniter to Buffer
* @return Descriptor length
*/
static u16 usbd_composite_get_descriptor(usb_dev_t *dev, usb_setup_req_t *req, u8 *buf)
{
   usbd_composite_dev_t *cdev = &usbd_composite_dev;
   usb_speed_type_t speed = dev->dev_speed;
   u16 len = 0;
   u16 desc_len;
   u16 total_len = 0;

   switch (USB_HIGH_BYTE(req->wValue)) {
   //...
   case USB_DESC_TYPE_CONFIGURATION:
   case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
      usb_os_memcpy((void *)buf, (void *)usbd_composite_config_desc, USB_LEN_CFG_DESC);
      buf += USB_LEN_CFG_DESC;
      total_len += USB_LEN_CFG_DESC;
      desc_len = cdev->cdc->get_descriptor(dev, req, buf);
      buf += desc_len;
      total_len += desc_len;
      desc_len = cdev->msc->get_descriptor(dev, req, buf);
      total_len += desc_len;
      buf = dev->ep0_in.xfer_buf;
      if (USB_HIGH_BYTE(req->wValue) == USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION) {
         buf[USB_CFG_DESC_OFFSET_TYPE] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
      }
      buf[USB_CFG_DESC_OFFSET_TOTAL_LEN] = USB_LOW_BYTE(total_len);
      buf[USB_CFG_DESC_OFFSET_TOTAL_LEN + 1] = USB_HIGH_BYTE(total_len);
      len = total_len;
      break;
      }

   return len;
}

应用层回调 API

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

  • Composite 应用层回调 API

    usbd_composite_cb_t 是针对整个复合设备状态的回调,由应用层实现。

  • 子功能类应用层回调 API

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

    typedef struct {
       int(* init)(void);
       int(* deinit)(void);
       int(* setup)(usb_setup_req_t *req, u8 *buf);
       int(* set_config)(void);
       void (*status_changed)(u8 old_status, u8 status);
       int(* sof)(void);
       int(* received)(u8 *buf, u32 len);
       void(* transmitted)(u8 status);
    } usbd_composite_function_class_xx_usr_cb_t;
    

    API

    描述

    init

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

    deinit

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

    setup

    在控制传输setup或data阶段被调用,用于处理应用相关的控制请求

    set_config

    在类驱动set_config回调中被调用,用于通知应用层UAC类驱动已就绪

    status_changed

    在USB连接状态改变时被调用,用于应用层处理USB热插拔事件

    sof

    收到SOF中断时被调用,用于应用层处理时钟同步

    transmitted

    在IN传输完成时被调用,用于应用层以异步的方式获取IN传输状态

    received

    在OUT传输完成时被调用,用于应用层以异步的方式获取OUT传输状态

CDC ACM + MSC 示例

下面以 CDC ACM + MSC 为例介绍自定义的子功能类应用层回调(MSC 没有使用应用层回调):

/**
* @brief User callback structure for CDC ACM events.
* @details This structure allows the application layer to handle CDC ACM events.
*/
typedef struct {
   int(* init)(void);                             /**< Called during class driver initialization for application resource setup. */
   int(* deinit)(void);                           /**< Called during class driver deinitialization for resource cleanup. */
   int(* setup)(usb_setup_req_t *req, u8 *buf);   /**< Called during control transfer SETUP/DATA phases to handle application-specific control requests. */
   int(* received)(u8 *buf, u32 len);             /**< Called when new data is received on the Bulk OUT endpoint. */
   void(* transmitted)(u8 status);                /**< Called after data transmission on the Bulk IN endpoint is complete. */
} usbd_composite_cdc_acm_usr_cb_t;

面向应用层 API

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

CDC ACM + MSC 示例

下面以 CDC ACM + MSC 为例介绍复合设备类驱动面向应用层 API 实现:

/**
* @brief  Init composite class
* @param  cdc_bulk_out_xfer_size: CDC ACM bulk out xfer buffer size
* @param  cdc_bulk_in_xfer_size: CDC ACM bulk in xfer buffer size
* @param  cdc_cb: CDC ACM user callback
* @param  cb: composite user callback
* @return Status
*/
int usbd_composite_init(u16 cdc_bulk_out_xfer_size, u16 cdc_bulk_in_xfer_size, usbd_composite_cdc_acm_usr_cb_t *cdc_cb, usbd_composite_cb_t *cb)
{
   int ret;
   usbd_composite_dev_t *cdev = &usbd_composite_dev;

   if (cdc_cb == NULL) {
      ret = HAL_ERR_PARA;
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Invalid user cb\n");
      return ret;
   }

   if (cb != NULL) {
      cdev->cb = cb;
   }

   ret = usbd_composite_cdc_acm_init(cdev, cdc_bulk_out_xfer_size, cdc_bulk_in_xfer_size, cdc_cb);
   if (ret != HAL_OK) {
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Init CDC ACM itf fail: %d\n", ret);
      return ret;
   }

   ret = usbd_composite_msc_init(cdev);
   if (ret != HAL_OK) {
      RTK_LOGS(TAG, RTK_LOG_ERROR, "Init MSC itf fail: %d\n", ret);
      usbd_composite_cdc_acm_deinit();
      return ret;
   }

   cdev->cdc = (usbd_class_driver_t *)&usbd_composite_cdc_acm_driver;
   cdev->msc = (usbd_class_driver_t *)&usbd_composite_msc_driver;

   usbd_register_class(&usbd_composite_driver);

   return ret;
}

/**
* @brief  DeInit composite class
* @param  void
* @return Status
*/
void usbd_composite_deinit(void)
{
   usbd_unregister_class();

   usbd_composite_msc_deinit();
   usbd_composite_cdc_acm_deinit();
}

备注

详细类驱动说明请参考: 自定义设备方案

API 说明

驱动 API

应用示例

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

应用设计

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

加载驱动

复合设备初始化过程需要依次加载 USB 核心初始化及 Composite 类驱动。定义配置结构体并注册用户回调函数是必要步骤。

步骤说明:

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

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

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

  • 加载类驱动:调用 usbd_composite_init() 加载复合类驱动。

下面以 CDC ACM + MSC 为例介绍加载复合设备驱动实现:

其中 MSC 功能大部分交互由协议栈自动处理,应用层主要关注磁盘的初始化与去初始化。 在 USB 初始化之前,必须先确保存储介质(如 SD 卡或 Flash)已就绪,并调用磁盘初始化接口。

static usbd_config_t composite_cfg = {
   .speed = CONFIG_USBD_COMPOSITE_SPEED,
   .isr_priority = CONFIG_USBD_COMPOSITE_ISR_THREAD_PRIORITY,
   .intr_use_ptx_fifo = 0U,
}

static usbd_composite_cdc_acm_usr_cb_t composite_cdc_acm_usr_cb = {
   .init = composite_cdc_acm_cb_init,
   .deinit = composite_cdc_acm_cb_deinit,
   .setup = composite_cdc_acm_cb_setup,
   .received = composite_cdc_acm_cb_received,
   .transmitted = composite_cdc_acm_cb_transmitted
};

static usbd_composite_cb_t composite_cb = {
   .status_changed = composite_cb_status_changed,
};

int ret = 0;

/* Initializes the underlying storage disk. */
ret = usbd_composite_msc_disk_init();
if (ret != HAL_OK) {
   return;
}

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

/* Initialize composite class driver.   */
ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
                     CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
                     &composite_cdc_acm_usr_cb,
                     &composite_cb);
if (ret != HAL_OK) {
   usbd_composite_msc_disk_deinit();
   usbd_composite_deinit();
   return;
}

热插拔事件处理

通过注册 status_changed 回调函数来监听 USB 连接状态变化(连接/断开)。

参考 设备连接状态检测 , 获取更多细节。

备注

建议使用信号量(Semaphore)通知专用任务线程进行处理,避免在中断上下文中执行耗时操作。

下面以 CDC ACM + MSC 为例介绍复合设备驱动热插拔事件处理:

static u8 composite_attach_status;
static rtos_sema_t composite_attach_status_changed_sema;

/* USB status change callback */
static usbd_composite_cb_t composite_cb = {
   .status_changed = composite_cb_status_changed,
};

/* Callback executed in ISR context */
static void composite_cb_status_changed(u8 old_status, u8 status)
{
   composite_attach_status = status;
   rtos_sema_give(composite_attach_status_changed_sema);
}

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

   UNUSED(param);

   for (;;) {
         /* Wait for status change signal */
      if (rtos_sema_take(composite_attach_status_changed_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
         if (composite_attach_status == USBD_ATTACH_STATUS_DETACHED) {
            RTK_LOGS(TAG, RTK_LOG_INFO, "DETACHED\n");
            /* 1. Clean up composite class resources */
            usbd_composite_deinit();
            /* 2. De-initialize USB core */
            ret = usbd_deinit();
            if (ret != 0) {
               break;
            }
            usbd_composite_msc_disk_deinit();
            /* 3. Re-initialize for next connection */
            usbd_composite_msc_disk_init();
            ret = usbd_init(&composite_cfg);
            if (ret != 0) {
               break;
            }
            ret = usbd_composite_init(CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_OUT_XFER_SIZE,
                              CONFIG_USBD_COMPOSITE_CDC_ACM_MSC_BULK_IN_XFER_SIZE,
                              &composite_cdc_acm_usr_cb,
                              &composite_cb);
            if (ret != 0) {
               usbd_deinit();
               break;
            }
         } else if (composite_attach_status == USBD_ATTACH_STATUS_ATTACHED) {
            RTK_LOGS(TAG, RTK_LOG_INFO, "ATTACHED\n");
         } else {
            RTK_LOGS(TAG, RTK_LOG_INFO, "INIT\n");
         }
      }
   }
   RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
   rtos_task_delete(NULL);
}

卸载驱动

在不再需要 USB 功能或系统关机时,需按加载的反序释放资源。 下面以 CDC ACM + MSC 为例介绍复合设备驱动卸载实现:

/* De-initializes the underlying storage disk. */
usbd_composite_msc_disk_deinit();

/* Deinitialize composite class driver first */
usbd_composite_deinit();

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

运行方式

该示例演示了如何通过复合设备协议栈,将 Ameba 开发板配置为同时具备 CDC ACM(虚拟串口) 和 MSC(大容量存储) 功能的设备。

当开发板连接至 USB 主机(如 PC)时,系统将识别出两个独立的逻辑设备。用户既可以通过串口工具与开发板进行通信,也可以像操作普通 U 盘一样读写板载 SD 卡中的文件。

该示例路径: {SDK}/example/usb/usbd_composite_cdc_acm_msc ,可为开发者设计自定义 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 usbd_composite_cdc_acm_msc -p
    
  • Menuconfig 配置确认

    若编译失败,请执行 ameba.py menuconfig,确认已选择 USB Composite Device 以及具体的复合类组合(CDC ACM + MSC)。

    - Choose `CONFIG USB --->`:
    
       [*] Enable USB
             USB Mode (Device) --->
       [*] Composite
             Select Composite Class (CDC ACM + MSC) --->
    
          (X) CDC ACM + MSC
             Select storage media (SD Card (SD mode))  --->
    

结果验证

  • 启动设备

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

    [COMP] USBD COMP demo start
    
  • 连接主机

    用 USB 线缆将开发板连接到 PC。

    当该设备连接至 Windows 主机时,设备管理器将呈现如下层级结构:

    • 通用串行总线控制器 下出现:USB Composite Device (由 usbccgp.sys 驱动)。

    • 通用串行总线控制器 下出现:USB 大容量存储设备 (对应 MSC 功能)。

    • 端口 (COM LPT) 下出现:USB 串行设备 (COMx) (对应 CDC ACM 功能)。

  • 功能验证 1:CDC ACM (虚拟串口)

    • 在 PC 端打开串口调试工具(如 Realtek Trace Tool)。

    • 选择 Ameba 枚举出的虚拟串口号。

    • 发送任意字符,开发板将原样回传接收到的数据,验证通信正常。

  • 功能验证 2:MSC (大容量存储)

    PC 端文件管理器中应自动弹出一个新的可移动磁盘盘符。用户可以双击打开该盘符,对插入的 SD 卡进行文件读写操作。

    备注

    本示例使用 SD 卡作为 MSC 的底层存储介质,请确保开发板板载 SDIOH 插槽 中已插入一张格式化好的 SD 卡。 请避免在数据读写过程中拔出 SD 卡或断开 USB 连接,以免造成文件系统损坏。