自定义主机方案
概述
USB Vendor Class (Vendor Specific) 利用 USB 规范的开放性,允许厂商自定义的私有协议或请求,支持开发者灵活定义配置与传输类型。 Ameba 基于底层 USB 协议栈提供了主机端的 Vendor 驱动设计参考示例,与自定义的 USB 设备建立高自由度的专属数据通道。 作为 Vendor 主机,开发者可以基于此开发私有命令集与特定格式数据与 Vendor 设备进行交互,实现超越标准主机类限制的定制化控制与功能扩展。
特性
支持热插拔
自动解析描述符,自适应速度模式
支持批量/中断/等时三种传输类型
支持数据完整性校验测试和数据回传测试
与 Vendor 设备应用示例适配,具体参考 自定义设备方案
应用场景
作为具备高度可定制性的 USB 主机,Ameba 可通过 USB 接口建立主机与设备间专属的控制与通信通道,突破标准类的功能限制,实现以下典型应用,例如:
自定义协议通信:定义私有命令集与数据帧格式,用于设备与主机间的专用通信(如参数配置、故障诊断、状态查询)。
安全固件升级:利用自定义 Vendor 协议传输固件,实现私有的安全升级机制。
特殊外设控制:控制非标准 USB 设备(如特定传感器、加密狗或自定义 IO 扩展板)。
协议简介
请参考自定义设备方案的对应章节: 协议简介 。
Vendor 类没有官方统一的 USB 类标准规范。其描述符结构、命令请求及端点用途均由厂商自行定义,并需配合主机端驱动或应用程序进行解析。
描述符结构
Vendor 描述符结构可包含标准的设备描述符,配置描述符,设备质量描述符,其他速度描述符和字符串描述符。 详细描述符定制项,请参考 设备描述符定制 。
对于 Vendor 设备,描述符中有几个关键点需要注意:
设备描述符
Vendor 设备的类代码 bDeviceClass 设置为 0xFF/0x00,主机通常不会自动加载通用驱动(如 HID 或 MSC),而是去寻找匹配 VID/PID 的专用驱动程序。
Device Descriptor
| bDeviceClass: 0xFF/0x00 (0xFF: Vendor Specific Class, 0x00: Interface-based Class)
| bDeviceSubClass: 0xFF/0x00 (Vendor Specific SubClass)
| bDeviceProtocol: 0xFF/0x00 (Vendor Specific Protocol)
| idVendor (Vendor ID, e.g. 0x0BDA is for Realtek)
| idProduct (Product ID)
| ...
接口描述符
可将接口的类代码 bInterfaceClass 设置为 0xFF 表明自定义功能类。复合设备的功能区分通常发生在接口层级。
Interface Descriptor
| bInterfaceClass: 0xFF (0xFF: Vendor Specific Class)
| bInterfaceSubClass (Vendor Specific SubClass)
| bInterfaceProtocol (Vendor Specific Protocol)
| ...
例如:一个复合设备既有标准 MSC 功能,又有 Vendor 自定义功能。
Interface 0: bInterfaceClass = 0x08 (MSC)
Interface 1: bInterfaceClass = 0xFF (Vendor Specific)
字符串描述符
字符串描述符虽然不是 Vendor 规范特有,但在 Vendor 请求中,字符串描述符常用于传递序列号(iSerialNumber)或特定的固件版本信息。
例如:设备描述符中三个字段 iManufacturer/iProduct/iSerialNumber 依次设为 1, 2, 3。
Device Descriptor
├── iManufacturer : 1 ──┐
├── iProduct : 2 ──┤ (Index)
├── iSerialNumber : 3 ──┘
│
└── USB String Table
│
├── String Descriptor (Index 0: Must be read first!)
│ └── LANGID Code Array (Language ID Array)
│ └── 0x0409 (English - United States)
│
├── String Descriptor (Index 1: Pointed by `iManufacturer` index)
│ └── "Realtek Semiconductor Corp." (Unicode)
│
├── String Descriptor (Index 2: Pointed by the `iProduct` index)
│ └── "Realtek USB Vendor Device" (Unicode)
│
├── String Descriptor (Index 3: Pointed by the `iSerialNumber` index)
│ └── "00E04C000001" (Unicode)
│ ├── Funtion 1: Distinguish devices (Instance ID)
│ └── Funtion 2: Function binding of USB composite device
│
└── String Descriptor (Index 4/5/6/...: Optional/Customizable)
│ ├── "FW_Ver_1.0" (Firmware version information)
│ └── "MAC_ADDR_..." (Network Configuration)
|
│ ...
iSerialNumber:对于 Vendor 设备,强烈建议实现该字符串且保证字符串值唯一。
重要性: 对于 Vendor 设备,如果没有序列号字符串,Windows 主机可能会在每次设备插入不同的 USB 端口时,都认为是一个新设备并重新加载驱动。
唯一性: 确保每颗芯片的序列号不同,否则两台同型号设备同时插入可能会冲突。
对于 Vendor 设备,除了常规的厂商/产品名,将
iSerialNumber用于设备识别是高频且最具技术含量的用法。自定义字符串:对于 Vendor 设备,因为没有标准类的限制,厂商经常利用未使用的字符串索引来传递固件信息或配置。
场景 A:固件版本号
可以定义索引 4 为固件版本字符串(例如 "FW_Ver_1.0")。
主机量产工具不需要发复杂的 Vendor 请求,直接发标准请求
GET_DESCRIPTOR(String, Index=4) 就能读到版本号。这在工厂测试环节非常高效。
场景 B:MAC 地址 (网卡常用)
对于 USB 网卡,MAC 地址通常存在 eFuse 或 Flash 里。
有些固件实现会将 MAC 地址转换成 ASCII 字符串放在
iSerialNumber中,或者放在一个专门的自定义索引中,供上层应用读取。
控制请求
USB 协议将控制请求类型分为三类:
Standard (标准请求): 所有 USB 设备都必须支持的通用命令(如
GET_DESCRIPTOR,SET_ADDRESS)。Class (类请求): 特定设备类定义的命令(如 HID 类的
GET_REPORT,MSC 类的Bulk-Only Reset)。Vendor (厂商请求): 这是 USB-IF 留给厂商自由发挥的空间。只要主机和设备约定好,可以定义任何命令(比如:读写寄存器、下载固件、进入工厂模式等)。
所有的控制传输都始于一个 8 字节的 SETUP 包,在 SETUP 第一个字节 bmRequestType 的 bit[6:5] 决定是否为 Vendor 请求:
Bit Position |
Field Name |
Description & Values |
|---|---|---|
Bit 7 |
Direction |
|
Bit 6..5 |
Request Type |
|
Bit 4..0 |
Recipient |
|
在开发 Vendor 请求时,需特别注意以下几个关键点以确保设备正确响应主机的控制请求:
请求类型检查:设备在处理 SETUP 包时,必须解析
bmRequestType字段,确认其 Type 字段为10(即 Vendor 类型)。避免请求值冲突:为防止自定义的
bRequest值与 USB 标准请求(如 0x06: GET_DESCRIPTOR)产生混淆,建议维护一份清晰的请求值定义表,确保所有自定义请求具有唯一且明确的标识。数据方向控制:必须严格遵守
bmRequestType的 Bit 7(即 Direction 位)。若主机发送 OUT 请求但 wlength > 0 且 Bit 7 为 IN,则可能导致设备 STALL 或总线错误,因此需确保方向一致。端点使用规范:所有 Vendor 请求默认通过控制端点进行传输,因此在实现中应确保控制请求的处理逻辑正确绑定到该端点。
Vendor 请求的应用场景
Vendor 请求允许用户通过自定义的控制传输指令,可以灵活地实现对设备的专有配置、状态管理及数据流控制。
例如以下三种应用模式:
高性能流式传输 (High-Performance Streaming)
此场景适用于高带宽、持续性的数据传输应用,如高速数据采集仪等。
工作机制:
控制信道 (Vendor Request): 用于发送开启/停止控制、参数配置等低频指令。
数据信道 (BULK IN): 通过批量端点持续高效地向主机上报数据。
类驱动
驱动架构
协议栈提供了一个 Vendor Class(厂商自定义类)主机驱动的设计参考示例。 不同于 HID 或 MSC 等标准类驱动,它不绑定特定的行业协议,而是提供了一个灵活的基础框架,赋予开发者极大的自由度来定制私有的 USB 通信协议。
驱动架构采用分层设计,自上而下包含三个核心层次:
用户应用层:实现具体的业务逻辑。用户仅需通过注册回调结构体即可与驱动交互,利用封装好的接口发送或接收数据,无需关心底层 USB 协议细节。
Vendor 类驱动层:(本驱动示例核心实现)建立在 USB 主机核心栈之上,作为中间层连接底层硬件与上层应用。负责管理通道资源、维护状态机以及处理各类数据传输等。是开发者进行二次开发的主要基础。
核心驱动层: 负责底层 USB 硬件中断处理、总线枚举、传输请求 (URB) 调度及标准协议栈管理。
类驱动实现
Vendor 类驱动在系统架构中起着承上启下的作用,其核心交互逻辑主要围绕以下三个接口展开:
主机类驱动回调 API:类驱动通过注册标准的
usbh_class_driver_t结构体,实现与底层 USB Core 进行交互。面向应用的回调 API:通过应用层在初始化时注册的
usbh_vendor_cb_t,向上层应用提供异步事件通知(如连接、断开、数据接收)。面向应用的 API:提供给应用层调用的功能接口。调用后驱动会切换内部状态机的状态,开启数据传输的调度。
驱动回调机制
备注
上图仅为说明不同层级的回调函数执行的流程,并未列出所有调用场景。
类驱动回调函数
类驱动需要定义一个标准的 usbh_class_driver_t 结构体,作为统一的入口注册到 USB Core 中,是 Core 层通知 Class 层“发生了某事”的主要手段。
id_table: 支持的设备 ID 列表,核心层使用此表与插入的设备进行匹配,以决定是否加载此驱动。
attach: 设备连接并匹配成功后
detach: 设备断开时调用。
setup: 枚举完成进入类请求阶段,用于发送类特定的标准控制请求,完成设备进入数据传输状态前的必要配置。
process: 类驱动驱动就绪后的状态机处理函数。
sof: SOF 中断时调用,用于处理对时序要求严格的逻辑,主要用于 同步传输。
completed: 当通道上的传输完成时调用。
面向应用层的回调函数
Vendor 类驱动面向应用层的回调结构体 usbh_vendor_cb_t,由用户层实现。一般可选择实现:
API |
描述 |
|---|---|
init |
在类驱动初始化时被调用,用于初始化应用相关的资源 |
deinit |
在类驱动注销时被调用,用于注销应用相关的资源 |
attach |
在类驱动执行attach回调时被调用,用于应用层处理设备连接事件 |
detach |
在类驱动执行detach回调时被调用,用于应用层处理设备断开事件 |
setup |
在类驱动执行setup回调时被调用,用于指示应用层类驱动已准备好进行数据传输 |
receive |
在类驱动收到IN数据时被调用,用于应用层处理设备上报的数据 |
transmit |
在类驱动OUT数据传输完成时被调用,用于应用层获取OUT传输状态 |
加载与卸载类驱动
usbh_vendor_init() 是用于加载 Vendor 类驱动的顶层函数。
执行用户回调初始化:如果注册了用户
init回调函数,则优先调用它,这给了上层应用一个机会执行应用层特有的初始化逻辑。注册类驱动:调用
usbh_register_class(),将定义好的usbh_class_driver_t类驱动实例注册到向主机核心驱动,使主机准备好响应枚举。
示例:
typedef struct {
usbh_vendor_xfer_t bulk_in_xfer; //BULK IN pipe xfer.
// Other pipe xfer if exit.
usbh_vendor_cb_t *cb; //User callback.
usb_host_t *host; //USB host core.
usbh_vendor_state_t state; //Class Driver state.
} usbh_vendor_host_t;
/* Define Vendor host */
static usbh_vendor_host_t usbh_vendor_host;
/* Define Vendor Class Driver */
static usbh_class_driver_t usbh_vendor_driver = {
.id_table = vendor_devs,
.attach = usbh_vendor_attach,
.detach = usbh_vendor_detach,
.setup = usbh_vendor_setup,
.process = usbh_vendor_process,
.sof = usbh_vendor_sof,
};
int usbh_vendor_init(usbh_vendor_cb_t *cb)
{
usbh_vendor_host_t *vendor = &usbh_vendor_host;
/* 1. Execute User Init Callback */
if (cb != NULL) {
vendor->cb = cb;
if (cb->init != NULL) {
ret = cb->init();
if (ret != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "User init err %d\n", ret);
return ret;
}
}
}
/* 2. Register Class Driver */
usbh_register_class(&usbh_vendor_driver);
return HAL_OK;
}
usbh_vendor_deinit() 是用于用于卸载 Vendor 类驱动的顶层函数。
执行用户回调清理:如果注册了用户
deinit回调函数,则优先调用它,告知上层应用清理用户层私有资源或逻辑。重置状态机:设置驱动状态机的状态为 IDLE。
释放所有通道和资源:通过
usbh_close_pipe()强制关闭所有打开的通道,并清理所有在初始化过程中分配的资源,防止内存泄漏或通道占用。注销类驱动:调用
usbh_unregister_class(),将 Vendor 驱动从 USB 核心栈中移除。此操作后,设备将不再响应相关的 USB 事件。
示例:
int usbh_vendor_deinit(void)
{
usbh_vendor_host_t *vendor = &usbh_vendor_host;
/* 1. Execute User Deinit Callback */
if ((vendor->cb != NULL) && (vendor->cb->deinit != NULL)) {
vendor->cb->deinit();
}
/* 2. Set the state of the drive state machine to IDLE. */
vendor->state = VENDOR_STATE_IDLE;
/* 3. Close all pipelines */
usbh_vendor_deinit_all_pipe();
/* 4. Release all resources */
usbh_vendor_deinit_all_xfer();
/*5. Unregister Class Driver */
usbh_unregister_class(&usbh_vendor_driver);
return HAL_OK;
}
连接与断连处理
设备的连接与断开由 USB Core 自动检测,并调用类驱动的对应回调进行资源管理。
设备连接
当 Core 层枚举到匹配 usbh_dev_id_t 的 Vendor 设备时, usbh_vendor_attach 回调被调用。该阶段的主要职责是:
获得并解析对应的接口描述符。
根据端点描述符,通过
usbh_open_pipe()打开对应通道。初始化每个通道对应的传输管理结构体
usbh_vendor_xfer_t。调用用户
attach回调,告知应用层。
示例:
static int usbh_vendor_attach(usb_host_t *host)
{
usbh_vendor_host_t *vendor = &usbh_vendor_host;
usbh_itf_data_t *itf_data;
usbh_dev_id_t dev_id = {0,};
/* 1. Sets up device identification parameters to match a vendor class interface */
dev_id.bInterfaceClass = VENDOR_CLASS_CODE;
dev_id.bInterfaceSubClass = VENDOR_SUBCLASS_CODE;
dev_id.bInterfaceProtocol = VENDOR_PROTOCOL;
dev_id.mMatchFlags = USBH_DEV_ID_MATCH_ITF_INFO; //Core only needs to match interface information (ignore VID/PID)
/* 2. Search for interfaces that match the dev_id rule in the devices associated with the host */
itf_data = usbh_get_interface_descriptor(host, &dev_id);
if (itf_data == NULL) {
// Match failure: Although the device is connected, there is no driver supporting the Interface
RTK_LOGS(TAG, RTK_LOG_ERROR, "Get itf desc fail\n");
return HAL_ERR_PARA;
} else {
/* 3. If found, initializes the vendor host with the host pointer and sets the state to transfer */
vendor->host = host;
vendor->state = VENDOR_STATE_XFER;
/* 4. Get data in/out endpoints */
usbh_vendor_get_endpoints(host, itf_data->itf_desc_array);
/* 5. Calls the `attach` callback function if it exists */
if ((vendor->cb != NULL) && (vendor->cb->attach != NULL)) {
vendor->cb->attach();
}
return HAL_OK;
}
}
设备断开
当设备拔出时, usbh_vendor_detach 回调被调用。该阶段的主要职责是:
重置驱动状态机的状态为 IDLE。
调用
usbh_close_pipe()关闭所有已打开的通道。调用用户
detach回调,告知应用层。
示例:
static int usbh_vendor_detach(usb_host_t *host)
{
UNUSED(host);
usbh_vendor_host_t *vendor = &usbh_vendor_host;
/* 1. Reset the state of the driver state machine to IDLE */
vendor->state = VENDOR_STATE_IDLE;
/* 2. Close all pipes in turn */
usbh_vendor_deinit_all_pipe();
/* 3. Calls the `detach` callback function if it exist */
if ((vendor->cb != NULL) && (vendor->cb->detach != NULL)) {
vendor->cb->detach();
}
return HAL_OK;
}
设备和驱动匹配机制
在 USB 主机驱动中, attach 回调函数与 usbh_dev_id_t 结构体共同协作,完成了从“通用设备连接”到“特定接口绑定”的关键过程。
匹配过程如下:
定义匹配规则:过配置
usbh_dev_id_t结构体,指定目标设备特征(如类代码、协议等)。Match Flags: 使用
mMatchFlags精确控制匹配策略(如仅匹配 VID/PID,或匹配特定 Class/SubClass)。灵活性: 精确控制是匹配整个设备还是设备中的某个特定接口(这对复合设备尤为重要)。
开发者根据实际连接的 Vendor 设备进行适配。
搜索匹配:在
attach回调中,调用核心 APIusbh_get_interface_descriptor()。USB Core 会遍历当前连接设备的所有接口描述符,与传入的
usbh_dev_id_t规则进行比对,若匹配成功,则返回对应的接口描述符指针。
类驱动状态机
usbh_vendor_process 回调函数是主机端 Vendor 类的核心状态机处理函数。与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。
USB 主机类驱动采用状态机来管理数据传输处理,驱动由 API 调用和底层中断反馈更新状态。
状态机一般包含三种核心状态:
VENDOR_STATE_IDLE(空闲状态):系统初始化或连接断开后的待机状态,等待新的传输请求或事件触发。
VENDOR_STATE_XFER(数据传输状态):核心数据处理状态,根据事件消息中的 通道号 (pipe_num) 分发处理相应的传输事件。
VENDOR_STATE_ERROR(错误处理状态):异常处理状态,尝试通过标准请求 (Clear Feature) 实现错误恢复。
示例:
switch (vendor->state) {
case VENDOR_STATE_IDLE:
break
case VENDOR_STATE_XFER:
/* Distribute and process according to the pipe number */
if (event.msg.pipe_num == vendor->bulk_in_xfer.pipe.pipe_num) {
usbh_vendor_bulk_process_rx(host);
} else if (event.msg.pipe_num == vendor->isoc_out_xfer.pipe.pipe_num) {
usbh_vendor_isoc_process_tx(host);
}
/* Deal with other pipe */
break;
case VENDOR_STATE_ERROR:
/* Try to recover */
if (usbh_ctrl_clear_feature(host, 0x00U) == HAL_OK) {
vendor->state = VENDOR_STATE_IDLE;// Restoration successful, reset to IDLE
}
break;
}
数据传输处理
本小节详细介绍了 Vendor 类驱动中的数据传输处理流程。主机端的传输主要分为 TRX API -> Process -> Callback 三个阶段:
传输请求发起:由应用层调用 API 主动发起 OUT(发送)或 IN(接收)传输请求。
状态机管理:驱动程序通过维护传输状态机,实现与设备端的数据交互。
回调通知:传输完成或出错时,通过
usbh_vendor_cb_t回调通知应用层。该回调机制实现了应用层与底层驱动的解耦,应用层无需轮询等待。
启动发送: 应用层调用 TX API ,如
usbh_vendor_bulk_transmit()。API 内部实现:
设置通道的传输缓存和传输长度。
设置通道为传输状态
USBH_EP_XFER_START。调用
usbh_notify_class_state_change()通知核心状态发生变化,触发状态机调度处理。
核心处理:
usbh_vendor_process回调中检测到事件,根据通道号分发给对应的处理函数。底层调度: 调用
usbh_transfer_process()开启底层传输和处理传输结果。完成通知: 调用用户
transmit回调函数通知上层发送结果。
启动接收: 应用层调用 RX API ,如
usbh_vendor_bulk_receive()。API 内部实现:
设置通道的传输缓存和传输长度。
设置通道为传输状态
USBH_EP_XFER_START。调用
usbh_notify_class_state_change()通知核心状态发生变化,触发状态机调度处理。
核心处理:
usbh_vendor_process回调中检测到事件,根据通道号分发给对应的处理函数。底层调度: 调用
usbh_transfer_process()开启底层传输和处理传输结果。完成通知:
若收到数据:调用:cpp:func:usbh_get_last_transfer_size 获取实际接收长度。
调用用户
receive回调函数将数据上传给应用层。
备注
BULK IN ZLP 处理
如果收到零长度包,同样触发
receive回调,所以需要应用层对 ZLP 的情况进行判断处理
备注
USB DMA 要求数据缓冲区地址和 Cache line 对齐,所以应用层传下的收发缓存区地址必须地址对齐。
特殊处理逻辑
BULK ZLP 处理
在批量传输中,如果数据长度正好是 MPS(最大包长)的整数倍,驱动会自动标记
trx_zlp以便在数据后自动接收/发送一个 ZLP 结束传输。示例:
if ((pipe->xfer_len > 0) && ((pipe->xfer_len % pipe->ep_mps) == 0) && (pipe->ep_type == USB_CH_EP_TYPE_BULK)) { //ZLP pipe->trx_zlp = 1; }
同步传输和帧同步
同步传输对时间敏感,利用 SOF 中断处理同步传输的时序控制。
SOF 回调:
usbh_vendor_sof回调函数在每帧起始触发,用于追踪当前帧号。发送调度: 确保数据包严格按照帧间隔在正确的(微)帧内传输。
发送处理函数中判断后续仍有传输时,会将通道状态置为
USBH_EP_XFER_WAIT_SOF。在 SOF 回调中检查帧间隔,满足条件时调用
usbh_notify_class_state_change()触发核心调度进行实际的数据发送。
示例:
static int usbh_vendor_sof(usb_host_t *host) { usbh_vendor_host_t *vendor = &usbh_vendor_host; /* 1.Obtain the current frame number for ISOC scheduling */ int cur_frame = usbh_get_current_frame_number(host); /* 2. Waiting for the correct SOF interval to start next transfer - if cur_frame - last frame_num >= interval, means we should trigger a xfer ASAP. - if xfer_state = USBH_EP_XFER_WAIT_SOF, it means that last xfer has been done, so in sof intr, we should check whether the next frame will be the xfer frame */ if ((usbh_get_elapsed_frame_cnt(host, pipe->frame_num) >= pipe->ep_interval) || ((pipe->xfer_state == USBH_EP_XFER_WAIT_SOF) && (cur_frame - out_xfer->cur_frame) % pipe->ep_interval == 0)){ //3. Set next transfer parameters: e.g. buffer and length //4. Start next transfer usbh_notify_class_state_change(host, pipe->pipe_num); } return HAL_OK; }
通道配置
Vendor 主机驱动在设备枚举阶段 (usbh_vendor_attach 回调函数) 中解析配置描述符,根据接口描述符自动查找并申请相应的传输资源,实现完整的数据与控制通道。
该示例中的通道配置如下:
数量 |
描述 |
|---|---|
2 |
默认控制传输 0 |
1 |
批量 IN 传输 |
1 |
批量 OUT 传输 |
1 |
中断 IN 传输 |
1 |
中断 OUT 传输 |
1 |
同步 IN 传输 |
1 |
同步 OUT 传输 |
API 说明
应用示例
应用设计
本节详细介绍 Vendor 应用的完整开发流程,涵盖驱动加载、热插拔处理、如何建立传输以及如何处理数据等核心环节。 但其传输的数据内容(如 Loopback 测试数据)是无具体业务含义的。开发者可以基于此框架:
定义私有命令集:替换示例中的测试数据,实现具体的私有协议解析逻辑。
调整资源配置:根据实际业务吞吐量,调整 FIFO 大小和缓冲区大小。
加载驱动
使用 Vendor 驱动前,需定义配置结构体并注册回调函数,随后调用初始化接口加载 USB 主机核心驱动及 Vendor 类驱动。
步骤说明:
硬件配置:配置 USB 速度模式(High Speed/Full Speed)及中断优先级等。
回调注册:定义用户回调结构体
usbh_vendor_cb_t,和usbh_user_cb_t挂载各个阶段的处理函数。加载核心驱动:调用
usbh_init()加载 USB 核心驱动。加载类驱动:调用
usbh_vendor_init()加载 Vendor 类驱动。
示例:
static usbh_config_t usbh_cfg = {
.speed = USB_SPEED_HIGH,
.ext_intr_enable = USBH_SOF_INTR,
.isr_priority = INT_PRI_MIDDLE,
.main_task_priority = 3U,
.tick_source = USBH_SOF_TICK,
};
static usbh_vendor_cb_t vendor_usr_cb = {
.attach = vendor_cb_attach,
.detach = vendor_cb_detach,
.setup = vendor_cb_setup,
.transmit = vendor_cb_transmit,
.receive = vendor_cb_receive,
};
static usbh_user_cb_t usbh_usr_cb = {
.process = vendor_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_vendor_init(&vendor_usr_cb);
if (ret != HAL_OK) {
/* If class driver init fails, clean up the core driver */
usbh_deinit();
return;
}
热插拔事件处理
作为主机,系统必须能够健壮地处理 USB 设备的动态插入与移除,SDK 默认支持热插拔机制。
处理逻辑:
设备插入 (Attach):USB 核心检测到设备,重新执行枚举和驱动加载流程。
设备拔出 (Detach):触发回调释放信号量,应用线程捕获后执行反初始化,并释放堆内存。
示例:
static rtos_sema_t vendor_detach_sema;
/* USB detach callback */
static usbh_vendor_cb_t vendor_usr_cb = {
.detach = vendor_cb_detach,
};
/* Callback executed in ISR context */
static int vendor_cb_detach(void)
{
RTK_LOGS(TAG, RTK_LOG_INFO, "DETACH\n");
rtos_sema_give(vendor_detach_sema);
return HAL_OK;
}
/* Thread handling the state machine */
static void vendor_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
if (rtos_sema_take(vendor_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_vendor_deinit();
/* 2. De-initialize USB core */
usbh_deinit();
rtos_time_delay_ms(10);
RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%08x\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) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init USBH fail: %d\n", ret);
break;
}
ret = usbh_vendor_init(&vendor_usr_cb);
if (ret != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init vendor fail: %d\n", ret);
usbh_deinit();
break;
}
}
}
rtos_task_delete(NULL);
}
数据收发处理
当 Vendor 主机枚举成功后,可以调用 API 开启数据收发。 以下流程适用于 BULK、INTR 和 ISOC 类型的传输,此处以 BULK 传输为例。
static __IO int vendor_is_ready = 0;
static rtos_sema_t vendor_bulk_send_sema;
static rtos_sema_t vendor_bulk_receive_sema;
static u8 vendor_bulk_loopback_tx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE] __attribute__((aligned(CACHE_LINE_SIZE)));
static u8 vendor_bulk_loopback_rx_buf[USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE] __attribute__((aligned(CACHE_LINE_SIZE)));
static usbh_vendor_cb_t vendor_usr_cb = {
.setup = vendor_cb_setup,
.transmit = vendor_cb_transmit,
.receive = vendor_cb_receive,
};
static int vendor_cb_setup(void)
{
vendor_is_ready = 1;
return HAL_OK;
}
/**
* @brief USB Vendor transmit callback function
* @param ep_type transmission type (BULK, INTERRUPT, ISOC)
* @return HAL status
*/
static int vendor_cb_transmit(u8 ep_type)
{
//Release the different semaphore according to different transmission, e.g. transmission type
switch (ep_type) {
case USB_CH_EP_TYPE_BULK:
rtos_sema_give(vendor_bulk_send_sema);
break;
//Other transmission
default:
break;
}
return HAL_OK;
}
/**
* @brief USB Vendor receive callback function
* @param ep_type transmission type (BULK, INTERRUPT, ISOC)
* @param buf Received data buffer
* @param len Length of received data
* @param status Transfer status
* @return HAL status
*/
static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
// Get endpoint maximum packet size for each type
u16 vendor_bulk_in_mps = usbh_vendor_get_bulk_ep_mps();
switch (ep_type) {
case USB_CH_EP_TYPE_BULK:
// 1. Check if transfer transfer is complete successfully
// 2. Reset total length and signal completion
rtos_sema_give(vendor_bulk_receive_sema);
} else {
RTK_LOGS(TAG, RTK_LOG_ERROR, "%d RX fail: %d\n", ep_type, status);
}
break;
}
return HAL_OK;
}
/*---------------- BULK TRX Test --------------------*/
/*1. Wait for device to be ready for data transfer*/
while (1) {
if (vendor_is_ready) { /* Check if device is ready */
rtos_time_delay_ms(10);
break;
}
}
/* 2. BULK TX test */
/* Initiate transfer with TX configuration: transfer length and times */
usbh_vendor_bulk_transmit(vendor_bulk_loopback_tx_buf, USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE, USBH_VENDOR_BULK_LOOPBACK_CNT);
/* 3. Wait for transfer completion semaphore */
if (rtos_sema_take(vendor_bulk_send_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
// Success indicates transfer completion
}
/* 4. BULK RX test */
/* Initiate transfer with RX configuration: transfer length and times */
usbh_vendor_bulk_receive(vendor_bulk_loopback_rx_buf, USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE, USBH_VENDOR_BULK_LOOPBACK_CNT);
/* 5. Wait for transfer completion semaphore */
if (rtos_sema_take(vendor_bulk_receive_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
// Success indicates transfer completion
}
三种不同传输类型在接收成功后 receive 回调中的处理方式不同:
处理逻辑:
批量传输接收后累积数据直到满足特定条件后才释放信号量。 这主要是因为协议规定当批量传输长度为 MPS 整数倍时,需要追加一个 ZLP 零长度包标识传输结束。 用户层需要区别这种情况,所以需要等待完整的数据包序列或特定的结束条件(如 ZLP 零长度包、短包或缓冲区满)才能确认一次完整的传输完成。 这种机制确保了数据的完整性和正确性,避免了在数据未完全接收时就进行后续处理。
示例:
static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
/* 1. Get endpoint maximum packet size of BULK IN endpoint */
u16 vendor_bulk_in_mps = usbh_vendor_get_bulk_ep_mps();
switch (ep_type) {
case USB_CH_EP_TYPE_BULK:
/* 2. Check if transfer was successful */
if (status == HAL_OK) {
/* 3 Update total received length */
vendor_bulk_total_rx_len += len;
/* 4. Determine if transfer is complete based on: Zero-length packet (ZLP)/Short packet (not multiple of MPS)/ Buffer full condition */
if ((len == 0) || (len % vendor_bulk_in_mps)
|| ((len % vendor_bulk_in_mps == 0) && (len < USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE))
|| (vendor_bulk_total_rx_len > USBH_VENDOR_BULK_LOOPBACK_BUF_SIZE)) {
/* 5. Reset total length and signal completion */
vendor_bulk_total_rx_len = 0;
rtos_sema_give(vendor_bulk_receive_sema);
}
} else {
RTK_LOGS(TAG, RTK_LOG_ERROR, "%d RX fail: %d\n", ep_type, status);
}
break;
}
return HAL_OK;
}
处理逻辑:
中断传输的处理相对简单,主要基于短包检测。当接收到的数据长度小于或等于 MPS 时,通常表示一个完整的传输周期结束。 这种设计符合中断传输的特点,即传输数据量较小且具有周期性,通过短包检测可以及时识别传输完成。
示例:
static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
/* 1. Get endpoint maximum packet size of Interrupt IN endpoint */
u16 vendor_intr_in_mps = usbh_vendor_get_intr_ep_mps();
switch (ep_type) {
case USB_CH_EP_TYPE_INTR:
/* 2. Check if transfer was successful */
if (status == HAL_OK) {
/* 3. Update total received length */
vendor_intr_total_rx_len += len;
/* 4. Determine if transfer is complete based on: Short packet (less than MPS) with data received */
if (((len < vendor_intr_in_mps) && (vendor_intr_total_rx_len > 0))
|| (vendor_intr_total_rx_len >= USBH_VENDOR_INTR_LOOPBACK_BUF_SIZE)) { //
/*5. Reset total length and signal completion */
vendor_intr_total_rx_len = 0;
rtos_sema_give(vendor_intr_receive_sema);
}
} else {
// Log error if transfer failed
RTK_LOGS(TAG, RTK_LOG_ERROR, "%d RX fail: %d\n", ep_type, status);
}
break;
}
return HAL_OK;
}
处理逻辑:
同步传输采用立即释放信号量的方式,因为同步传输通常用于实时数据传输,如音频或视频流,其传输的及时性比数据完整性更重要。 每次接收到数据包就立即通知上层处理,确保实时性要求得到满足。
示例:
static int vendor_cb_receive(u8 ep_type, u8 *buf, u32 len, int status)
{
switch (ep_type) {
case USB_CH_EP_TYPE_ISOC:
// For isochronous transfers, immediately signal completion
rtos_sema_give(vendor_isoc_rxdone_sema);
break;
}
return HAL_OK;
}
备注
分配的用于收发的缓存必须要 Cache-line 地址对齐。
完整的数据收发逻辑请参考 SDK 示例代码:
{SDK}/example/usb/usbh_vendor/example_usbh_vendor.c。
卸载驱动
在不再需要 USB 功能或系统关机时,按加载的反序释放资源。
示例:
/* Deinitialize Vendor class driver. */
usbh_vendor_deinit();
/* Deinitialize USB host core driver. */
usbh_deinit();
运行方式
本节介绍了一个完整的 Vendor 主机示例,该示例演示了如何通过 Vendor 协议栈实现与设备之间的自定义数据双向通信。
该示例代码路径: {SDK}/example/usb/usbh_vendor,可为开发者设计自定义 Vendor 产品提供完整的参考方案。
配置与编译
编译与烧录
在 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_vendor -p
Menuconfig 配置确认
若编译失败,请执行
ameba.py menuconfig,确认已选择USBH VENDOR。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (host) ---> [*] Vendor
结果验证
启动设备
重启开发板,观察串口日志,应显示如下启动信息:
[VND-I] USBH vendor demo start
连接主机
使用 USB 线缆将开发板连接特定厂商的 USB 设备(例如,另一块运行本 USB 协议栈自定义设备方案的开发板,详见 自定义设备方案 )
数据通信测试
Ameba 开发板将识别 Vendor 设备并自动进行收发测试。
``` [VND-I] ISOC test start, times:100, size: 1024 [VEN-I] ISOC OUT test finish 100/100: [VEN-I] 0 1 2 3 4 5 6 7 8 9 ``` [VEN-I] 90 91 92 93 94 95 96 97 98 99 [VEN-I] ISOC IN test finish 100/100: [VEN-I] 0 1 2 3 4 5 6 7 8 9 ``` [VEN-I] 90 91 92 93 94 95 96 97 98 99 [VND-I] ISOC test PASS [VND-I] BULK loopback test start, times:100, size: 512 [VND-I] INTR loopback test start, times:100, size: 1024 [VND-I] BULK loopback test PASS 100/100 [VND-I] INTR loopback test PASS 100/100 [VND-I] USBH vendor demo stop