USB 设备支持
概述
USB 设备支持由 USB 设备控制器(UDC)驱动(USB 设备控制器(UDC)驱动 API)和 USB 设备协议栈(USB 设备协议栈 API)组成。USB 设备控制器(UDC)驱动 API 为 USB 设备控制器提供通用且与厂商无关的接口;尽管各层之间存在明确的分离,但 USB 设备控制器(UDC)驱动 API 的目的只是为 Zephyr 的 USB 设备协议栈服务。
设备协议栈支持多个设备控制器,这意味着如果 SoC 具有多个控制器,它们可以同时使用。支持全速和高速设备控制器。它还支持在运行时向某个配置注册多个功能或类实例,或稍后更改配置。它内置对多个 USB 类的支持,并提供用于实现自定义 USB 功能的 API。
示例
zperf: Network Traffic Generator 要构建设备支持示例,请设置配置 overlay 文件
-DEXTRA_CONF_FILE=overlay-usbd.conf和设备树 overlay 文件-DDTC_OVERLAY_FILE=usbd_cdc_ecm.overlay,可以直接设置,也可以通过west设置。
如何配置和启用 USB 设备支持
对于 Zephyr 项目仓库中的 USB 设备支持示例,我们有一个用于实例化、配置和初始化的公共文件:samples/subsys/usb/common/sample_usbd_init.c。下面使用该文件中的代码片段作为示例。USB 示例中使用的、以 SAMPLE_USBD_ 为前缀的 USB Samples Kconfig 选项具有 Zephyr 项目特定的默认值,且其作用范围仅限于项目示例。在以下示例中,你需要将这些 Kconfig 选项和其他默认值替换为适合你的应用或硬件的值。
USB 设备协议栈需要一个上下文结构来管理其属性和运行时数据。定义设备上下文的首选方式是使用 USBD_DEVICE_DEFINE 宏。这会创建一个具有给定名称的静态 usbd_context 变量。可以实例化任意数量的上下文。一个 USB 控制器设备可以分配给多个上下文,但同一时间只能初始化并使用一个上下文。应用不得直接访问或操作上下文属性。
/*
* Instantiate a context named sample_usbd using the default USB device
* controller, the Zephyr project vendor ID, and the sample product ID.
* Zephyr project vendor ID must not be used outside of Zephyr samples.
*/
USBD_DEVICE_DEFINE(sample_usbd,
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
CONFIG_SAMPLE_USBD_VID, CONFIG_SAMPLE_USBD_PID);
你的 USB 设备可能有制造商、产品和序列号字符串描述符。要实例化这些字符串描述符,应用应使用相应的 USBD_DESC_MANUFACTURER_DEFINE、USBD_DESC_PRODUCT_DEFINE 和 USBD_DESC_SERIAL_NUMBER_DEFINE 宏。字符串描述符还需要使用 USBD_DESC_LANG_DEFINE 宏实例化一次语言描述符。
USBD_DESC_LANG_DEFINE(sample_lang);
USBD_DESC_MANUFACTURER_DEFINE(sample_mfr, CONFIG_SAMPLE_USBD_MANUFACTURER);
USBD_DESC_PRODUCT_DEFINE(sample_product, CONFIG_SAMPLE_USBD_PRODUCT);
IF_ENABLED(CONFIG_HWINFO, (USBD_DESC_SERIAL_NUMBER_DEFINE(sample_sn)));
必须在运行时将字符串描述符添加到设备上下文,然后才能使用 usbd_add_descriptor() 初始化 USB 设备。
err = usbd_add_descriptor(&sample_usbd, &sample_lang);
if (err) {
LOG_ERR("Failed to initialize language descriptor (%d)", err);
return NULL;
}
err = usbd_add_descriptor(&sample_usbd, &sample_mfr);
if (err) {
LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err);
return NULL;
}
err = usbd_add_descriptor(&sample_usbd, &sample_product);
if (err) {
LOG_ERR("Failed to initialize product descriptor (%d)", err);
return NULL;
}
IF_ENABLED(CONFIG_HWINFO, (
err = usbd_add_descriptor(&sample_usbd, &sample_sn);
))
if (err) {
LOG_ERR("Failed to initialize SN descriptor (%d)", err);
return NULL;
}
USB 设备针对每种支持的速度至少需要一个配置实例。应用应使用 USBD_CONFIGURATION_DEFINE 实例化一个配置。之后,USB 设备功能会分配到配置中。
static const uint8_t attributes = (IS_ENABLED(CONFIG_SAMPLE_USBD_SELF_POWERED) ?
USB_SCD_SELF_POWERED : 0) |
(IS_ENABLED(CONFIG_SAMPLE_USBD_REMOTE_WAKEUP) ?
USB_SCD_REMOTE_WAKEUP : 0);
/* Full speed configuration */
USBD_CONFIGURATION_DEFINE(sample_fs_config,
attributes,
CONFIG_SAMPLE_USBD_MAX_POWER, &fs_cfg_desc);
/* High speed configuration */
USBD_CONFIGURATION_DEFINE(sample_hs_config,
attributes,
CONFIG_SAMPLE_USBD_MAX_POWER, &hs_cfg_desc);
在使用 usbd_add_configuration() 初始化 USB 设备之前,必须先在运行时将特定速度的每个配置实例添加到设备上下文。请注意 USBD_SPEED_FS 和 USBD_SPEED_HS。第一个全速或高速配置的 bConfigurationValue 为 1,后续配置依次递增。
err = usbd_add_configuration(&sample_usbd, USBD_SPEED_FS,
&sample_fs_config);
if (err) {
LOG_ERR("Failed to add Full-Speed configuration");
return NULL;
}
尽管我们已经做了很多工作,但这个 USB 设备还没有任何功能。一个设备可以具有多个配置,在不同速度下包含不同的功能集。可以使用 usbd_register_class() 在 USB 设备初始化之前向其注册功能或类。所需配置通过 USBD_SPEED_FS 或 USBD_SPEED_HS 以及配置编号来指定。对于简单情况,可以使用 usbd_register_all_classes() 注册所有可用实例。
err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_FS, 1, blocklist);
if (err) {
LOG_ERR("Failed to add register classes");
return NULL;
}
准备工作的最后一步是使用 usbd_init() 初始化设备。之后,设备配置便无法更改。可以使用 usbd_shutdown() 取消初始化设备,所有实例都可以重用,但必须重复之前的步骤。因此,可以先关闭设备、注册另一种类型的配置或功能,然后再次初始化。在 USB 控制器层面,usbd_init() 只执行检测 VBUS 变化所必需的操作。某些控制器类型只有在存在 VBUS 信号时才能执行下一步。
功能或类的实现可能需要自己特定的配置步骤,这些步骤应在初始化 USB 设备之前执行。
err = usbd_init(&sample_usbd);
if (err) {
LOG_ERR("Failed to initialize device support");
return NULL;
}
启用 USB 设备的最后一步是调用 usbd_enable();之后,如果 USB 设备已连接到 USB 主机控制器,主机就可以开始枚举该设备。应用可以使用 usbd_disable() 禁用 USB 设备。
ret = usbd_enable(sample_usbd);
if (ret) {
LOG_ERR("Failed to enable device support");
return ret;
}
USB 消息通知
应用可以使用 usbd_msg_register_cb() 注册回调,以接收来自 USB 设备支持子系统的消息通知。这些消息大多与通用设备状态变化有关,另有少数是来自 USB CDC ACM 实现的特定类型。
err = usbd_msg_register_cb(&sample_usbd, msg_cb);
if (err) {
LOG_ERR("Failed to register message callback");
return NULL;
}
辅助函数 usbd_msg_type_string() 可用于将 usbd_msg_type 转换为便于日志记录的可读形式。
如果控制器支持 VBUS 状态变化检测,那么电池供电的应用可能只想在连接到主机时才启用 USB 设备。通用应用应使用 usbd_can_detect_vbus() 检查是否具备此能力。
static void msg_cb(struct usbd_context *const usbd_ctx,
const struct usbd_msg *const msg)
{
LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type));
if (msg->type == USBD_MSG_CONFIGURATION) {
LOG_INF("\tConfiguration value %d", msg->status);
}
if (usbd_can_detect_vbus(usbd_ctx)) {
if (msg->type == USBD_MSG_VBUS_READY) {
if (usbd_enable(usbd_ctx)) {
LOG_ERR("Failed to enable device support");
}
}
if (msg->type == USBD_MSG_VBUS_REMOVED) {
if (usbd_disable(usbd_ctx)) {
LOG_ERR("Failed to disable device support");
}
}
}
}
内置功能
USB 设备协议栈具有内置 USB 功能。有些功能可以通过专用 API 直接在用户应用中使用,例如 HID 或音频类设备;另一些则使用通用的 Zephyr RTOS 驱动 API,例如 MSC 和 CDC 类实现。标识字符串 用于标识类或功能实例(n),并作为参数传递给 usbd_register_class()。
类或功能 |
用户 API(如有) |
标识字符串 |
|---|---|---|
USB Audio 2 类 |
|
|
USB CDC ACM 类 |
|
|
USB CDC ECM 类 |
以太网设备 |
|
USB 大容量存储类(MSC) |
|
|
USB 人机接口设备(HID) |
|
|
蓝牙 HCI USB 传输层 |
|
|
USB 视频类(UVC) |
视频设备 |
|