USB 主机与设备栈
本页介绍 AC79NN SDK 中位于 apps/common/usb 的 USB 双模式协议栈:它同时提供设备(Device/Gadget)栈与主机(Host)栈,并承载 MSD、HID、CDC、UAC/UAC2、UVC、Printer 等标准类驱动,以及 ADB、AOA、Apple MFI 等厂商扩展协议,是 U 盘、USB 声卡、USB 摄像头、USB 键盘/鼠标、USB 升级等上层能力共用的底层基础。
Purpose and Scope
本文档的目标读者是需要在 AC79NN 平台上启用、调试或扩展 USB 功能的嵌入式工程师。页面覆盖:
- USB 栈的目录结构与分层(公共层、设备栈、主机栈、类驱动、厂商协议);
- 设备栈的初始化时序与类注册机制(
usb_device.c、usb_config.h); - 主机栈的配置入口、端点缓冲区管理与中断注册;
- 各 USB 类驱动(MSD、HID、CDC、UAC、UVC、Printer)的归属与职责;
- 由
TCFG_USB_*编译宏控制的启用/禁用方式; - 关键 API 的签名、参数与语义。
不覆盖(属于兄弟页面):具体外设应用(如 U 盘文件系统、音频播放流程、摄像头采集流水线)请参见对应外设/应用文档;底层控制器寄存器操作与 DMA 细节属于 asm/usb.h 描述的硬件抽象层,不在本页展开。
Overview
AC79NN 的 USB 控制器可工作于两种角色:
- 设备模式(Slave/Gadget):芯片作为 USB 外设连接 PC 或手机,通过
usb_device_mode(usb_id, class)动态挂载一个或多个类驱动,实现 U 盘(MSD)、声卡(UAC)、虚拟串口(CDC)、摄像头(UVC)、HID 等设备功能; - 主机模式(Host):芯片作为 USB 主控,通过
usb_host_config(usb_id)枚举外接设备,配合usb_ctrl_transfer.c与usb_bulk_transfer.c完成控制/批量传输,支持外接 U 盘、键盘鼠标、ADB 调试、AOA 附件等。
两种模式共用同一套硬件抽象(asm/usb.h)、端点 DMA 缓冲区管理(usb_alloc_ep_dmabuffer)与中断注册机制(usb_h_isr_reg / usb_g_isr_reg),上层通过 usb_config.h 暴露的统一接口选择工作模式。这种"一份硬件、两套栈"的设计让同一颗芯片在无需改版的情况下既能被 PC 枚举,也能自主枚举外部设备,是穿戴/音频 SoC 典型的外设互联方案。
栈的启用完全由编译期宏驱动(如 TCFG_USB_SLAVE_ENABLE、TCFG_USB_SLAVE_MSD_ENABLE 等),未启用的类不会被编译进来,从而精确控制代码体积与 RAM 占用——这对资源受限的 MCU 至关重要。
Architecture
下图依据 apps/common/usb 目录的真实组织关系绘制:
flowchart TD
subgraph sg_Common["公共层 apps/common/usb"]
USBConfig["usb_config.h<br/>模式配置与中断/DMA 注册接口"]
USBCommon["usb_common_def.h<br/>公共宏与类型定义"]
SCSI["usb/scsi.h<br/>SCSI 命令定义"]
end
subgraph sg_Device["设备栈 apps/common/usb/device"]
USBDevice["usb_device.c<br/>设备模式入口与类挂载"]
Desc["descriptor.c<br/>描述符管理"]
UserSetup["user_setup.c<br/>厂商 Setup 请求"]
MSD["msd.c / msd_upgrade.c<br/>大容量存储与升级"]
HID["hid.c<br/>人机接口设备"]
CDC["cdc.c<br/>通信设备类"]
UAC["uac.c / uac2.c / uac_stream.c<br/>音频类"]
UVC["uvc.c<br/>视频类"]
Printer["printer.c<br/>打印机类"]
RamDisk["ram_disk.c / ram_disk_circle.c / ram_disk_update.c<br/>RAM 盘"]
TaskPC["task_pc.c<br/>PC 交互任务"]
end
subgraph sg_Host["主机栈 apps/common/usb/host"]
CtrlTransfer["usb_ctrl_transfer.c<br/>控制传输"]
BulkTransfer["usb_bulk_transfer.c<br/>批量传输"]
Storage["usb_storage.h<br/>存储类"]
HostCDC["usb_host_cdc.h<br/>CDC 类"]
Audio["audio.h / hid.h / usb_hid_keys.h"]
ADB["adb.c / adb_rsa_key.c<br/>Android 调试桥"]
AOA["aoa.c<br/>Android Open Accessory"]
MFI["apple_mfi.c<br/>Apple MFi"]
end
USBDevice --> Desc
USBDevice --> UserSetup
USBDevice --> MSD
USBDevice --> HID
USBDevice --> CDC
USBDevice --> UAC
USBDevice --> UVC
USBDevice --> Printer
USBDevice --> RamDisk
USBDevice --> TaskPC
CtrlTransfer --> Storage
CtrlTransfer --> HostCDC
CtrlTransfer --> ADB
CtrlTransfer --> AOA
CtrlTransfer --> MFI
BulkTransfer --> ADB
BulkTransfer --> AOA
USBConfig --> USBDevice
USBConfig --> CtrlTransfer
USBConfig --> USBCommon
各层职责说明:
| 层次 | 代表文件 | 职责 |
|---|---|---|
| 公共配置层 | usb_config.h | 向上层应用屏蔽硬件差异,提供模式切换、端点缓冲、中断注册的统一定义;同时包含 usb/device/usb_stack.h 与 usb/host/usb_host.h 两个栈头文件 |
| 设备栈核心 | usb_device.c | 完成 SIE/中断/DMA 初始化,按 class 位掩码逐个挂载类驱动,负责设备保持(hold/release) |
| 设备类驱动 | msd.c、hid.c、cdc.c、uac.c、uvc.c、printer.c | 各标准类的事件回调、描述符表(*_desc_config)与请求处理,由 usb_add_desc_config 注册 |
| 主机栈核心 | usb_host.c(经 usb/host/usb_host.h 引用)、usb_ctrl_transfer.c、usb_bulk_transfer.c | 主机枚举、控制/批量传输原语 |
| 厂商协议 | adb.c、aoa.c、apple_mfi.c | 基于控制/批量传输实现的私有协议,用于手机互联 |
设计意图:设备栈的类驱动全部通过编译宏 + 位掩码注册两条路径解耦——编译期宏决定代码是否编入,运行期 class 位决定是否挂载。这样既能在运行时切换功能(例如从 U 盘切到声卡),又不会为未使用的类付出任何代码与 RAM 代价。
注:图中
usb_host.c与usb/device/usb_stack.h为头文件引用所证明的栈入口(见 usb_config.h);其余文件均通过仓库文件列表确认存在。
设备栈实现详解
初始化时序:从复位到可被枚举
设备模式入口是 usb_device_init()(编译宏 TCFG_USB_SLAVE_ENABLE 控制),其执行顺序如下(usb_device.c):
usb_config(usb_id)— 硬件基础配置(时钟、引脚、控制器模式);usb_g_sie_init(usb_id)— 初始化串行接口引擎(SIE),使能设备侧数据通路;- 依据
FUSB_MODE写入电源/速度配置(usb_write_power(usb_id, 0x40)或0x60),并在未定义时报编译错误(#error "USB_SPEED_MODE not defined")——这是强制开发者显式声明速度模式的防呆设计; usb_slave_init(usb_id)— 从机功能初始化;- 分配 EP0 DMA 缓冲区(64 字节)并调用
usb_set_dma_raddr将端点 0~4 的 DMA 读地址统一指向该缓冲区——EP0 的控制传输(Setup/Data/Status 阶段)共用同一 DMA 缓冲,减少内存占用; - 使能
INTRUSB_RESET_BABBLE | INTRUSB_SUSPEND总线级中断,清空并配置 TX/RX 端点中断; usb_g_isr_reg(usb_id, 3, 0)— 将设备 ISR 注册到中断优先级 3、CPU 0。
static void usb_device_init(const usb_dev usb_id)
{
usb_config(usb_id);
usb_g_sie_init(usb_id);
#if defined(FUSB_MODE) && FUSB_MODE
usb_write_power(usb_id, 0x40);
#elif defined(FUSB_MODE) && (FUSB_MODE==0)
usb_write_power(usb_id, 0x60);
#else
#error "USB_SPEED_MODE not defined"
#endif
usb_slave_init(usb_id);
if (!ep0_dma_buffer[usb_id]) {
ep0_dma_buffer[usb_id] = usb_alloc_ep_dmabuffer(usb_id, 0, 64);
}
usb_set_dma_raddr(usb_id, 0, ep0_dma_buffer[usb_id]);
usb_set_dma_raddr(usb_id, 1, ep0_dma_buffer[usb_id]);
usb_set_dma_raddr(usb_id, 2, ep0_dma_buffer[usb_id]);
usb_set_dma_raddr(usb_id, 3, ep0_dma_buffer[usb_id]);
usb_set_dma_raddr(usb_id, 4, ep0_dma_buffer[usb_id]);
usb_write_intr_usbe(usb_id, INTRUSB_RESET_BABBLE | INTRUSB_SUSPEND);
usb_clr_intr_txe(usb_id, -1);
usb_clr_intr_rxe(usb_id, -1);
usb_set_intr_txe(usb_id, 0);
usb_set_intr_rxe(usb_id, 0);
usb_g_isr_reg(usb_id, 3, 0);
}
Source: usb_device.c
设计意图:初始化顺序遵循"硬件就绪 → SIE 使能 → 电源/速度确定 → 从机功能 → DMA 缓冲 → 中断"的依赖链。EP0 缓冲在首次使用时惰性分配(if (!ep0_dma_buffer[usb_id])),避免在从未使用 USB 的固件中浪费 64 字节 RAM。
类挂载与运行期模式切换
usb_device_mode(usb_id, class) 是设备模式的核心切换函数(usb_device.c):
class == 0(卸载模式):按编译宏逐个释放已注册的类(msd_release、uac_release、cdc_release、uvc_release、hid_release、printer_release),然后调用usb_device_hold(usb_id)(内部执行usb_g_hold+usb_release)并延时 15 个系统 tick,让总线完全断开后再切换;class != 0(挂载模式):先usb_add_desc_config(usb_id, MAX_INTERFACE_NUM, NULL)重置描述符表,随后用class_index依次递增地为每个命中的类添加描述符(usb_add_desc_config(usb_id, class_index++, msd_desc_config))并注册对应事件处理(msd_register等)。
int usb_device_mode(const usb_dev usb_id, const u32 class)
{
u8 class_index = 0;
if (class == 0) {
#if TCFG_USB_SLAVE_MSD_ENABLE
msd_release(usb_id);
#endif
#if TCFG_USB_SLAVE_AUDIO_ENABLE
uac_release(usb_id);
#endif
#if TCFG_USB_SLAVE_CDC_ENABLE
cdc_release(usb_id);
#endif
#if TCFG_USB_SLAVE_UVC_ENABLE
uvc_release(usb_id);
#endif
#if TCFG_USB_SLAVE_HID_ENABLE
hid_release(usb_id);
#endif
#if TCFG_USB_SLAVE_PRINTER_ENABLE
printer_release();
#endif
usb_device_hold(usb_id);
os_time_dly(15);
return 0;
}
usb_add_desc_config(usb_id, MAX_INTERFACE_NUM, NULL);
#if TCFG_USB_SLAVE_MSD_ENABLE
if ((class & MASSSTORAGE_CLASS) == MASSSTORAGE_CLASS) {
log_info("add desc msd");
usb_add_desc_config(usb_id, class_index++, msd_desc_config);
msd_register(usb_id);
}
#endif
/* ... 其余类同理:HID/CDC/UAC/UVC/Printer ... */
}
Source: usb_device.c
关键点:
class参数是位掩码(如MASSSTORAGE_CLASS),可一次挂载多个类,例如 U 盘 + 声卡组合;class_index用于在描述符表中按顺序登记每个接口;- 释放与挂载都受
TCFG_USB_SLAVE_*_ENABLE编译宏保护,未编译的类调用会被预处理器剔除; - 代码中保留了
usb_ep_conflict_check的调用(当前被注释),说明设计上曾考虑过端点冲突预检——多类并存时端点资源(EP 数量有限)的分配冲突是真实存在的约束,当前版本交由各驱动的register流程自行处理; - 切换前
usb_add_desc_config(usb_id, MAX_INTERFACE_NUM, NULL)重置描述符表,避免上次挂载的接口残留导致描述符错乱。
设备保持(Hold)与总线断开
usb_device_hold() 组合了 usb_g_hold() 与 usb_release():前者让设备侧 SIE 进入保持状态,后者释放控制器资源。配合 os_time_dly(15) 的延时,保证在模式切换过程中总线电平稳定、主机侧能看到一次干净的断开再枚举,避免热切换时出现协议级错误。
主机栈实现详解
主机模式通过 usb_config.h 暴露的统一接口驱动,核心入口如下(usb_config.h):
void usb_host_config(usb_dev usb_id); /* 主机模式配置 */
void usb_host_free(usb_dev usb_id); /* 主机模式释放 */
void *usb_h_get_ep_buffer(const usb_dev usb_id, u32 ep);
void *usb_h_alloc_ep_buffer(const usb_dev usb_id, u32 ep, u32 dma_size);
void usb_h_free_ep_buffer(const usb_dev usb_id, void *buf);
void usb_h_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void usb_g_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void usb_sof_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void *usb_alloc_ep_dmabuffer(const usb_dev usb_id, u32 ep, u32 dma_size);
void usb_free_ep_dmabuffer(const usb_dev usb_id, void *buf);
Source: usb_config.h
主机侧传输原语
主机栈的传输能力集中在 usb_ctrl_transfer.c(控制传输,用于枚举与类请求)与 usb_bulk_transfer.c(批量传输,用于 U 盘、ADB 等大数据量通路)两个文件中,其配套头文件 usb_ctrl_transfer.h、usb_bulk_transfer.h 声明了对外接口。上层类协议(如 usb_storage.h 的存储类、usb_host_cdc.h 的 CDC 类、adb.c、aoa.c、apple_mfi.c)都建立在这两个传输原语之上。
典型主机侧数据路径:
flowchart LR
App["上层应用<br/>(U盘/ADB/音频)"] --> Class["主机类驱动<br/>usb_storage / adb / aoa / apple_mfi"]
Class --> Ctrl["usb_ctrl_transfer.c<br/>控制传输"]
Class --> Bulk["usb_bulk_transfer.c<br/>批量传输"]
Ctrl --> EP["端点 DMA 缓冲<br/>usb_h_alloc_ep_buffer"]
Bulk --> EP
EP --> HW["USB 控制器<br/>(asm/usb.h)"]
端点缓冲区管理
主机与设备模式都通过 usb_alloc_ep_dmabuffer / usb_free_ep_dmabuffer 分配 DMA 缓冲区。DMA 缓冲区要求地址与对齐满足控制器要求,因此必须走专用分配器而非普通堆分配。usb_h_alloc_ep_buffer 进一步提供按端点号(ep)组织的缓冲管理,usb_h_get_ep_buffer 用于查询已分配端点的缓冲地址——批量传输场景下,类驱动先分配端点缓冲,再在批量传输中反复使用,避免每笔传输都重新分配。
中断注册的三种角色
| 接口 | 角色 | 说明 |
|---|---|---|
usb_h_isr_reg(usb_id, prio, cpu) | 主机中断 | 主机模式下处理总线事件与端点完成 |
usb_g_isr_reg(usb_id, prio, cpu) | 设备中断 | 设备模式下处理 Setup 请求与端点事件(设备栈以优先级 3、CPU 0 注册) |
usb_sof_isr_reg(usb_id, prio, cpu) | SOF 中断 | 帧起始中断,用于需要帧同步的场景(设备栈中默认注释关闭,以降低中断频率) |
优先级与 CPU 亲和可调,便于在多核(如 AC79 系列双核)场景下把 USB 中断固定到特定核,避免与音频等实时任务抢占。
配置选项
USB 栈的启用与类选择由编译期宏控制(在 app_config.h 等工程配置中定义),核心宏如下:
| 宏 | 类型 | 默认 | 说明 |
|---|---|---|---|
TCFG_USB_SLAVE_ENABLE | bool | 0 | 总开关:是否编译设备(从机)栈 |
TCFG_USB_SLAVE_MSD_ENABLE | bool | 0 | 大容量存储类(U 盘/升级盘) |
TCFG_USB_SLAVE_AUDIO_ENABLE | bool | 0 | 音频类 UAC(含 uac2、uac_stream) |
TCFG_USB_SLAVE_CDC_ENABLE | bool | 0 | 通信设备类(虚拟串口) |
TCFG_USB_SLAVE_UVC_ENABLE | bool | 0 | 视频类(摄像头) |
TCFG_USB_SLAVE_HID_ENABLE | bool | 0 | 人机接口设备(键盘/鼠标/自定义 HID) |
TCFG_USB_SLAVE_PRINTER_ENABLE | bool | 0 | 打印机类 |
FUSB_MODE | int | 必填 | 速度模式:非 0 → 高速(写 0x40);0 → 全速(写 0x60);未定义直接编译报错 |
MAX_INTERFACE_NUM | int | — | 描述符表最大接口数,供 usb_add_desc_config 使用 |
运行期行为配置由 usb_device_mode(usb_id, class) 的 class 位掩码决定,可组合多个类同时挂载,或在运行时切换到 class == 0 完成整体卸载。
API 参考
usb_host_config(usb_dev usb_id)
将指定 USB 控制器配置为主机模式。usb_id 为 USB 硬件编号(usb_dev 枚举)。调用后即可使用主机类驱动与传输原语。
usb_host_free(usb_dev usb_id)
释放主机模式占用的资源,用于从主机模式退出。
void *usb_alloc_ep_dmabuffer(const usb_dev usb_id, u32 ep, u32 dma_size)
为端点 ep 分配 dma_size 字节的 DMA 缓冲区,返回缓冲区地址;失败返回 NULL。设备栈用它为 EP0 分配 64 字节控制传输缓冲。
void usb_free_ep_dmabuffer(const usb_dev usb_id, void *buf)
释放 DMA 缓冲区。
void *usb_h_get_ep_buffer(const usb_dev usb_id, u32 ep) / usb_h_alloc_ep_buffer(...) / usb_h_free_ep_buffer(...)
主机模式端点缓冲的查询、分配与释放三件套,供主机类驱动在批量传输场景复用缓冲。
void usb_h_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id) / usb_g_isr_reg(...) / usb_sof_isr_reg(...)
分别注册主机、设备、SOF 中断处理;priority 为中断优先级,cpu_id 为绑定的 CPU 核。
int usb_device_mode(const usb_dev usb_id, const u32 class)
设备模式运行期切换。class == 0 卸载全部类并 hold 总线;非 0 时按位掩码挂载对应类。返回 0 表示成功。
核心流程
设备模式:从挂载类到被主机枚举
以下时序基于 usb_device_init → usb_device_mode → ISR 处理链的真实调用关系绘制:
sequenceDiagram
participant App as 应用任务
participant Dev as usb_device.c
participant Desc as descriptor.c
participant Class as 类驱动(msd/hid/cdc/uac/uvc/printer)
participant HW as USB 控制器/DMA
participant PC as PC 主机
App->>Dev: usb_device_mode(usb_id, MASSSTORAGE_CLASS)
Dev->>Desc: usb_add_desc_config(usb_id, MAX_INTERFACE_NUM, NULL) 重置
Dev->>Desc: usb_add_desc_config(usb_id, idx, msd_desc_config)
Dev->>Class: msd_register(usb_id)
Class-->>Dev: 注册回调/端点就绪
Dev-->>App: 返回 0
Note over HW,PC: USB 插入,总线复位
PC->>HW: 复位/SETUP(Get Descriptor)
HW->>Dev: INTRUSB_RESET_BABBLE 中断
Dev->>Desc: 组包设备/配置描述符
HW-->>PC: 描述符响应
PC->>HW: SET_CONFIGURATION
HW->>Dev: Setup 请求
Dev->>Class: 类请求分发(msd/hid 等)
Class-->>HW: 状态/数据响应
PC->>Class: SCSI 命令(CBW) → 数据 → CSW
Class-->>PC: 完成
要点:枚举完全由主机(PC)驱动,设备栈只负责在中断上下文中应答 Setup 请求并把类请求分发给对应驱动;大数据量通路(如 MSD 的 CBW/CSW)由类驱动的端点处理,不与 EP0 争用。
主机模式:从配置到外设枚举
sequenceDiagram
participant App as 应用任务
participant HC as usb_host_config
participant HClass as 主机类驱动(usb_storage/adb/aoa)
participant Ctrl as usb_ctrl_transfer.c
participant Bulk as usb_bulk_transfer.c
participant Dev as 外接设备(U盘/手机)
App->>HC: usb_host_config(usb_id)
HC-->>App: 就绪
App->>Ctrl: 枚举(Get Device/Config Descriptor)
Ctrl->>Dev: 控制传输
Dev-->>Ctrl: 描述符
App->>HClass: 匹配类并初始化
HClass->>Bulk: 批量传输
Bulk->>Dev: OUT/IN 数据
Dev-->>HClass: 数据/状态
失败模式、边界情况与并发
端点冲突(多类并存)
设备栈允许一次挂载多个类,但控制器端点(EP)数量有限。源码中保留了 usb_ep_conflict_check(usb_id) 的调用框架(当前注释禁用),说明端点冲突是已知风险;多类组合(如 MSD + UAC + UVC 同时挂载)时需确保各驱动的端点分配不重叠。若启用组合类后出现枚举失败,应优先核查端点分配。
模式切换时序
usb_device_mode(usb_id, 0) 先逐个 *_release 释放类,再 usb_g_hold + usb_release,并 os_time_dly(15) 等待总线稳定。若在类释放前立即切换,主机侧可能看到残留端点导致枚举异常;因此切换函数本身已内置延时,应用层不应跳过。
DMA 缓冲与对齐
EP0 的 DMA 缓冲(64 字节)在首次初始化时惰性分配并共享给端点 0~4(usb_set_dma_raddr 指向同一地址)。这意味着端点 0~4 的 DMA 读地址相同——设计上仅 EP0 控制传输使用该缓冲;若类驱动为其它端点直接复用 EP0 缓冲而非法覆盖,会造成数据错乱。批量端点应使用 usb_h_alloc_ep_buffer 或 usb_alloc_ep_dmabuffer 单独分配。
中断上下文约束
设备 ISR 以优先级 3 注册,Setup 请求在中断上下文中分发到类驱动。类驱动的请求处理回调必须保持短小,不能阻塞或调用耗时操作(如等待 Flash 擦写完成再应答 Setup),否则会拖长中断占用、影响音频等实时任务。需要长耗时操作的路径(如 MSD 的底层读写在专用任务中完成)与 EP0 应答解耦。
编译期配置错误
FUSB_MODE 未定义会直接触发 #error "USB_SPEED_MODE not defined" 编译失败——这是刻意的防呆设计,强制开发者为每个 USB 实例显式声明速度模式,避免因默认值不确定导致高速/全速行为漂移。
扩展点
- 新增设备类驱动:参照
msd.c模式,实现xxx_desc_config(描述符表)与xxx_register(usb_id)/xxx_release(usb_id),在usb_device_mode中按TCFG_USB_SLAVE_XXX_ENABLE宏追加挂载/释放分支,并通过usb_add_desc_config(usb_id, class_index++, xxx_desc_config)登记接口; - 新增主机类协议:在
apps/common/usb/host/下实现,基于usb_ctrl_transfer.h/usb_bulk_transfer.h原语构建(参考adb.c、aoa.c的既有结构); - 自定义 Setup 请求:
user_setup.c承载设备端厂商自定义请求处理,可在此扩展私有命令; - 中断亲和性:通过
usb_g_isr_reg/usb_h_isr_reg的cpu_id参数将 USB 中断绑定到特定核,为多核负载均衡提供接口。