CherryUSB 在 NXP CHIPIDEA 平台上的移植与配置指南:USB_NOCACHE_RAM_SECTION 与 DCD 驱动剖析

原创2026-10-13 18:31:541,702 阅读
文章标签:嵌入式物联网驱动开发

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)会:

  1. 清空全部 qHD/qTD 内存;
  2. 将未配置的端点默认设置为 Bulk 类型(避免未配置端点保持 Control 类型导致 PID 跟踪异常);
  3. 为端点 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 完成事件与 ENDPTSETUPSTAT SETUP 事件。完成事件会遍历链式 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 芯片时无需修改任何上层类实现。

五、移植接入步骤小结

  1. 配置非缓存段:在工程的 usb_config.h 中按 3.2 节表格覆盖 USB_NOCACHE_RAM_SECTION 为 NXP 链接脚本实际的非缓存段名(如 .NonCacheable),并确认 _dcd_data0 被正确放置。
  2. 实现底层钩子:驱动声明了 usb_dc_low_level_init / usb_dc_low_level_deinit(见 usb_dc_chipidea.c),需要你在板级代码中实现时钟使能、引脚复用、控制器供电与中断号配置。
  3. 注册总线与中断:调用 usbd_initialize(busid, reg_base, event_handler) 时传入 CHIPIDEA 控制器的寄存器基地址;在芯片的 USB 中断服务函数中调用 USBD_IRQHandler(busid)(注意:该驱动按 busid 参数区分多实例,CONFIG_USBDEV_MAX_BUS == 2 时第二个实例使用 _dcd_data1)。
  4. 按需调节端点数量:若芯片实际端点少于 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 的完整参考。

登录后查看全文
CherryUSB