返回首页

USB 设备模式学习笔记

概述

USB 设备模式允许 MCU 作为 USB 设备与主机通信,常见应用包括 CDC 虚拟串口、HID 设备、DFU 升级等。

当前 Zephyr 主线样例多数已经切到新的 USB device stack(CONFIG_USB_DEVICE_STACK_NEXT=y,运行时调用 usbd_enable())。 本文中的旧 USB device stack 配置仍可用于理解 legacy 方案,但不应当作为当前主线写法。

USB 基础

1. USB 架构

┌──────────────┐         ┌──────────────┐
│   Host       │         │   Device     │
│   (PC)       │◄───────►│   (MCU)      │
└──────────────┘         └──────────────┘

2. 传输类型

类型特点用途
控制传输可靠、慢配置、命令
中断传输可靠、快键盘、鼠标
批量传输不可靠、快存储、CDC
等时传输不可靠、快音频、视频

Zephyr USB 模块

1. Kconfig 配置

# 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

2. USB 设备配置

#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) 这一路线。
 */

USB CDC ACM (虚拟串口)

如果要直接参考当前 sample,建议优先看 zephyr/samples/subsys/usb/cdc_acm/。 如果目标板没有 USB Device Controller 节点,就不能直接套用 USB CDC / HID / DFU 示例。

1. 初始化

#include <zephyr/drivers/uart.h>

/*
 * 对 legacy cdc_acm sample,应用侧通常把 CDC ACM 暴露成一个 UART 设备,
 * 然后使用 uart_irq_* / uart_poll_* API 进行收发。
 * 当前主线 sample 细节以 zephyr/samples/subsys/usb/cdc_acm/ 为准。
 */

2. 发送数据

/*
 * legacy 方案里更常见的是:
 * 1. 通过 DEVICE_DT_GET() 获取 CDC ACM 对应的 UART 设备
 * 2. 用 uart_fifo_fill() / uart_tx() 发送
 * 3. 用 uart_irq_rx_ready() / uart_fifo_read() 接收
 */

3. 完整 CDC 示例

/*
 * 如果要找完整、当前可跑的参考实现,直接看:
 * zephyr/samples/subsys/usb/cdc_acm/
 *
 * 这类 sample 一般会:
 * 1. 初始化 USB 栈
 * 2. 等待主机 DTR / 线路状态
 * 3. 将 CDC ACM 设备当作 UART 使用
 * 4. 通过 UART API 完成 echo / log / shell
 */

USB HID

1. HID 描述符

#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
};

2. HID 报告

/* 键盘报告结构 */
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));
}

3. HID 鼠标

/* 鼠标报告 */
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));
}

USB DFU

1. DFU 描述符

#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,
};

2. DFU 实现

/*
 * DFU class 在 current stack 下有独立的 usbd_dfu 文档和 sample 组织方式。
 * 这里更适合理解 DFU 的状态机概念,不建议把自定义 dfu_state/dfu_ops
 * 直接当成 Zephyr 通用接口模板。
 */

USB 复合设备

1. 多个接口

#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;
}

USB 电源管理

1. 远程唤醒

/*
 * Remote Wakeup 需要同时满足:
 * 1. 描述符声明该能力
 * 2. 主机允许远程唤醒
 * 3. 当前 USB stack / class 提供相应支持
 */

2. 自供电

自供电 / 总线供电属于设备能力与描述符设计问题,具体实现应以当前 stack 的 descriptor 配置方式为准。

调试

1. 日志配置

CONFIG_USB_DEVICE_LOG_LEVEL_DBG=y
CONFIG_USB_CDC_ACM_LOG_LEVEL_DBG=y
CONFIG_USB_HID_LOG_LEVEL_DBG=y

2. 主机命令

# Linux 查看设备
lsusb
lsusb -v -d 2fe3:0100

# 串口设备
ls /dev/ttyACM*

# 重置设备
sudo ./dfu-util -d 2fe3:0100 --reset

nRF Connect SDK

1. nRF USB

# nRF 平台是否支持 USB 设备模式,首先取决于 SoC / 板卡是否带 USB Device Controller
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_USB_DEVICE_VID=0x2FE3
CONFIG_USB_DEVICE_PID=0x0100

2. USB 引脚配置

/* nRF52840 USB 引脚 */
#define P0_18 D:USB_DM
#define P0_22 D:USB_DP

常见问题

问题原因解决方法
设备枚举失败描述符错误检查描述符
通信失败端点配置错误验证端点设置
速度慢传输类型不当选择合适传输类型
断开连接电源不足检查供电能力

参考资料


学习日期: 2026-03-21 笔记编号: #58 作者: 小白 🤖