HiSpark编程指南

- 调测资料
  - SDK Sample使用指南
    - 前言
    - 概述
    - Sample框架
    - 外设 Sample
    - SLE UART Sample
    - SLE UART DMA Sample
    - SLE Mouse Sample
    - SLE amic vdt with dongle
    - SLE microphone
    - SLE measure dis
    - BLE Lowpower Sample
    - BLE UART Sample
    - BLE Keyboard Sample
    - BLE SLE TAG Sample
    - BLE Mouse Sample
    - USB Keyboard Sample
    - USB Mouse Sample
    - USB AMIC VDT Sample
    - RCU
    - 多连接
    - air mouse
    - SLE MOUSE with Dongle
    - SLE OTA dongle
    - game mouse
    - BLE UUID
    - 缩略语
  - SDK二次开发网络安全注意事项
    - 前言
    - 产品安全解决方案
    - 结论
  - SDK开发环境搭建用户指南
    - 前言
    - 开发环境搭建
    - 编译SDK
    - 新建APP
  - 内存维测指导书
    - 前言
    - 简介
    - 内存维测功能
  - 射频AT测试指南
    - 前言
    - 射频测试AT指令
    - AT指令组包示例
    - 注意事项
  - 维测指导书
    - 前言
    - 概述
    - 日志说明
    - 日志分析
    - 问题定位
    - 典型案例
    - 常见问题FAQ
- 软件资料
  - AT命令使用指南
    - 前言
    - 指令说明
    - 通用AT指令
    - BT业务AT指令
    - 新增AT指令方法
  - DFU升级APK开发指南
    - 前言
    - 功能特性
    - 系统框架
    - 工程文件部署
    - 界面实现
    - 升级界面Fragment实现
    - 关于界面Fragment实现
    - 应用清单AndroidManifest
  - DFX软件开发指南
    - 前言
    - DIAG维测功能
    - 死机诊断
    - Dump解析
  - EFUSE使用指南
    - 前言
    - 概述
    - 使用软件接口读写EFUSE
    - 使用BurnTool烧写EFUSE
  - NFC TAG软件开发指导书
    - 前言
    - 概述
    - 开发指导
  - NV存储用户指南
    - 前言
    - NV简介
    - NV编译预置
    - NV API指南
    - 定制化NV结构使用介绍
    - 通过NV自定义传输数据最大长度
  - Release notes
    - 1.0.18版本描述文件
    - 1.0.17版本描述文件
    - 1.0.16版本描述文件
  - SDK开发指南
    - 前言
    - 概述
    - 系统接口
  - SDK接口变更说明书
    - 前言
    - BLE SDK接口变更说明
  - SP31 SLP使用指南
    - 开源版本
  - USB HID复合设备开发指南
    - 前言
    - 概述
    - 实现方式
  - 代码框架说明
    - 前言
    - SDK模块框架
    - SDK目录结构
    - SDK目录说明
  - 低功耗FAQ
    - 前言
    - 系统无法睡眠
    - IO无法唤醒系统
    - 唤醒后外设异常
    - 低功耗导致OTA升级异常
    - 功耗比预期高
    - 锁中断导致BT业务阻塞
    - 概率性狗超时
    - 供电模式&软件配置
    - 提前唤醒时间问题
  - 低功耗sample使用指南
    - 前言
    - 编译&测试
    - 系统状态管理
    - 用户指南
  - 低功耗开发指南
    - 前言
    - 低功耗概述
    - 低功耗方案
    - 开发指引
    - 功耗优化
    - 常见问题
  - 功率配置说明书
    - 前言
    - 射频功率配置说明
  - 升级方案使用指南
    - 前言
    - 概述
    - 升级包制作
    - 升级包传输
    - 升级包保存
    - 升级包本地升级
    - FAQ
  - 快速入门指南
    - 前言
    - 文档目录
    - 环境搭建
    - 基础功能开发
    - 调优
    - 特性开发
    - 测试报告
  - 星闪应用层SLE-Link协议
    - 前言
    - 概述
    - SLE Frame格式定义
    - 服务接口定义
    - 交互流程示例
  - 星闪软件开发指导书
    - 前言
    - 概述
    - Device Discovery接口
    - Connection Manager接口
    - SSAP Server接口
    - SSAP client接口
    - Low Latency接口
    - Transmission Manager接口
    - SLE Passkey接口
    - SLE Channel Map 接口
  - 红外学习使用指南
    - 前言
    - 红外学习方式
    - 通过比较器进行红外学习
    - 使用指南
  - 蓝牙软件开发指导书
    - 前言
    - 概述
    - GAP接口
    - GATT server接口
    - GATT client接口
  - 设备驱动开发指南
    - 前言
    - Pinctrl
    - GPIO
    - UART
    - SPI
    - I2C
    - ADC
    - DMA
    - PWM
    - WDT
    - Timer
    - Systick
    - TCXO
    - Flash
    - SFC
    - QDEC
    - KEYSCAN
    - PDM
    - USB_HID
    - USB_DFU
    - I2S
    - TRNG
    - 附：MIDDLEWARE
  - 音频编解码开发指导书
    - 前言
    - 概述
    - Audio Encoder
    - Audio Decoder
    - 调试与功能
- 工具资料
  - BurnTool工具使用指导书
    - 前言
    - BurnTool工具简介
    - 操作指南
    - FAQ
  - HiSpark Studio使用指南
    - 前言
    - 工具简介
    - 开发环境搭建
    - 工程管理
    - 工程配置
    - 编译运行
    - 软件包烧录
    - 栈分析和镜像分析
    - 工程调试
    - 串口控制台工具
    - Kconfig配置
    - GUI工程创建与使用
    - 高阶分析功能使用
    - 常见错误

### 该页内容

- 前言
- 概述
- 实现方式
  - 一个接口实现多个功能
  - 多个接口实现多个功能
  - 实现虚拟串口功能
  - 实现升级功能(DFU)
  - 实现音频数据接口(UAC)

# 前言

概述

本文档描述了BS2X USB HID复合设备的两种实现方式。

读者对象

本文档主要适用于以下工程师：

- 产品软件开发工程师

- 技术支持工程师

符号约定

在本文中可能出现下列标志，它们所代表的含义如下。

符号 | 说明
--- | ---
| 表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。
| 表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。
| 表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。
| 用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。 “须知”不涉及人身伤害。
| 对正文中重点信息的补充说明。 “说明”不是安全警示信息，不涉及人身、设备及环境伤害信息。

修改记录

文档版本 | 发布日期 | 修改说明
--- | --- | ---
02 | 2024-08-20 | 更新“实现方式”章节内容。
01 | 2024-05-15 | 第一次正式版本发布。
00B01 | 2024-03-08 | 第一次临时版本发布。

# 概述

USB设备的功能是由接口来承载的，对应到代码即是接口描述符，一般一个接口就是一个功能，这个功能可能是鼠标、键盘、手柄等，也可能是自定义功能。HID复合设备是一类多功能设备，例如鼠标+键盘、手柄+键盘等。

# 实现方式

添加任何类型的HID设备都需要调用接口hid_add_report_descriptor。“hid_add_report_descriptor”定义在“f_hid.c”，其参数及功能如下：

参数 | 说明
--- | ---
report_desc | 报告描述符的首地址。
report_desc_len | 报告描述符的长度。
protocol | 报告描述符的协议类型（0=none, 1=keyboard, 2=mouse），其对应接口描述符的bInterfaceProtocol字段。

函数功能：添加报告描述符并和接口关联，会自动创建接口描述符、hid描述符和端点描述符。

函数返回值：设备功能对应的id号。

## 一个接口实现多个功能

HID设备一个接口对应一个报告描述符，由报告描述符来描述接口的功能，而报告描述符可通过report_id项来区分同一个报告描述符的不同功能。

报告描述符1如下：

    // 键盘设备
    usage_page(1),      0x01,
    usage(1),           0x06,
    collection(1),      0x01,
    report_id(1),       0x01,
    usage_page(1),      0x07,
    usage_minimum(1),   0xE0,
    usage_maximum(1),   0xE7,
    logical_minimum(1), 0x00,
    logical_maximum(1), 0x01,
    report_size(1),     0x01,
    report_count(1),    0x08,
    input(1),           0x02,
    report_count(1),    0x01,
    report_size(1),     0x08,
    input(1),           0x01,
    report_count(1),    0x05,
    report_size(1),     0x01,
    usage_page(1),      0x08,
    usage_minimum(1),   0x01,
    usage_maximum(1),   0x05,
    output(1),          0x02,
    report_count(1),    0x01,
    report_size(1),     0x03,
    output(1),          0x01,
    report_count(1),    0x06,
    report_size(1),     0x08,
    logical_minimum(1), 0x00,
    logical_maximum(1), 0x65,
    usage_page(1),      0x07,
    usage_minimum(1),   0x00,
    usage_maximum(1),   0x65,
    input(1),           0x00,
    end_collection(0),
    // 自定义数据接收
    usage_page(2), 0xB1, 0xFF,
    usage(1),           0x1,
    collection(1),      0x01,
    report_id(1),       0x08,
    collection(1),      0x00,
    report_count(1),    0xc,
    report_size(1),     0x8,
    usage_minimum(1),   0x0,
    usage_maximum(1),   0xFF,
    output(1),           2,
    end_collection(0),
    end_collection(0),
    // 自定义数据收发
    usage_page(2), 0xB2, 0xFF,
    usage(1),           0x1,
    collection(1),      0x01,
    report_id(1),       0x09,
    collection(1),      0x00,
    report_count(1),    0x3f,
    report_size(1),     0x8,
    usage_minimum(1),   0x0,
    usage_maximum(1),   0xFF,
    output(1),           2,
    usage(1),           0x2,
    report_count(1),    0x3f,
    report_size(1),     0x8,
    usage_minimum(1),   0x0,
    usage_maximum(1),   0xFF,
    input(1),           0,
    end_collection(0),
    end_collection(0),

上述报告描述符定义了一个键盘设备+自定义数据接收+自定义数据收发三个功能的复合设备，他们通过report_id+数据的方式来区分功能，即只有一个功能的报告描述符，在发送数据时直接发送数据即可，拥有多个功能的报告描述符在发送数据时需要在数据的头部加上report_id。

## 多个接口实现多个功能

HID设备一个接口对应一个报告描述符，由报告描述符来描述接口的功能，那么多个接口多个报告描述符也可以实现复合设备功能，BS2X芯片除了端点0之外，一共有三个IN端点，三个OUT端点，因此最多支持三个接口。

多个接口多个报告描述符的示例如下。

报告描述符1如下：

    usage_page(1),      0x01,
    usage(1),         0x06,
    collection(1),      0x01,
    report_id(1),       0x01,
    usage_page(1),      0x07,
    usage_minimum(1),    0xE0,
    usage_maximum(1),    0xE7,
    logical_minimum(1),   0x00,
    logical_maximum(1),   0x01,
    report_size(1),     0x01,
    report_count(1),    0x08,
    input(1),         0x02,
    report_count(1),    0x01,
    report_size(1),     0x08,
    input(1),         0x01,
    report_count(1),    0x05,
    report_size(1),     0x01,
    usage_page(1),      0x08,
    usage_minimum(1),    0x01,
    usage_maximum(1),    0x05,
    output(1),        0x02,
    report_count(1),    0x01,
    report_size(1),     0x03,
    output(1),        0x01,
    report_count(1),    0x06,
    report_size(1),     0x08,
    logical_minimum(1),   0x00,
    logical_maximum(1),   0x65,
    usage_page(1),      0x07,
    usage_minimum(1),    0x00,
    usage_maximum(1),    0x65,
    input(1),         0x00,
    end_collection(0),
    // 自定义数据接收
    usage_page(2),       0xB1, 0xFF,
    usage(1),         0x1,
    collection(1),      0x01,
    report_id(1),       0x08,
    collection(1),     0x00,
    report_count(1),    0xc,
    report_size(1),     0x8,
    usage_minimum(1),    0x0,
    usage_maximum(1),    0xFF,
    output(1),         2,
    end_collection(0),
    end_collection(0),
    // 自定义数据收发
    usage_page(2),        0xB2, 0xFF,
    usage(1),          0x1,
    collection(1),      0x01,
    report_id(1),       0x09,
    collection(1),      0x00,
    report_count(1),     0x3f,
    report_size(1),      0x8,
    usage_minimum(1),     0x0,
    usage_maximum(1),     0xFF,
    output(1),          2,
    usage(1),          0x2,
    report_count(1),     0x3f,
    report_size(1),      0x8,
    usage_minimum(1),     0x0,
    usage_maximum(1),     0xFF,
    input(1),           0,
    end_collection(0),
    end_collection(0),

上述报告描述符定义了一个键盘设备+自定义数据接收+自定义数据收发三个功能的复合设备，他们通过report_id+数据的方式来区分功能，即只有一个功能的报告描述符，在发送数据时直接发送数据即可，拥有多个功能的报告描述符在发送数据时需要在数据的头部加上“report_id”。此描述符可通过“hid_add_report_descriptor”函数注册到接口0。

报告描述符2如下：

    usage_page(1),      0x01,
    usage(1),          0x02,
    collection(1),      0x01,
    usage(1),          0x01,
    report_count(1),     0x03,
    report_size(1),      0x01,
    usage_page(1),      0x09,
    usage_minimum(1),     0x1,
    usage_maximum(1),     0x3,
    logical_minimum(1),    0x00,
    logical_maximum(1),    0x01,
    input(1),          0x02,
    report_count(1),     0x01,
    report_size(1),      0x05,
    input(1),          0x01,
    usage_page(1),      0x01,
    usage(1),          0x38,
    report_count(1),     0x01,
    report_size(1),      0x08,
    logical_minimum(1),    0x81,
    logical_maximum(1),    0x7f,
    input(1),          0x06,
    usage(1),          0x30,
    usage(1),          0x31,
    report_count(1),     0x02,
    report_size(1),      0x10,
    logical_minimum(2),   0x01, 0x80,
    logical_maximum(2),   0xff, 0x7f,
    input(1),          0x06,
    end_collection(0)

此描述符描述了鼠标功能。通过再一次调用“hid_add_report_descriptor”函数可将此描述符注册到接口1，会自动创建接口描述符、hid描述符和端点描述符，不需要改“f_hid.c”。

此时已实现两个接口的复合设备，其中接口0实现的是键盘设备+自定义数据接收+自定义数据收发三个功能，接口1实现的是鼠标功能。示例代码可查看“application/samples/products/usb_mouse/mouse_usb/usb_init_app.c”。

如果需要更深程度的自定义，比如需要添加out端点/需要添加空的interface，需要在kconfig里开启HID Custom（如下图示）：

开启后会编译f_hid_custom.c而不是f_hid.c, 此时需要自行修改f_hid_custom.c里的f_hid_desc_array数组来自定义config描述符(只需要改config描述符，端点初始化之类的操作都会自动完成)。注意: 自定义了几个interface就应该调用几次hid_add_report_descriptor函数，如果是空的interface，可以调用hid_add_report_descriptor并传入NULL。

例：实现4个interface，第一个interface使用1个in端点，第二个interface是个空interface，后两个interface都用2个端点，1in 1out。

若要实现4个interface需要在Kconfig更改最大报告描述符数量为4，并使能custom HID 如下图示，

然后修改配置描述符如下，4个interface需要在应用层调用4次hid_add_report_descriptor来将报告描述符注册到对应的interface上，第二个是空interface，故在第二次调用hid_add_report_descriptor时传入NULL即可，其他三次则需要传入对应功能的报告描述符。

    static struct usb_cfgdesc_s g_fhid_config_desc =
    {
        .len         = sizeof(struct usb_cfgdesc_s),
        .type        = USB_DESC_TYPE_CONFIG,
        HSETW(.totallen, 0), /* Size of all descriptors, set later */
        .ninterfaces = 0x1,  /* Number of Interfaces */
        .cfgvalue    = 0x1,  /* ID of this configuration */
        .icfg        = 0x0,  /* Index of string descriptor */
        .attr        = 0xa0, /* Bus-powered and remote wakeup */
        .mxpower     = 0x32  /* Maximum power consumption from the bus */
    };
    static struct usb_ifdesc_s g_fhid_intf_desc =
    {
        .len      = sizeof(struct usb_ifdesc_s),
        .type     = USB_DESC_TYPE_INTERFACE,
        .ifno     = 0,    /* Index number of this interface */
        .alt      = 0,    /* Index of this settings */
        .neps     = 1,    /* Number of endpoint */
        .classid  = 0x03, /* bInterfaceClass: HID */
        .subclass = 1,    /* bInterfaceSubClass : 1=BOOT, 0=no boot */
        .protocol = 0,    /* bInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
        .iif      = 0     /* Index of string descriptor */
    };
    static struct usb_hid_desc g_fhid_desc =
    {
        .bLength          = sizeof(struct usb_hid_desc),
        .bDescriptorType  = USB_DESC_TYPE_HID, /* HID type is 0x21 */
        HSETW(.bcdHID, 0x0110),                /* bcdHID: HID Class Spec release number HID 1.1 */
        .bCountryCode     = 0x00,              /* bCountryCode: Hardware target country */
        .bNumDescriptors  = 0x01,              /* bNumDescriptors: Number of HID class descriptors to follow */
        {
            {
                .bDescriptorType = 0x22,       /* bDescriptorType */
            }
        }
    };
    static struct usb_epdesc_s g_fhid_in_ep_desc =
    {
        .len      = sizeof(struct usb_epdesc_s),
        .type     = USB_DESC_TYPE_ENDPOINT,
        .addr     = USB_DIR_IN | 0x01,
        .attr     = 0x03,                       /* bmAttributes = 00000011b */
        HSETW(.mxpacketsize, HID_IN_DATA_SIZE), /* wMaxPacketSize = 64 */
        .interval = 1                           /* bInterval = 125us */
    };
    static struct usb_ifdesc_s g_fhid_intf_desc_null =
    {
        .len      = sizeof(struct usb_ifdesc_s),
        .type     = USB_DESC_TYPE_INTERFACE,
        .ifno     = 0,    /* Index number of this interface */
        .alt      = 0,    /* Index of this settings */
        .neps     = 0,    /* Number of endpoint */
        .classid  = 0,    /* bInterfaceClass: HID */
        .subclass = 0,    /* bInterfaceSubClass : 1=BOOT, 0=no boot */
        .protocol = 0,    /* bInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
        .iif      = 0     /* Index of string descriptor */
    };
    static struct usb_ifdesc_s g_fhid_intf2_desc =
    {
        .len      = sizeof(struct usb_ifdesc_s),
        .type     = USB_DESC_TYPE_INTERFACE,
        .ifno     = 0,    /* Index number of this interface */
        .alt      = 0,    /* Index of this settings */
        .neps     = 2,    /* Number of endpoint */
        .classid  = 0x03, /* bInterfaceClass: HID */
        .subclass = 1,    /* bInterfaceSubClass : 1=BOOT, 0=no boot */
        .protocol = 0,    /* bInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
        .iif      = 0     /* Index of string descriptor */
    };
    static struct usb_hid_desc g_fhid2_desc =
    {
        .bLength          = sizeof(struct usb_hid_desc),
        .bDescriptorType  = USB_DESC_TYPE_HID, /* HID type is 0x21 */
        HSETW(.bcdHID, 0x0110),                /* bcdHID: HID Class Spec release number HID 1.1 */
        .bCountryCode     = 0x00,              /* bCountryCode: Hardware target country */
        .bNumDescriptors  = 0x01,              /* bNumDescriptors: Number of HID class descriptors to follow */
        {
            {
                .bDescriptorType = 0x22,       /* bDescriptorType */
            }
        }
    };
    static struct usb_epdesc_s g_fhid_in_ep2_desc =
    {
        .len      = sizeof(struct usb_epdesc_s),
        .type     = USB_DESC_TYPE_ENDPOINT,
        .addr     = USB_DIR_IN | 0x02,
        .attr     = 0x03,                       /* bmAttributes = 00000011b */
        HSETW(.mxpacketsize, HID_IN_DATA_SIZE), /* wMaxPacketSize = 64 */
        .interval = 1                           /* bInterval = 125us */
    };
    static struct usb_epdesc_s g_fhid_out_ep_desc =
    {
        .len      = sizeof(struct usb_epdesc_s),
        .type     = USB_DESC_TYPE_ENDPOINT,
        .addr     = USB_DIR_OUT | 0x01,
        .attr     = 0x03,                        /* bmAttributes = 00000011b */
        HSETW(.mxpacketsize, HID_OUT_DATA_SIZE), /* wMaxPacketSize */
        .interval = 1                            /* bInterval = 125us */
    };
    static struct usb_ifdesc_s g_fhid_intf3_desc =
    {
        .len      = sizeof(struct usb_ifdesc_s),
        .type     = USB_DESC_TYPE_INTERFACE,
        .ifno     = 0,    /* Index number of this interface */
        .alt      = 0,    /* Index of this settings */
        .neps     = 2,    /* Number of endpoint */
        .classid  = 0x03, /* bInterfaceClass: HID */
        .subclass = 1,    /* bInterfaceSubClass : 1=BOOT, 0=no boot */
        .protocol = 0,    /* bInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
        .iif      = 0     /* Index of string descriptor */
    };
    static struct usb_hid_desc g_fhid3_desc =
    {
        .bLength          = sizeof(struct usb_hid_desc),
        .bDescriptorType  = USB_DESC_TYPE_HID, /* HID type is 0x21 */
        HSETW(.bcdHID, 0x0110),                /* bcdHID: HID Class Spec release number HID 1.1 */
        .bCountryCode     = 0x00,              /* bCountryCode: Hardware target country */
        .bNumDescriptors  = 0x01,              /* bNumDescriptors: Number of HID class descriptors to follow */
        {
            {
                .bDescriptorType = 0x22,       /* bDescriptorType */
            }
        }
    };
    static struct usb_epdesc_s g_fhid_in_ep3_desc =
    {
        .len      = sizeof(struct usb_epdesc_s),
        .type     = USB_DESC_TYPE_ENDPOINT,
        .addr     = USB_DIR_IN | 0x03,
        .attr     = 0x03,                       /* bmAttributes = 00000011b */
        HSETW(.mxpacketsize, HID_IN_DATA_SIZE), /* wMaxPacketSize = 64 */
        .interval = 1                           /* bInterval = 125us */
    };
    static struct usb_epdesc_s g_fhid_out_ep2_desc =
    {
        .len      = sizeof(struct usb_epdesc_s),
        .type     = USB_DESC_TYPE_ENDPOINT,
        .addr     = USB_DIR_OUT | 0x02,
        .attr     = 0x03,                        /* bmAttributes = 00000011b */
        HSETW(.mxpacketsize, HID_OUT_DATA_SIZE), /* wMaxPacketSize */
        .interval = 1                            /* bInterval = 125us */
    };
    /* fhid desc array includes:
     * 1. config_desc (for all report map)
     * 2. iface_desc、hid_desc、in ep_desc (report map 0)
     * 3. iface_desc、hid_desc、in ep_desc (report map 1)
     * ...
     * n+1. iface_desc、hid_desc、in ep_desc (report map n-1)
     *
     * g_fhid_desc_array is like this:
     *
     * config_desc  iface_desc(0)  hid_desc(0)  ep_desc(0) [ep_desc(0)]  iface_desc(1)  hid_desc(1)  ep_desc(1) [ep_desc(1)].. NULL
     *                  |                                     |              |                                     |
     *                  |_ _ _ _ _ _ report map 0 _ _ _ _ _ _ |              |_ _ _ _ _ _ report map 1 _ _ _ _ _ _ |
     *
     * Total Length : 2 + (4) * n
     */
    #define HID_SINGLE_PROTOCOL_DESC_NUM 4
    #define HID_DESC_ARRAY_MAX_NUM (1 + (HID_SINGLE_PROTOCOL_DESC_NUM) * HID_REPORT_MAP_NUM)
    static const uint8_t *g_fhid_desc_array[HID_DESC_ARRAY_MAX_NUM] =
    {
        (const uint8_t *)&g_fhid_config_desc,
        (const uint8_t *)&g_fhid_intf_desc,
        (const uint8_t *)&g_fhid_desc,
        (const uint8_t *)&g_fhid_in_ep_desc,
        (const uint8_t *)&g_fhid_intf_desc_null,
        (const uint8_t *)&g_fhid_intf2_desc,
        (const uint8_t *)&g_fhid2_desc,
        (const uint8_t *)&g_fhid_in_ep2_desc,
        (const uint8_t *)&g_fhid_out_ep_desc,
        (const uint8_t *)&g_fhid_intf3_desc,
        (const uint8_t *)&g_fhid3_desc,
        (const uint8_t *)&g_fhid_in_ep3_desc,
        (const uint8_t *)&g_fhid_out_ep2_desc,
        NULL,
    };

## 实现虚拟串口功能

虚拟串口设备类型是DEV_SERIAL，但通常情况下，我们使用的是HID+ACM的符合设备，即类型DEV_SER_HID。因为ACM设备并非HID设备，所以报告描述符并不需要进行修改，可以使用2.1和2.2中的报告描述符传入

hid_add_report_descriptor，再调用usbd_set_device_info(DEV_SER_HID, &str_manufacturer, &str_product, &str_serial_number, dev_id)和usb_init(DEVICE, DEV_SER_HID)即可枚举HID设备和ACM串口。

ACM串口需要新建一个线程进行数据接收，若单板端写数据发送到USB主机，则直接调用write接口；若单板端读取从USB主机发来的数据，open串口后先调用ioctl接口，然后再调用read接口，ioctl用法如下：

usb_serial_ioctl(0, CONSOLE_CMD_RD_BLOCK_SERIAL, 1);

创建一个大小4096字节的buffer来存储读取的值（虚拟串口最大支持读写4096字节数据）

usb_serial_read(0, g_usb_serial_recv_data, SERIAL_RECV_DATA_MAX_LEN);usb_serial_read的返回值是读取到的数据长度。

usb_serial_write(0, g_usb_serial_recv_data, recv_len);

示例代码可查看“application/samples/products/sle_dongle/sle_dongle_hid_serial.c”。

## 实现升级功能(DFU)

DFU功能依赖于Burntool工具，建议下载和使用尽可能新版本的Burntool。

Burntool工具可以对Usage Page设置为0xFFB1的设备进行DFU升级，如下文描述符：

    usage_page(2),      0xB1, 0xFF,
    usage(1),        0x1,
    collection(1),     0x01,
    report_id(1),      0x08,
    collection(1),     0x00,
    report_count(1),    0xc,
    report_size(1),    0x8,
    usage_minimum(1),   0x0,
    usage_maximum(1),   0xFF,
    output(1),        2,
    end_collection(0),
    end_collection(0),

在hid接收数据的线程中添加处理Burntool发送的升级消息的分支，当有升级包通过Burntool发送时，Burntool会下发frame_type为0x1e的消息：

    if (command.frame_type == 0x1e) {
        osal_printk("start dfu\n");
        usb_deinit();
        osal_msleep(USB_DEINIT_DELAY);
        usb_dfu_init();
        usb_dfu_wait_ugrade_done_and_reset();
        break;
     }

middleware/utils/usb_class/f_dfu.c中定义了名为usb_dfu_download_callback的弱函数，在开始升级时会调用该函数，需要用户自己将该函数实现以完成升级流程，具体实现可参考“application/samples/products/sle_dongle/sle_dongle_hid_serial.c”。

## 实现音频数据接口(UAC)

音频数据接口设备类型是DEV_UAC，初始化完成后需要调用uac_wait_host接口等待主机发送识别到UAC设备的信号，通常入参传入1表示WAIT_HOST_FOREVER，即在声音设置->输入中选择该设备，才能完成UAC的初始化。UAC1.0最高支持双声道192Khz 16b的音源，而LiteOS提供的UAC驱动为16Khz 16b，将想要发送的16bit音频数据填充到buffer中，使用vdt_usb_uac_send_data(buffer, buffer_len)接口发送，具体实现可参考“application/samples/products/usb_amic_vdt/vdt_usb/vdt_usb.c”。