USB 设备模式允许 MCU 作为 USB 设备与主机通信,常见应用包括 CDC 虚拟串口、HID 设备、DFU 升级等。
当前 Zephyr 主线样例多数已经切到新的 USB device stack(CONFIG_USB_DEVICE_STACK_NEXT=y,运行时调用 usbd_enable())。 本文中的旧 USB device stack 配置仍可用于理解 legacy 方案,但不应当作为当前主线写法。
┌──────────────┐ ┌──────────────┐
│ Host │ │ Device │
│ (PC) │◄───────►│ (MCU) │
└──────────────┘ └──────────────┘
| 类型 | 特点 | 用途 |
|---|---|---|
| 控制传输 | 可靠、慢 | 配置、命令 |
| 中断传输 | 可靠、快 | 键盘、鼠标 |
| 批量传输 | 不可靠、快 | 存储、CDC |
| 等时传输 | 不可靠、快 | 音频、视频 |
# prj.conf
CONFIG_USB_DEVICE_STACK_NEXT=y
# 下面这些 class / legacy 配置是否可用,取决于具体 sample 和板级支持
# CONFIG_USB_DEVICE_STACK=y
# CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y
# CONFIG_USB_CDC_ACM=y
# HID
# CONFIG_USB_HID=y
# CONFIG_USB_HID_BOOT_PROTOCOL=y
# DFU
# CONFIG_USB_DFU=y
# 描述符
CONFIG_USB_DEVICE_PRODUCT="Zephyr Device"
CONFIG_USB_DEVICE_MANUFACTURER="Zephyr"
CONFIG_USB_DEVICE_VID=0x2FE3
CONFIG_USB_DEVICE_PID=0x0100
#include <zephyr/usb/usb_device.h> // legacy
#include <zephyr/usb/usbd.h> // current stack
/*
* current stack 的典型流程:
* 1. 创建 / 初始化 usbd_context
* 2. 注册 class / configuration
* 3. 调用 usbd_enable()
*
* legacy stack 则更常见 usb_enable(NULL) 这一路线。
*/
如果要直接参考当前 sample,建议优先看 zephyr/samples/subsys/usb/cdc_acm/。 如果目标板没有 USB Device Controller 节点,就不能直接套用 USB CDC / HID / DFU 示例。
#include <zephyr/drivers/uart.h>
/*
* 对 legacy cdc_acm sample,应用侧通常把 CDC ACM 暴露成一个 UART 设备,
* 然后使用 uart_irq_* / uart_poll_* API 进行收发。
* 当前主线 sample 细节以 zephyr/samples/subsys/usb/cdc_acm/ 为准。
*/
/*
* legacy 方案里更常见的是:
* 1. 通过 DEVICE_DT_GET() 获取 CDC ACM 对应的 UART 设备
* 2. 用 uart_fifo_fill() / uart_tx() 发送
* 3. 用 uart_irq_rx_ready() / uart_fifo_read() 接收
*/
/*
* 如果要找完整、当前可跑的参考实现,直接看:
* zephyr/samples/subsys/usb/cdc_acm/
*
* 这类 sample 一般会:
* 1. 初始化 USB 栈
* 2. 等待主机 DTR / 线路状态
* 3. 将 CDC ACM 设备当作 UART 使用
* 4. 通过 UART API 完成 echo / log / shell
*/
#include <zephyr/usb/class/hid.h>
/* HID 描述符 - 键盘 */
static const uint8_t hid_kb_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Keyboard)
0x19, 0xE0, // Usage Minimum (Left Control)
0x29, 0xE7, // Usage Maximum (Right GUI)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Constant)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Keyboard)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x81, 0x00, // Input (Data, Array, Absolute)
0xC0 // End Collection
};
/* 键盘报告结构 */
struct hid_kb_report {
uint8_t modifiers; // Modifier keys
uint8_t reserved; // Reserved
uint8_t keys[6]; // Key codes
};
/* 发送按键 */
int hid_keyboard_send(uint8_t key)
{
struct hid_kb_report report = {
.modifiers = 0,
.reserved = 0,
.keys = { key, 0, 0, 0, 0, 0 },
};
return hid_device_send_report(&report, sizeof(report));
}
/* 发送修饰键 + 按键 */
int hid_keyboard_send_mod(uint8_t mod, uint8_t key)
{
struct hid_kb_report report = {
.modifiers = mod,
.reserved = 0,
.keys = { key, 0, 0, 0, 0, 0 },
};
return hid_device_send_report(&report, sizeof(report));
}
/* 释放所有按键 */
int hid_keyboard_release(void)
{
struct hid_kb_report report = { 0 };
return hid_device_send_report(&report, sizeof(report));
}
/* 鼠标报告 */
struct hid_mouse_report {
uint8_t buttons; // Button state
int8_t x; // X movement
int8_t y; // Y movement
int8_t wheel; // Wheel
};
/* 发送鼠标移动 */
int hid_mouse_move(int8_t x, int8_t y)
{
struct hid_mouse_report report = {
.buttons = 0,
.x = x,
.y = y,
.wheel = 0,
};
return hid_device_send_report(&report, sizeof(report));
}
/* 发送鼠标点击 */
int hid_mouse_click(uint8_t button)
{
struct hid_mouse_report report = {
.buttons = button,
.x = 0,
.y = 0,
.wheel = 0,
};
int err = hid_device_send_report(&report, sizeof(report));
if (err < 0) return err;
k_msleep(10);
report.buttons = 0;
return hid_device_send_report(&report, sizeof(report));
}
#include <zephyr/usb/class/dfu.h>
/* DFU 功能描述 */
static const struct dfu_descriptor dfu_desc = {
.func = DFU_RUNTIME,
.attributes = DFU_ATTR_CAN_DNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT,
.detach_timeout = 1000,
.transfer_size = 4096,
};
/*
* DFU class 在 current stack 下有独立的 usbd_dfu 文档和 sample 组织方式。
* 这里更适合理解 DFU 的状态机概念,不建议把自定义 dfu_state/dfu_ops
* 直接当成 Zephyr 通用接口模板。
*/
#include <zephyr/usb/usbd.h>
/* 复合设备描述符 */
USBD_CONFIGURATION_DESCR_DEFINE(sample) struct usbd_configuration_config0 = {
.bConfigurationValue = 1,
.bmAttributes = USB_ATTR_DEFAULT,
.MaxPower = 100,
};
/* CDC + HID 复合设备 */
static int composite_init(void)
{
/* 注册 CDC ACM */
cdc_acm_init();
/* 注册 HID */
hid_init();
return 0;
}
/*
* Remote Wakeup 需要同时满足:
* 1. 描述符声明该能力
* 2. 主机允许远程唤醒
* 3. 当前 USB stack / class 提供相应支持
*/
自供电 / 总线供电属于设备能力与描述符设计问题,具体实现应以当前 stack 的 descriptor 配置方式为准。
CONFIG_USB_DEVICE_LOG_LEVEL_DBG=y
CONFIG_USB_CDC_ACM_LOG_LEVEL_DBG=y
CONFIG_USB_HID_LOG_LEVEL_DBG=y
# Linux 查看设备
lsusb
lsusb -v -d 2fe3:0100
# 串口设备
ls /dev/ttyACM*
# 重置设备
sudo ./dfu-util -d 2fe3:0100 --reset
# nRF 平台是否支持 USB 设备模式,首先取决于 SoC / 板卡是否带 USB Device Controller
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_USB_DEVICE_VID=0x2FE3
CONFIG_USB_DEVICE_PID=0x0100
/* nRF52840 USB 引脚 */
#define P0_18 D:USB_DM
#define P0_22 D:USB_DP
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 设备枚举失败 | 描述符错误 | 检查描述符 |
| 通信失败 | 端点配置错误 | 验证端点设置 |
| 速度慢 | 传输类型不当 | 选择合适传输类型 |
| 断开连接 | 电源不足 | 检查供电能力 |
学习日期: 2026-03-21 笔记编号: #58 作者: 小白 🤖