5.24 USB设备
5.24 USB设备
📚 本节导读
学习时长: 约 45 分钟
难度级别: ⭐⭐⭐⭐⭐
前置知识: C 语言基础、USB 协议基础、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS 中 TinyUSB 协议栈的集成架构
- 理解 OSAL(操作系统抽象层)在 TinyUSB 中的作用
- 掌握 USB 设备模式(Device)的配置与使用
- 掌握 USB 主机模式(Host)的配置与使用
- 了解 CDC、HID、MSC 等 USB 类驱动
一、概述
OneOS 集成了 TinyUSB 开源 USB 协议栈,提供完整的 USB 设备(Device)和主机(Host)功能。驱动源码位于 drivers/tinyusb/ 和 drivers/usb/ 目录下。TinyUSB 是一个轻量级的开源 USB 主机/设备库,专为嵌入式系统设计,通过 OSAL(操作系统抽象层)与 OneOS 内核无缝集成。
TinyUSB 的特点:
- 双模支持:同时支持 USB Device 和 USB Host 模式
- OSAL 集成:通过 osal_task_delay、osal_mutex 等 OSAL 接口与 OneOS 内核对接
- 多类驱动:支持 CDC(虚拟串口)、HID(人机接口)、MSC(大容量存储)、DFU(固件升级)等
- 模块化设计:分离设备、主机、各类驱动的实现,按需编译
- 事件驱动:基于事件处理机制进行通信和数据传输
1.1 使用场景
一句话核心:USB/TinyUSB 驱动让 MCU 既能当 USB 设备(虚拟串口/键盘/存储,连电脑或手机),也能当 USB 主机(识别 U 盘/键盘/鼠标等外设)。OneOS 通过集成 TinyUSB 提供完整双模支持,应用只需写类驱动回调。
与"用串口/GPIO 模拟外设"对比:
| 维度 | 用 TinyUSB | 串口/IO 模拟 |
|---|---|---|
| 即插即用 | 标准 USB 枚举,免驱动 | 需专用上位机协议 |
| 功能 | CDC/HID/MSC/DFU 多类 | 单一 |
| 双模 | 支持 Device + Host | 不支持 Host |
| 通用性 | 电脑/手机直接识别 | 受限 |
典型应用场景:
| 场景 | 模式 | 实际工作情况 |
|---|---|---|
| USB 虚拟串口(调试/下载) | Device | CDC 类,电脑识别为 COM 口,tud_cdc_read/write 通信 |
| USB 键盘/鼠标 | Device | HID 类,tud_hid_report 发送报告 |
| USB 大容量存储 | Device | MSC 类,配合文件系统做 U 盘 |
| U 盘读取 | Host | tuh_task 枚举,tuh_msc_* 读写 U 盘 |
| 固件升级 | Device | DFU 类,免去编程器 |
选型边界表:
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 极简单外设通信 | 串口 | USB 协议栈开销大 |
| MCU 无 USB 外设 | 串口/SPI | 无 USB 硬件 |
| 需要 U 盘/键鼠接入 | TinyUSB Host | 标准 USB 枚举 |
结合源码的使用注意点:
- 必须周期调
tud_task/tuh_task:协议栈靠任务函数推进,漏调则枚举/收发卡住; - Kconfig 选对 hw 和 example:不同 MCU USB 外设(DWC2/STM32FS/Synopsys)对应不同 hw 配置,选错无法编译;
- 中断/时钟要配好:TinyUSB 需要正确的 USB 中断和 48MHz 时钟,否则枚举失败。
二、TinyUSB 架构
2.1 整体架构
┌─────────────────────────────────────────────────────┐
│ 应用层 │
│ (CDC读写 / HID报告 / MSC文件操作) │
├─────────────────────────────────────────────────────┤
│ TinyUSB 协议栈 (tusb) │
│ ┌──────────┬──────────┬──────────┬──────────┐ │
│ │ CDC │ HID │ MSC │ DFU │ │
│ │ 类驱动 │ 类驱动 │ 类驱动 │ 类驱动 │ │
│ ├──────────┴──────────┴──────────┴──────────┤ │
│ │ USB Device / Host 核心 │ │
│ ├───────────────────────────────────────────┤ │
│ │ OSAL 适配层 (OneOS) │ │
│ │ mutex / queue / task_delay / ... │ │
│ └───────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ OneOS 内核 (mutex/queue/sem) │
├─────────────────────────────────────────────────────┤
│ MCU USB 外设 (DWC2 / FSDEV) │
└─────────────────────────────────────────────────────┘2.2 OSAL 适配层
TinyUSB 通过 OSAL(OS Abstraction Layer) 屏蔽不同 RTOS 的差异。osal.h 定义了统一的 OSAL 接口(信号量/互斥量/队列),具体实现按 CFG_TUSB_OS 编译进对应的 osal_*.h。OneOS 提供 RT-Thread 兼容头(rtthread.h),使 TinyUSB 的 osal_rtthread.h(用 rt_* API)能直接在 OneOS 上编译运行。
osal.h 的 OS 选择(drivers/tinyusb/src/osal/osal.h,与项目逐字一致):
#if CFG_TUSB_OS == OPT_OS_NONE
#include "osal_none.h"
#elif CFG_TUSB_OS == OPT_OS_FREERTOS
#include "osal_freertos.h"
#elif CFG_TUSB_OS == OPT_OS_MYNEWT
#include "osal_mynewt.h"
#elif CFG_TUSB_OS == OPT_OS_PICO
#include "osal_pico.h"
#elif CFG_TUSB_OS == OPT_OS_RTTHREAD
#include "osal_rtthread.h"
#elif CFG_TUSB_OS == OPT_OS_CUSTOM
#include "tusb_os_custom.h" // implemented by application
#else
#error OS is not supported yet
#endifosal.h 声明的 OSAL 接口(drivers/tinyusb/src/osal/osal.h,与项目逐字一致):
//------------- Semaphore -------------//
static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef);
static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr);
static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec);
//------------- Mutex -------------//
static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef);
static inline bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec);
static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl);
//------------- Queue -------------//
static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef);
static inline bool osal_queue_receive(osal_queue_t qhdl, void* data);
static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr);
static inline bool osal_queue_empty(osal_queue_t qhdl);OneOS 实际用的映射(经 osal_rtthread.h + OneOS 的 rtthread 兼容层):
| TinyUSB OSAL 接口 | osal_rtthread.h 实现(rt_* API) | 说明 |
|---|---|---|
osal_semaphore_create | rt_sem_init | 静态初始化信号量 |
osal_semaphore_post | rt_sem_release | 释放信号量 |
osal_semaphore_wait | rt_sem_take | 获取信号量(毫秒超时) |
osal_mutex_create | rt_mutex_init | 静态初始化互斥量 |
osal_mutex_lock | rt_mutex_take | 上锁(毫秒超时) |
osal_mutex_unlock | rt_mutex_release | 解锁 |
osal_queue_create | rt_mq_init | 静态初始化消息队列 |
osal_queue_send | rt_mq_send | 发送消息 |
osal_queue_receive | rt_mq_recv | 接收消息 |
注意(重要):
osal.h本身没有osal_task_delay——任务延迟不在 OSAL 接口里,是应用层直接在循环里调os_task_msleep/rt_thread_mdelay(见 5.x 示例)。- 早期文档把 OSAL 映射写成
os_mutex_create/os_mq_create(OneOS 动态 API),实际 TinyUSB 的 OSAL 用的是rt_mutex_init/rt_mq_init(静态初始化,因osal_mutex_create只拿osal_mutex_def_t*静态定义)。OneOS 通过提供rtthread.h兼容头让osal_rtthread.h直接可用。OSAL_TIMEOUT_WAIT_FOREVER = UINT32_MAX,TinyUSB 用它表示无限等待(如控制传输OSAL_TIMEOUT_CONTROL_XFER)。
三、关键 API
3.1 TinyUSB 初始化
bool tusb_init(void);| 返回值 | 说明 |
|---|---|
true | 初始化成功 |
false | 初始化失败 |
初始化 TinyUSB 协议栈,包括设备栈和主机栈。需在创建/启动 tud_task/tuh_task 之前调用。
3.2 设备模式任务
void tud_task(void);USB 设备模式任务函数,必须在任务中周期性调用(如 while(1) { tud_task(); os_task_msleep(1); })。它处理:
- 设备枚举:响应主机的 GET_DESCRIPTOR/SET_ADDRESS/SET_CONFIGURATION 等控制请求;
- 控制传输:处理端点 0 的控制请求,分发给各设备回调(
tud_vendor_control_xfer_cb等); - 类请求:把 CDC/HID/MSC 类请求分发给对应类驱动。
关键理解 —— 中断驱动 + 任务处理:USB 硬件中断负责收发数据,
tud_task在任务上下文处理"软件状态机"(枚举、请求、通知回调)。必须周期调用tud_task,否则协议栈状态不推进、主机无法枚举设备。中断里不要调用tud_task(它是任务上下文函数,可能阻塞)。
3.3 主机模式任务
void tuh_task(void);USB 主机模式任务函数,必须在任务中周期性调用(如 while(1) { tuh_task(); os_task_msleep(1); })。它处理:
- 设备检测:轮询 USB 总线,发现设备插拔;
- 枚举:对新插入设备执行标准枚举(读描述符、分配地址、配置);
- 数据传输:处理各端点的数据收发,分发给类驱动回调(
tuh_cdc_*/tuh_hid_*/tuh_msc_*)。
关键理解 —— 主机靠回调通知应用:设备枚举完成后,
tuh_mounted_cb(dev_addr)被调用;断开时tuh_umounted_cb。应用通过这些回调(+ 轮询tuh_mounted)感知设备状态(见 5.3 示例)。
3.4 设备模式 API
/* 检查设备是否已配置 */
bool tud_mounted(void);
/* 检查设备是否挂起 */
bool tud_suspended(void);
/* 远程唤醒主机 */
void tud_remote_wakeup(void);3.5 CDC 类 API
/* CDC 已连接 */
bool tud_cdc_connected(void);
/* CDC 可用字节数 */
uint32_t tud_cdc_available(void);
/* CDC 读取数据 */
uint32_t tud_cdc_read(void *buffer, uint32_t bufsize);
/* CDC 读取一个字符 */
char tud_cdc_read_char(void);
/* CDC 写入数据 */
uint32_t tud_cdc_write(const void *buffer, uint32_t bufsize);
/* CDC 写入一个字符 */
uint32_t tud_cdc_write_char(char ch);
/* CDC 刷新发送缓冲区 */
void tud_cdc_write_flush(void);3.6 HID 类 API
/* 检查 HID 是否就绪 */
bool tud_hid_ready(void);
/* 发送 HID 报告 */
bool tud_hid_report(uint8_t report_id, const void *report, uint16_t len);3.7 主机模式 API
/* 检查指定地址的设备是否已挂载(连接并枚举完成) */
bool tuh_mounted(uint8_t dev_addr);
/* 获取指定设备的 VID/PID(返回是否成功) */
bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t *vid, uint16_t *pid);四、Kconfig 配置
TinyUSB 的 Kconfig 结构是三层选择:① 角色(role:device/host/otg)→ ② 硬件(hw:dwc2/stm32fs 等)→ ③ 示例(device/host example)。没有独立的 OS_USING_TINYUSB_CDC 这类"类开关"——类由所选示例决定。以下与 drivers/tinyusb/Kconfig 逐字一致的关键部分:
menu "TinyUSB"
config OS_USING_TINYUSB
bool "Enable tinyusb"
default n
if OS_USING_TINYUSB
choice
prompt "tinyusb role"
default OS_USING_TINYUSB_DEVICE
config OS_USING_TINYUSB_DEVICE
bool "device"
config OS_USING_TINYUSB_HOST
bool "host"
config OS_USING_TINYUSB_OTG
bool "otg"
endchoice
choice
prompt "tinyusb hw"
default OS_USING_TINYUSB_HW_DWC2
config OS_USING_TINYUSB_HW_DWC2
bool "dwc2"
config OS_USING_TINYUSB_HW_STM32FS
bool "stm32fs"
config OS_USING_TINYUSB_HW_STM32_SYNOPSYS
bool "stm32 synopsys"
config OS_USING_TINYUSB_HW_OHCI
bool "ohci"
config OS_USING_TINYUSB_HW_EHCI
bool "ehci"
config OS_USING_TINYUSB_HW_CIHS_DEVICE
bool "ci_hs device"
config OS_USING_TINYUSB_HW_CIHS_HOST
bool "ci_hs host"
config OS_USING_TINYUSB_HW_NRF5X_DEVICE
bool "nrf5x device"
endchoice
choice
prompt "tinyusb device examples"
default OS_USING_TINYUSB_DEVICE_CDC_MSC
depends on (OS_USING_TINYUSB_DEVICE || OS_USING_TINYUSB_OTG)
config OS_USING_TINYUSB_DEVICE_CDC_MSC
bool "tinyusb device cdc_msc"
config OS_USING_TINYUSB_DEVICE_HID_KEYBOARD
bool "tinyusb device hid_keyboard"
config OS_USING_TINYUSB_DEVICE_HID_MOUSE
bool "tinyusb device hid_mouse"
config OS_USING_TINYUSB_DEVICE_MSC
bool "tinyusb device msc"
config OS_USING_TINYUSB_DEVICE_DFU
bool "tinyusb device dfu"
config OS_USING_TINYUSB_DEVICE_USER_EXAMPLE
bool "tinyusb device user_example"
endchoice
choice
prompt "tinyusb host examples"
default OS_USING_TINYUSB_HOST_CDC_MSC_HID
depends on (OS_USING_TINYUSB_HOST || OS_USING_TINYUSB_OTG)
config OS_USING_TINYUSB_HOST_CDC_MSC_HID
bool "tinyusb host cdc_msc_hid"
config OS_USING_TINYUSB_HOST_HID_CONTROLLER
bool "tinyusb host hid_controller"
endchoice
endif
endmenu配置步骤说明:
| 步骤 | 选择 | 作用 |
|---|---|---|
| 1 | OS_USING_TINYUSB | 总开关 |
| 2 | OS_USING_TINYUSB_DEVICE/HOST/OTG | 选角色(设备/主机/双模) |
| 3 | OS_USING_TINYUSB_HW_* | 选MCU USB 外设类型(dwc2/stm32fs/synopsys/ohci/ehci 等),选错无法编译 |
| 4 | 选示例 | 按需选 CDC_MSC / HID / MSC / DFU / 自定义示例,决定启用哪些类 |
注意:早期文档写的
OS_USING_TINYUSB_CDC/OS_USING_TINYUSB_HID/OS_USING_TINYUSB_MSC不存在——TinyUSB 通过选示例(如OS_USING_TINYUSB_DEVICE_CDC_MSC)决定编译哪些类,而不是逐类开关。想用 CDC 就选 CDC_MSC 示例(或用OS_USING_TINYUSB_DEVICE_USER_EXAMPLE写自己的类实现)。
五、使用示例
5.1 USB CDC 虚拟串口(设备模式)
#include <tusb.h>
#include <os_task.h>
/* USB Device 任务 */
void usb_device_task(void *arg)
{
uint32_t written;
/* 初始化 TinyUSB(检查返回值) */
if (!tusb_init())
{
os_kprintf("tusb init failed\r\n");
return;
}
while (1)
{
/* 周期性调用设备任务(推进协议栈状态机) */
tud_task();
/* 检查 CDC 连接状态 */
if (tud_cdc_connected())
{
/* 读取上位机发送的数据 */
if (tud_cdc_available())
{
uint8_t buf[64];
uint32_t count = tud_cdc_read(buf, sizeof(buf));
/* 回显数据(写入全部字节并刷新) */
written = tud_cdc_write(buf, count);
if (written < count)
{
/* 发送缓冲满,等下一轮 flush 后再写剩余 */
os_kprintf("cdc write partial %lu/%lu\r\n",
(unsigned long)written, (unsigned long)count);
}
tud_cdc_write_flush();
}
}
os_task_msleep(1);
}
}5.2 USB HID 键盘示例
#include <tusb.h>
#include <os_task.h>
#include <string.h>
/* HID 键盘报告描述符 */
static const uint8_t desc_hid_report[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_REPORT_ID_KEYBOARD))
};
/* 发送按键 */
void hid_send_key(uint8_t modifier, uint8_t keycode)
{
if (!tud_hid_ready())
return;
/* 按键按下 */
uint8_t report[8] = {modifier, 0, keycode, 0, 0, 0, 0, 0};
if (!tud_hid_report(HID_REPORT_ID_KEYBOARD, report, sizeof(report)))
{
os_kprintf("hid report keydown failed\r\n");
return;
}
os_task_msleep(10);
/* 按键释放 */
memset(report, 0, sizeof(report));
if (!tud_hid_report(HID_REPORT_ID_KEYBOARD, report, sizeof(report)))
{
os_kprintf("hid report keyup failed\r\n");
}
}
void hid_task(void *arg)
{
tusb_init();
while (1)
{
tud_task();
/* 每秒发送一次 'A' 键 */
hid_send_key(0, HID_KEY_A);
os_task_msleep(1000);
}
}5.3 USB Host 模式检测设备
#include <tusb.h>
#include <os_task.h>
void usb_host_task(void *arg)
{
if (!tusb_init())
{
os_kprintf("tusb init failed\r\n");
return;
}
while (1)
{
/* 周期性调用主机任务(推进枚举/传输状态机) */
tuh_task();
/* 检测设备连接 */
for (uint8_t addr = 1; addr <= 127; addr++)
{
if (tuh_mounted(addr))
{
uint16_t vid, pid;
if (tuh_vid_pid_get(addr, &vid, &pid))
{
os_kprintf("USB device: VID=0x%04x PID=0x%04x\r\n", vid, pid);
}
}
}
os_task_msleep(10);
}
}六、USB 的适配(接入新 MCU 的 USB 外设)
6.1 先认识适配层:TinyUSB 是怎么"长"到芯片上的
TinyUSB 的协议栈核心(drivers/tinyusb/src/)跨平台通用,不碰具体寄存器。真正操作 MCU USB 外设的是 TinyUSB 的硬件驱动(port/)——每种 USB 外设(DWC2/STM32FS/Synopsys/OHCI/EHCI/CI_HS/nRF5x)一个目录。换 MCU,就是让 TinyUSB 能编译对应的 hw 驱动 + 配好中断/时钟/引脚。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层(tud_task/tuh_task + 类回调) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ TinyUSB 协议栈(drivers/tinyusb/src/,通用不改) │
│ device/usbd.c + host/usbh.c + class/cdc|hid|msc │
│ OSAL(osal_rtthread.h → OneOS rtthread 兼容层) │
└───────────────┬──────────────────────────┬────────────────┘
│ ①Kconfig 选 hw │ ②port/ 硬件驱动
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ TinyUSB 硬件驱动(port/xxx/)★需配好★ │
│ DWC2 / STM32FS / Synopsys / OHCI / EHCI 等 │
└───────────────┬──────────────────────────┬────────────────┘
│ USB 中断 + 48MHz 时钟 │ 引脚复用
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(MCU USB 外设 + PHY) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新 MCU 的 USB 外设,关键三步:① Kconfig 选对 hw(role + hw + example);② 提供正确的 USB 中断服务(ISR 里调 tud_int_handler/tuh_int_handler);③ 保证 48MHz USB 时钟和引脚复用正确。协议栈和 OSAL 都不用改。
6.2 接入新 MCU USB 外设的标准流程
第 1 步:确认 USB 外设类型——查手册确认 MCU 的 USB 外设是 DWC2/STM32FS/Synopsys/OHCI/EHCI 等,对应 TinyUSB port/ 下的哪个目录。
第 2 步:Kconfig 选 hw——在 drivers/tinyusb/Kconfig 里选对 OS_USING_TINYUSB_HW_*(如 STM32F4 用 STM32_SYNOPSYS,部分用 DWC2)。
第 3 步:配中断——在 BSP 的中断服务函数里调 TinyUSB 的中断处理函数(设备模式 tud_int_handler/tud_int_handler,主机模式 tuh_int_handler),USB 中断优先级要合适。
第 4 步:配时钟与引脚——提供 48MHz USB 时钟(CFG_TUSB_MCU 相关配置),配置 USB 数据线 D+/D- 引脚复用。
第 5 步:测试——设备模式连电脑看是否枚举;主机模式插 U 盘看 tuh_mounted_cb 是否触发。
6.3 接口契约:接入 USB 需要配什么
| 层 | 内容 | 职责 | 说明 |
|---|---|---|---|
| Kconfig | OS_USING_TINYUSB_HW_* | 选 USB 外设驱动 | 选错无法编译 |
| Kconfig | role/example | 选设备/主机 + 类 | 决定编译哪些类 |
| 中断 | tud_int_handler/tuh_int_handler | 处理 USB 硬件中断 | 在 USB ISR 中调用 |
| 时钟 | 48MHz USB 时钟 | 收发数据同步 | 无 48MHz 则枚举失败 |
| 引脚 | D+/D- 复用 | 物理连接 | 复用错则无信号 |
6.4 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
| hw 选对了? | OS_USING_TINYUSB_HW_* 与 MCU 外设匹配,否则编译/运行失败 |
USB 中断调了 tud_int_handler/tuh_int_handler? | 漏调则中断不处理、枚举卡住 |
| 48MHz 时钟就绪? | 无 48MHz 则 USB 收发错乱 |
CFG_TUSB_MCU/CFG_TUSB_OS 配置对? | 决定 port 目录和 OSAL,配置错编译错 |
tud_task/tuh_task 周期调用? | 漏调则协议栈状态机不推进 |
📝 本节小结
核心要点回顾
本节介绍了 OneOS 中 USB/TinyUSB 设备驱动的使用方法:
- TinyUSB 架构:OSAL 适配层 → USB 核心 → 类驱动(CDC/HID/MSC/DFU)→ 应用层
- OSAL 集成:TinyUSB 的
osal_*.h经rt_*API(rt_mutex_init/rt_mq_init等)与 OneOS(rtthread 兼容层)对接;osal.h本身没有osal_task_delay - 双模支持:同时支持 USB Device 和 USB Host 模式(
tud_*/tuh_*API) - CDC 虚拟串口:
tud_cdc_read/tud_cdc_write实现与上位机的串口通信 - HID 人机接口:
tud_hid_report发送键盘/鼠标/自定义报告 - Kconfig 配置:三层结构(role → hw → example),没有
OS_USING_TINYUSB_CDC这类逐类开关,类由所选示例决定 - 硬件适配:接入新 MCU = Kconfig 选 hw + 中断调
tud/tuh_int_handler+ 48MHz 时钟 + 引脚,协议栈不用改
TinyUSB 是 OneOS 推荐的 USB 解决方案,适用于需要 USB 通信的嵌入式 IoT 设备。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
忘记周期性调用 tud_task/tuh_task | 设备/主机协议栈的处理都依赖任务函数周期性执行,必须放到一个循环任务中调用 |
用不存在的 OS_USING_TINYUSB_CDC | TinyUSB 没有逐类开关,类是选示例(如 OS_USING_TINYUSB_DEVICE_CDC_MSC)决定的 |
在中断里直接调用 tud_cdc_write 等阻塞操作 | 应把数据放入队列,在任务上下文调用;中断上下文只做标志/数据搬运 |
| 混淆 Device 与 Host API | 设备模式用 tud_*,主机模式用 tuh_*,前缀不同,不要混用 |
未检查 tud_cdc_connected/tud_hid_ready | 主机未连接/未就绪时读写会失败,调用前必须检查连接/就绪状态 |
误以为 OSAL 用 os_mutex_create 动态 API | TinyUSB OSAL 用的是 rt_mutex_init/rt_mq_init(静态初始化),OneOS 靠 rtthread 兼容头 |
| 漏配 USB 中断/48MHz 时钟 | USB 中断漏调 tud/tuh_int_handler、无 48MHz 时钟都会导致枚举失败 |
下一步
掌握了 USB/TinyUSB 设备驱动后,接下来学习 SPI Flash 存储设备:
- 5.25 SFlash设备:了解 OneOS 如何基于 SFDP 自动探测 SPI Flash 并实现页读写擦除操作。