CherryUSB 在 NXP MCX / i.MX RT 上的移植指南:chipidea + EHCI 双栈适配与 NonCacheable 配置
CherryUSB 在 NXP MCX / i.MX RT 上的移植指南:chipidea + EHCI 双栈适配与 NonCacheable 配置
本篇技术指南聚焦 CherryUSB 开源 USB 协议栈在 NXP 平台上的移植方案:官方支持 MCXN9XX/MCXN236 与 IMRT10XX/IMRT11XX 两个系列,设备端复用 chipidea IP 驱动(port/chipidea/usb_dc_chipidea.c),主机端复用 EHCI 驱动(port/ehci/usb_hc_ehci.c),平台胶水层由 port/nxp/usb_glue_mcx.c 统一提供。读完本文,你将掌握 NXP 移植所必需的三件事:如何正确修改 USB_NOCACHE_RAM_SECTION 指向 .NonCacheable 段、必须配置哪些编译宏(CONFIG_USB_EHCI_NXP、CONFIG_USB_EHCI_HCCR_OFFSET),以及 usb_glue_mcx.c 中时钟、PHY、中断与 cache 操作的具体实现细节。
支持芯片列表与硬件架构
根据 port/nxp/README.md 的官方说明,CherryUSB 的 NXP 移植端口目前支持两大芯片系列,两者均采用 chipidea(设备端)+ EHCI(主机端) 的组合架构:
| 系列 | 型号 | 设备端 IP | 主机端 IP |
|---|---|---|---|
| MCX | MCXN9XX / MCXN236 | chipidea | EHCI |
| IMRT | IMRT10XX / IMRT11XX | chipidea | EHCI |
需要说明的是,该列表与 port/ehci/README.md、port/chipidea/README.md 中记录的支持矩阵一致,即 NXP 的这两个系列在 CherryUSB 中统一走 chipidea + EHCI 的移植路径,由同一份胶水文件 port/nxp/usb_glue_mcx.c 支撑设备与主机两种模式。
MCXN9XX 与 MCXN236 属于 NXP MCX 家族中的高性能型号,集成片上高速 USB PHY;IMRT10XX/IMRT11XX 则是 i.MX RT 跨界 MCU 系列,同样具备高速 USB 外设。由于这些芯片的 USB 控制器寄存器布局与 ChipIdea IP 兼容,CherryUSB 设备栈直接复用 chipidea 通用驱动,无需单独编写设备控制器驱动;主机栈则复用 EHCI 通用驱动,配合 usb_glue_mcx.c 完成平台相关的 PHY、时钟与中断初始化。
关键配置:修改 USB_NOCACHE_RAM_SECTION 指向 .NonCacheable
原文档给出的移植第一步,也是最重要的一步,是在工程配置头文件中将 USB_NOCACHE_RAM_SECTION 宏修改为指向 NXP 的 NonCacheable 段:
#define USB_NOCACHE_RAM_SECTION __attribute__((section(".NonCacheable")))
为什么必须改这个宏
CherryUSB 的默认配置模板 cherryusb_config_template.h 中给出了该宏的默认定义:
/* attribute data into no cache ram */
#define USB_NOCACHE_RAM_SECTION __attribute__((section(".noncacheable")))
默认段名为小写的 .noncacheable。而 NXP MCUXpresso SDK 的链接脚本中,非缓存段名称是首字母大写的 .NonCacheable(对应 NonCacheable 内存区域,通常位于 RAM 中禁用 cache 属性的区段)。如果直接使用默认定义,所有标注了 USB_NOCACHE_RAM_SECTION 属性的缓冲区变量会被放到一个不存在的段中,链接时会报错或落入错误的段区域,导致 USB DMA 访问与 CPU cache 数据不一致。
移植文档 docs/zh/quick_start/transplant.rst 也明确说明:USB_NOCACHE_RAM_SECTION 宏用于将变量指定到非 cache RAM 上。因此,在 NXP 工程中必须按上文所示覆盖为 .NonCacheable。
该宏影响哪些缓冲区
在 CherryUSB 中,USB_NOCACHE_RAM_SECTION 被广泛用于标注需要 DMA 访问的缓冲区,例如:
- chipidea 设备驱动 port/chipidea/usb_dc_chipidea.c 中的 EP0/EP 请求缓冲区;
- EHCI 主机驱动 port/ehci/usb_hc_ehci.c 中的 QH/QTD 描述符池、Frame List 以及传输缓冲区。
这些内存要么被 USB 控制器通过 DMA 直接读写,要么以描述符形式被 EHCI 硬件轮询。将它们放入非 cache 区域后,CPU 与 USB 硬件看到的始终是最新数据,避免在每次传输前后手动做 cache clean/invalidate 的额外开销,这是 NXP 高速 USB 场景下保证数据一致性的根本手段。
平台编译宏:CONFIG_USB_EHCI_NXP 与 HCCR 偏移
除了 USB_NOCACHE_RAM_SECTION,在 NXP 芯片上启用 EHCI 主机还需要在配置头文件中设置两个编译宏,否则编译会直接失败。这可以从胶水文件的编译期断言看出(port/nxp/usb_glue_mcx.c):
#if !defined(CONFIG_USB_EHCI_NXP)
#error "mcx ehci must config CONFIG_USB_EHCI_NXP"
#endif
#if !defined(CONFIG_USB_EHCI_HCCR_OFFSET) || CONFIG_USB_EHCI_HCCR_OFFSET != 0x100
#error "mcx ehci must config CONFIG_USB_EHCI_HCCR_OFFSET to 0x100"
#endif
CONFIG_USB_EHCI_NXP
该宏告知 EHCI 主机驱动当前运行在 NXP 平台,需要调用 NXP 特有的 PHY 断开检测逻辑。在 port/ehci/usb_hc_ehci.c 的端口状态变化处理中可以看到:
#if defined(CONFIG_USB_EHCI_NXP)
/* kUSB_ControllerEhci0 and kUSB_ControllerEhci1*/
extern void USB_EhcihostPhyDisconnectDetectCmd(uint8_t controllerId, uint8_t enable);
USB_EhcihostPhyDisconnectDetectCmd(2 + busid, 0);
#endif
当检测到端口设备拔出(PCD 事件且当前无连接)时,驱动通过 USB_EhcihostPhyDisconnectDetectCmd 关闭 PHY 的断开检测功能,该函数实现在 port/nxp/usb_glue_mcx.c 中,通过置位/清除 USBPHY_CTRL_ENHOSTDISCONDETECT_MASK 位控制。如果漏配此宏,该段代码会被编译掉,NXP 片上 PHY 的断开检测行为异常。
CONFIG_USB_EHCI_HCCR_OFFSET
该宏表示 EHCI 能力寄存器(HCCR)相对 USB 控制器寄存器基地址的偏移。配置模板中的默认值是:
/* ---------------- EHCI Configuration ---------------- */
#define CONFIG_USB_EHCI_HCCR_OFFSET (0x0)
#define CONFIG_USB_EHCI_FRAME_LIST_SIZE 1024
#define CONFIG_USB_EHCI_QH_NUM 10
#define CONFIG_USB_EHCI_QTD_NUM (CONFIG_USB_EHCI_QH_NUM * 3)
#define CONFIG_USB_EHCI_ITD_NUM 4
而 NXP MCX/i.MX RT 的 USB 控制器内部寄存器布局中,EHCI 能力寄存器区并非从基地址 0 开始,而是偏移 0x100。EHCI 驱动的寄存器访问宏(port/ehci/usb_hc_ehci.h)正是基于该宏计算地址的:
#define EHCI_HCCR ((struct ehci_hccr *)(uintptr_t)(bus->hcd.reg_base + CONFIG_USB_EHCI_HCCR_OFFSET))
#define EHCI_HCOR ((struct ehci_hcor *)(uintptr_t)(bus->hcd.reg_base + CONFIG_USB_EHCI_HCCR_OFFSET + g_ehci_hcd[bus->hcd.hcd_id].hcor_offset))
其中 hcor_offset 是在运行时从 HCCR 的 caplength 寄存器(EHCI_HCCR->caplength)读出的(port/ehci/usb_hc_ehci.c 的 usb_hc_init)。因此 NXP 工程必须把 CONFIG_USB_EHCI_HCCR_OFFSET 设置为 0x100,否则 EHCI 驱动访问到的将是错误的能力/操作寄存器地址,主机初始化必然失败。
设备端:chipidea 驱动复用
设备模式无需额外平台宏。构建脚本会把 port/chipidea/usb_dc_chipidea.c 与 port/nxp/usb_glue_mcx.c 一起编入工程(详见下文"构建集成"),设备控制器直接复用 ChipIdea IP 通用驱动,USB_OTG_DEV 即映射到总线注册的 reg_base:
#define USB_OTG_DEV ((CHIPIDEA_TypeDef *)g_usbdev_bus[busid].reg_base)
平台胶水层源码解析:usb_glue_mcx.c
port/nxp/usb_glue_mcx.c 是 NXP 移植的核心文件,负责设备/主机两种模式下所有平台相关逻辑。它同时包含 usbd_core.h 与 usbh_core.h,并依赖 MCUXpresso SDK 的 fsl_common.h 与 fsl_cache.h,需要将 NXP SDK 头文件路径加入工程包含目录。
中断处理:USBD_IRQHandler 与 USBH_IRQHandler
文件顶部声明了两个 __WEAK 弱函数,供用户按需覆盖:
__WEAK void USBD_IRQHandler(uint8_t busid)
{
}
__WEAK void USBH_IRQHandler(uint8_t busid)
{
}
实际中断入口是 NXP 的 USB1_HS_IRQHandler,通过函数指针 g_usb_nxp_irq 分发:
void USB1_HS_IRQHandler(void)
{
g_usb_nxp_irq(0);
}
设备模式初始化时 g_usb_nxp_irq 被赋值为 USBD_IRQHandler,主机模式时被赋值为 USBH_IRQHandler,从而让同一个硬件中断入口服务于两种角色。用户如需在中断中追加处理(例如清中断标志、通知 RTOS),可覆盖对应的弱函数。
设备模式低层初始化
设备模式的平台初始化在 usb_dc_low_level_init 中完成(port/nxp/usb_glue_mcx.c):
void usb_dc_low_level_init(uint8_t busid)
{
USB_ClockInit();
g_usb_nxp_irq = USBD_IRQHandler;
/* Install isr, set priority, and enable IRQ. */
NVIC_SetPriority((IRQn_Type)USB1_HS_IRQn, 3);
EnableIRQ((IRQn_Type)USB1_HS_IRQn);
}
void usb_dc_low_level_deinit(uint8_t busid)
{
DisableIRQ((IRQn_Type)USB1_HS_IRQn);
}
USB_ClockInit() 完成电源(DCDC/CORELDO 电平配置)、系统振荡器(SCG 20~30MHz XTAL 使能)、USB 时钟与片上高速 PHY 的完整初始化,其中关键步骤包括:
- 通过
SCG0->SOSCCFG配置外部晶振范围并等待SOSCVLD有效; CLOCK_EnableUsbhsPhyPllClock(kCLOCK_Usbphy480M, 24000000U)使能 480MHz USB PHY PLL;- 调用
USB_EhciPhyInit(kUSB_ControllerEhci0, BOARD_XTAL0_CLK_HZ, &phyConfig)初始化 PHY。
USB_EhciPhyInit 内部会做 PHY 上电(PWD = 0)、使能 UTMI Level2/Level3(支持 LS 设备与外部 FS Hub 下的 LS 设备)、设置 PLL 关闭自动恢复位,并按 phyConfig 校准高速 TX 驱动(D_CAL、TXCAL45DP、TXCAL45DM 三个 trim 参数,见文件中 BOARD_USB_PHY_D_CAL 等宏定义)。PHY 基地址通过 USB_EhciPhyGetBase(controllerId) 从 USBPHY_STACK_BASE_ADDRS / USBPHY_BASE_ADDRS 数组获取。
主机模式低层初始化
主机模式的平台初始化对应 usb_hc_low_level_init 与 usb_hc_low_level2_init:
void usb_hc_low_level_init(struct usbh_bus *bus)
{
USB_ClockInit();
g_usb_nxp_irq = USBH_IRQHandler;
/* Install isr, set priority, and enable IRQ. */
NVIC_SetPriority((IRQn_Type)USB1_HS_IRQn, 3);
EnableIRQ((IRQn_Type)USB1_HS_IRQn);
}
void usb_hc_low_level2_init(struct usbh_bus *bus)
{
usb_host_mode_init((CHIPIDEA_TypeDef *)(bus->hcd.reg_base));
}
usb_host_mode_init 将控制器切入主机模式:清除 USBMODE 的控制器模式位并置为 Host 模式(USB_USBMODE_CM_SET(3))、清除大小端位、配置 PORTSC1 的并行接口信号与收发器宽度、关闭中断阈值。该函数在 usb_hc_low_level2_init(EHCI 驱动的 __WEAK 钩子,见 port/ehci/usb_hc_ehci.c)中调用,属于主机控制器复位之后的第二步初始化,其注释明确要求"必须在复位后立即设置模式"。
主机侧还实现了 usbh_get_port_speed,从 PORTSC1 的 PSPD 字段读取端口速率,将 0x00/0x01/0x02 分别映射为 USB_SPEED_FULL / USB_SPEED_LOW / USB_SPEED_HIGH,其中识别到高速设备时会调用 USB_EhcihostPhyDisconnectDetectCmd 开启 PHY 断开检测。
Cache 操作:对接 fsl_cache
当配置了 CONFIG_USB_DCACHE_ENABLE 时,胶水文件提供三个 cache 操作函数,将 CherryUSB 的 cache 接口桥接到 NXP SDK 的 fsl_cache 驱动:
#ifdef CONFIG_USB_DCACHE_ENABLE
void usb_dcache_clean(uintptr_t addr, size_t size)
{
DCACHE_CleanByRange(addr, size);
}
void usb_dcache_invalidate(uintptr_t addr, size_t size)
{
DCACHE_InvalidateByRange(addr, size);
}
void usb_dcache_flush(uintptr_t addr, size_t size)
{
DCACHE_CleanInvalidateByRange(addr, size);
}
#endif
这三个函数分别对应数据 cache 的 clean(回写)、invalidate(失效)与 flush(回写并失效),由 EHCI 驱动在传输描述符处理前后调用(例如 port/ehci/usb_hc_ehci.c 中扫描异步/周期链表前后的 usb_dcache_invalidate)。配套地,配置模板中 CONFIG_USB_DCACHE_ENABLE 开启后还会要求 CONFIG_USB_ALIGN_SIZE 为 32 或 64(cherryusb_config_template.h),保证 DMA 缓冲区对齐到 cache line。
构建集成方式
NXP 移植端口已接入 CherryUSB 的两种主流构建脚本,通过"设备控制器选型"条件同时拉入 chipidea 设备驱动与 NXP 胶水文件。
CMake 构建脚本 cherryusb.cmake 中的组合:
elseif(CONFIG_CHERRYUSB_DEVICE_CHIPIDEA_MCX)
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/chipidea/usb_dc_chipidea.c)
list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/nxp/usb_glue_mcx.c)
RT-Thread SCons 构建脚本 SConscript 中的组合:
if GetDepend(['PKG_CHERRYUSB_DEVICE_CHIPIDEA_MCX']):
path += [cwd + '/port/chipidea']
src += Glob('port/chipidea/usb_dc_chipidea.c')
src += Glob('port/nxp/usb_glue_mcx.c')
在配置 CherryUSB 时,只需选择 CONFIG_CHERRYUSB_DEVICE_CHIPIDEA_MCX(CMake/Kconfig)或使能 PKG_CHERRYUSB_DEVICE_CHIPIDEA_MCX(RT-Thread),构建系统就会自动把设备控制器驱动与 NXP 平台胶水代码编入工程。主机侧则需要启用 EHCI 驱动(对应 port/ehci 目录)并正确设置上文两个编译宏。
移植步骤总结与常见问题排查
综合以上内容,在 NXP MCXN9XX/MCXN236、IMRT10XX/IMRT11XX 上使用 CherryUSB 的完整移植步骤如下:
- 配置头文件(如
cherryusb_config.h):将USB_NOCACHE_RAM_SECTION覆盖为__attribute__((section(".NonCacheable"))); - 主机模式必须:定义
CONFIG_USB_EHCI_NXP,并将CONFIG_USB_EHCI_HCCR_OFFSET设为0x100(默认是0x0); - 使能 DMA cache 支持(可选):定义
CONFIG_USB_DCACHE_ENABLE,并保持CONFIG_USB_ALIGN_SIZE为 32 或 64; - 集成构建:CMake 选择
CONFIG_CHERRYUSB_DEVICE_CHIPIDEA_MCX,或 RT-Thread 使能PKG_CHERRYUSB_DEVICE_CHIPIDEA_MCX,并加入 MCUXpresso SDK 的fsl_common.h/fsl_cache.h头文件路径; - 中断:确保
USB1_HS_IRQHandler正常链接(由胶水文件提供),如需自定义处理可覆盖USBD_IRQHandler/USBH_IRQHandler弱函数。
常见编译错误与对策:
#error "mcx ehci must config CONFIG_USB_EHCI_NXP"—— 主机模式下漏定义CONFIG_USB_EHCI_NXP;#error "mcx ehci must config CONFIG_USB_EHCI_HCCR_OFFSET to 0x100"——CONFIG_USB_EHCI_HCCR_OFFSET未设置为0x100;- 链接器提示
.noncacheable段不存在或段地址错误 ——USB_NOCACHE_RAM_SECTION未改为.NonCacheable,缓冲区落入了错误的段。
按上述步骤完成配置后,即可在 NXP MCX / i.MX RT 芯片上同时运行 CherryUSB 的 USB 设备栈(如 CDC、HID、MSC 等 class,参考 demo 目录下的模板)与 EHCI 主机栈(如 usbh_msc.c、usbh_hid.c 等),实现单芯片上的 USB Host/Device 功能。