USB 设备支持

概述

USB 设备支持由 USB 设备控制器(UDC)驱动(USB 设备控制器(UDC)驱动 API)和 USB 设备协议栈(USB 设备协议栈 API)组成。USB 设备控制器(UDC)驱动 API 为 USB 设备控制器提供通用且与厂商无关的接口;尽管各层之间存在明确的分离,但 USB 设备控制器(UDC)驱动 API 的目的只是为 Zephyr 的 USB 设备协议栈服务。

设备协议栈支持多个设备控制器,这意味着如果 SoC 具有多个控制器,它们可以同时使用。支持全速和高速设备控制器。它还支持在运行时向某个配置注册多个功能或类实例,或稍后更改配置。它内置对多个 USB 类的支持,并提供用于实现自定义 USB 功能的 API。

示例

如何配置和启用 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 类

音频类 2(UAC2)设备 API

uac2_n

USB CDC ACM 类

通用异步收发器(UART)

cdc_acm_n

USB CDC ECM 类

以太网设备

cdc_ecm_n

USB 大容量存储类(MSC)

USB 大容量存储类(MSC)设备 API

msc_n

USB 人机接口设备(HID)

HID 设备 API

hid_n

蓝牙 HCI USB 传输层

HCI RAW 通道

bt_hci_n

USB 视频类(UVC)

视频设备

uvc_n