CherryUSB 在 NXP CHIPIDEA 平台上的移植与配置指南:USB_NOCACHE_RAM_SECTION 与 DCD 驱动剖析
CherryUSB 在 NXP CHIPIDEA 平台上的移植与配置指南:USB_NOCACHE_RAM_SECTION 与 DCD 驱动剖析
CHIPIDEA 是 NXP 部分 MCU 内置的 USB 控制器 IP(常与 EHCI 主机控制器搭配使用),CherryUSB 为其提供了一套独立的设备控制器(DCD)驱动实现。本指南以仓库中 port/chipidea/README.md 为骨架,结合 usb_dc_chipidea.c 与寄存器定义源码,完整讲解支持芯片范围、非缓存 RAM 段配置方法,以及队列头(qHD)/队列描述符(qTD)驱动的底层工作机理,帮助你在 IMRT、MCX 系列芯片上完成 CherryUSB 设备栈的快速移植与排障。
一、驱动定位:CherryUSB 的 CHIPIDEA 设备控制器(DCD)支持
CherryUSB 将 USB 控制器 IP 抽象为统一的 DCD(Device Controller Driver)接口,上层设备栈通过 usbd_initialize、usbd_ep_open、usbd_ep_start_write/read 等标准 API 与控制器交互。针对 CHIPIDEA IP,CherryUSB 在 port/chipidea 目录下提供了三个文件:
usb_dc_chipidea.c:DCD 驱动实现(设备模式);usb_chipidea_reg.h:CHIPIDEA 控制器寄存器映射与位域宏(约 2200 余行,覆盖 GPTIMER、USBCMD、USBSTS、ENDPTCTRL 等全部寄存器);README.md:支持芯片列表与关键移植注意项。
从源码结构看,该驱动面向 设备(Device)模式 设计,其内部核心数据结构 dcd_data_t 在 usb_dc_chipidea.c 中定义,由每个端点的队列头和队列描述符数组组成:
typedef struct {
dcd_qhd_t qhd[CONFIG_USBDEV_EP_NUM * 2];
dcd_qtd_t qtd[CONFIG_USBDEV_EP_NUM * 2 * QTD_COUNT_EACH_ENDPOINT];
} dcd_data_t;
其中 CONFIG_USBDEV_EP_NUM 未定义时默认为 8(usb_dc_chipidea.c),即最多支持 8 个双向端点(16 个端点索引);QTD_COUNT_EACH_ENDPOINT 固定为 8,表示每个端点最多挂接 8 个传输描述符,单次传输最大拆分为 8 段、每段不超过 0x4000(16 KB)字节。
二、支持芯片列表:chipidea + EHCI 组合
根据 port/chipidea/README.md 与 port/ehci/README.md(后者汇总了各平台组合)的记载,CHIPIDEA 设备控制器当前支持以下 NXP 芯片:
| 芯片系列 | USB 控制器组合 |
|---|---|
| IMRT10XX / IMRT11XX | chipidea(设备)+ EHCI(主机) |
| MCXN9XX / MCXN236 | chipidea(设备)+ EHCI(主机) |
这里的 "chipidea + EHCI" 含义是:同一颗芯片的 USB 控制器在设备角色下由本目录的 usb_dc_chipidea.c 驱动,在主机角色下则由 EHCI 主机驱动 port/ehci/usb_hc_ehci.c 接管。NXP 之外,CHIPIDEA 形态的控制器还广泛存在于 HPMicro 全系列(见 port/ehci/README.md),HPMicro 平台使用同一套寄存器定义与驱动结构,仅在非缓存段命名上有所差异(下文详述)。
另外,docs/zh/quick_start/demo.rst 在快速上手章节也确认:NXP MCX 系列芯片对应的 USB IP 即为 CHIPIDEA/kinetis。
三、核心移植注意项:修改 USB_NOCACHE_RAM_SECTION
原文档给出的唯一必须修改项如下:
Modify USB_NOCACHE_RAM_SECTION
#define USB_NOCACHE_RAM_SECTION __attribute__((section(".NonCacheable")))
3.1 为什么 CHIPIDEA 驱动必须使用非缓存段
CHIPIDEA 控制器通过 DMA 直接读写内存中的 qHD/qTD 队列与传输缓冲区。在 Cortex-M 系列上,若这些结构被放置在带 cache 的 RAM 中,DMA 写入的数据可能仍停留在 cache 中而未被回写(或在 cache 中已被修改而 DMA 读到的是旧数据),导致传输描述符状态不同步、传输异常。因此驱动将关键数据结构显式放入非缓存段:
static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data0;
#if CONFIG_USBDEV_MAX_BUS == 2
static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data1;
#endif
见 usb_dc_chipidea.c。这里同时要求 2048 字节对齐——qHD 数组(EP_NUM*2 个、每个 64 字节)正好构成 2048 字节的整块,满足控制器对端点列表基地址对齐的要求(初始化时 ENDPTLISTADDR 写入了 dcd_data->qhd 的基地址,见 usb_dc_chipidea.c)。
3.2 不同平台的宏定义差异
USB_NOCACHE_RAM_SECTION 是 CherryUSB 的公共配置宏,在 cherryusb_config_template.h 中默认定义为:
#define USB_NOCACHE_RAM_SECTION __attribute__((section(".noncacheable")))
各平台需根据链接脚本中实际存在的段名覆盖该宏。仓库内的真实示例包括:
| 平台/配置文件 | 段名 |
|---|---|
| NXP IMRT/MCX(见 port/chipidea/README.md、port/nxp/README.md) | .NonCacheable |
| HPMicro 测试工程 tests/hpmicro/inc/usb_config.h | .noncacheable.non_init |
| 飞腾 pusb2/pusb3 平台 port/pusb2/freertos/usb_config.h | .noncacheable |
移植时请务必确认芯片 SDK 的链接脚本中存在对应命名段,若 SDK 中段名不同(例如大小写、后缀不同),需同步修改宏或链接脚本,否则驱动初始化时 _dcd_data0 可能落入普通 RAM,产生难以排查的传输偶发错误。
3.3 与 Cache 使能配置的配合
除数据结构放入非缓存段外,驱动还支持在使能数据 cache 的场景下对用户缓冲区做显式维护。在 usbd_ep_start_write 与 usbd_ep_start_read 中可以看到:
usb_dcache_clean((uintptr_t)data, USB_ALIGN_UP(data_len, CONFIG_USB_ALIGN_SIZE));
...
usb_dcache_invalidate((uintptr_t)data, USB_ALIGN_UP(data_len, CONFIG_USB_ALIGN_SIZE));
即在发送前对数据做 cache clean(回写),接收完成后对缓冲区做 invalidate,保证 DMA 与 CPU 视图一致。当工程定义了 CONFIG_USB_DCACHE_ENABLE 时,驱动还会断言用户缓冲区地址按 CONFIG_USB_ALIGN_SIZE 对齐(见 usb_dc_chipidea.c)。CONFIG_USB_ALIGN_SIZE 在 cherryusb_config_template.h 中默认 4(不使能 cache 时),HPMicro 测试工程则配置为 HPM_L1C_CACHELINE_SIZE(见 tests/hpmicro/inc/usb_config.h),NXP 平台移植时可按同样思路设置为对应 cache line 大小。
四、源码级原理:从寄存器到队列传输
4.1 寄存器映射与端点控制
usb_chipidea_reg.h 将控制器定义为 CHIPIDEA_TypeDef 结构体,核心寄存器包括:
USBCMD/USBSTS/USBINTR:命令、状态与中断使能;ENDPTLISTADDR:端点列表(qHD 数组)基地址;ENDPTCTRL[8]:每个物理端点的控制寄存器(STALL、TYPE、TOGGLE、ENABLE 位);ENDPTPRIME/ENDPTFLUSH/ENDPTSTAT:端点启动、冲刷与状态;ENDPTSETUPSTAT/ENDPTCOMPLETE:SETUP 接收状态与传输完成状态;PORTSC1/USBMODE/OTGSC:端口状态、模式选择与 OTG 控制。
驱动初始化流程 chipidea_init(usb_dc_chipidea.c)按顺序完成:控制器复位 → 设置 USBMODE 为设备模式(CM=2)→ 关闭 Setup Lockout(SLOM)→ 配置小端序与并行接口 → 使能 VBUS 放电。其中关闭 Setup Lockout 是为了让 SETUP 包处理更符合软件栈的期望时序。
4.2 总线复位与端点建立
收到总线复位中断后,chipidea_bus_reset(usb_dc_chipidea.c)会:
- 清空全部 qHD/qTD 内存;
- 将未配置的端点默认设置为 Bulk 类型(避免未配置端点保持 Control 类型导致 PID 跟踪异常);
- 为端点 0 OUT/IN 建立 qHD,写入
ep0_max_packet_size,并设置int_on_setup,从而在收到 SETUP 包时产生中断。
4.3 传输发起:qTD 链式拆分
chipidea_start_xfer(usb_dc_chipidea.c)负责把一次软件层的传输请求拆分为若干 16 KB 以内的 qTD 并链接成链表:
- 每个 qTD 通过
usb_qtd_init初始化,buffer<a href="https://link.gitcode.com/i/64d4c7703d268cebffb453c9ea056678" target="_blank">0]保存缓冲区首地址,后续 4 个 buffer 指针按 4 KB 页递增填充(见 [usb_dc_chipidea.c); - 最后一个 qTD 设置
int_on_complete,传输完成后触发中断; - 若拆分数量超过
QTD_COUNT_EACH_ENDPOINT(8 个)则直接返回失败; - 最后将首个 qTD 挂到 qHD 的
qtd_overlay.next,并通过写ENDPTPRIME启动 DMA 传输。
4.4 中断处理与完成回调
USBD_IRQHandler(usb_dc_chipidea.c)统一处理各类事件:
- 复位中断:复位端点状态并回调
usbd_event_reset_handler; - 挂起/恢复:通过
PORTSC1的 SUSP 位判断,分别回调usbd_event_suspend_handler/usbd_event_resume_handler; - 端口变化:依据
CCS位回调 connect/disconnect; - USB 中断:处理
ENDPTCOMPLETE完成事件与ENDPTSETUPSTATSETUP 事件。完成事件会遍历链式 qTD,累加实际传输字节数,若 qTD 出现halted/xact_err/buffer_err则上报传输错误;SETUP 事件则利用SUTW(Setup Write)机制从 qHD 的 setup_request 区域安全拷贝 8 字节设置包后回调usbd_event_ep0_setup_complete_handler。
由此可见,上层 CherryUSB 设备栈(如 core/usbd_core.c 的 usbd_initialize,它负责保存 reg_base 并调用 usb_dc_init)与底层 CHIPIDEA 驱动通过标准事件回调解耦,迁移到 IMRT/MCX 芯片时无需修改任何上层类实现。
五、移植接入步骤小结
- 配置非缓存段:在工程的
usb_config.h中按 3.2 节表格覆盖USB_NOCACHE_RAM_SECTION为 NXP 链接脚本实际的非缓存段名(如.NonCacheable),并确认_dcd_data0被正确放置。 - 实现底层钩子:驱动声明了
usb_dc_low_level_init/usb_dc_low_level_deinit(见 usb_dc_chipidea.c),需要你在板级代码中实现时钟使能、引脚复用、控制器供电与中断号配置。 - 注册总线与中断:调用
usbd_initialize(busid, reg_base, event_handler)时传入 CHIPIDEA 控制器的寄存器基地址;在芯片的 USB 中断服务函数中调用USBD_IRQHandler(busid)(注意:该驱动按busid参数区分多实例,CONFIG_USBDEV_MAX_BUS == 2时第二个实例使用_dcd_data1)。 - 按需调节端点数量:若芯片实际端点少于 8,可通过
CONFIG_USBDEV_EP_NUM收紧驱动占用的内存与 qHD/qTD 数组规模。
六、常见问题与排查建议
- 枚举后数据传输偶发失败:优先检查
USB_NOCACHE_RAM_SECTION段名是否与链接脚本一致,以及是否误把 qHD/qTD 放进了 cache 区;其次检查用户缓冲区在CONFIG_USB_DCACHE_ENABLE下是否满足CONFIG_USB_ALIGN_SIZE对齐。 - SETUP 包无响应或响应错乱:确认初始化时已按 4.1 节关闭 Setup Lockout,且
ENDPTSETUPSTAT中断路径未被打断(驱动在 EP0 传输前会等待 SETUP 状态位清空,见 usb_dc_chipidea.c)。 - 传输长度受限:单次端点传输超过 8 个 qTD(约 128 KB,每 qTD 16 KB)时
chipidea_start_xfer会返回失败,上层需自行拆分大块传输。 - 速度识别:
usbd_get_port_speed从PORTSC1的 PSPD 字段读取速度并映射为USB_SPEED_LOW/FULL/HIGH(见 usb_dc_chipidea.c),若枚举速度异常可从该函数返回值排查控制器侧配置。
本指南覆盖了原文档全部要点(NXP IMRT/MCX 支持列表与 USB_NOCACHE_RAM_SECTION 修改),并进一步从 usb_dc_chipidea.c 与 usb_chipidea_reg.h 源码层面解释了非缓存段需求、qHD/qTD 队列机制与中断处理链路,可作为在 NXP CHIPIDEA 平台移植 CherryUSB 的完整参考。