CherryUSB 在 NXP MCX / i.MX RT 上的移植指南:chipidea + EHCI 双栈适配与 NonCacheable 配置

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

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 访问的缓冲区,例如:

这些内存要么被 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 的完整移植步骤如下:

  1. 配置头文件(如 cherryusb_config.h):将 USB_NOCACHE_RAM_SECTION 覆盖为 __attribute__((section(".NonCacheable")));
  2. 主机模式必须:定义 CONFIG_USB_EHCI_NXP,并将 CONFIG_USB_EHCI_HCCR_OFFSET 设为 0x100(默认是 0x0);
  3. 使能 DMA cache 支持(可选):定义 CONFIG_USB_DCACHE_ENABLE,并保持 CONFIG_USB_ALIGN_SIZE 为 32 或 64;
  4. 集成构建:CMake 选择 CONFIG_CHERRYUSB_DEVICE_CHIPIDEA_MCX,或 RT-Thread 使能 PKG_CHERRYUSB_DEVICE_CHIPIDEA_MCX,并加入 MCUXpresso SDK 的 fsl_common.h/fsl_cache.h 头文件路径;
  5. 中断:确保 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 功能。

登录后查看全文
CherryUSB