Zephyr RTOS在NXP i.MX 95平台的移植与驱动开发实战指南

发布时间:2026/6/21 8:18:04

Zephyr RTOS在NXP i.MX 95平台的移植与驱动开发实战指南 1. 项目概述与i.MX 95平台解析如果你正在寻找一个能够驾驭高性能、异构多核处理器的实时操作系统RTOS并且厌倦了在不同硬件平台间移植代码的繁琐那么Zephyr RTOS与NXP i.MX 95的组合绝对值得你投入时间深入研究。我最近在i.MX 95 EVK开发板上完整走了一遍Zephyr的移植、构建、部署和驱动开发流程整个过程既有踩坑的艰辛也有跑通第一个“Hello World”时的畅快。i.MX 95这颗芯片定位非常清晰就是为安全、高效、智能的边缘计算而生它集成了强大的Cortex-A55核心、NPU、GPU以及丰富的安全特性目标直指汽车电子、工业物联网、医疗设备这些对实时性和可靠性要求严苛的领域。而Zephyr RTOS作为Linux基金会旗下的开源项目其最大的魅力在于它的“一次编写随处运行”潜力。它通过一套精巧的设备树DTS和Kconfig配置系统将硬件描述、驱动选择、功能裁剪完全解耦。这意味着为i.MX 95编写的驱动和应用经过适配后理论上可以相对平滑地迁移到其他同样支持Zephyr的Arm Cortex-A系列平台上。这种可移植性对于需要跨平台产品线的团队来说价值巨大。本文的目的就是把我在这块板子上从零开始让Zephyr跑起来并点亮几个关键外设的实战经验掰开揉碎了分享给你。无论你是刚接触Zephyr的新手还是想将现有项目迁移到i.MX 95的资深工程师相信这些从官方文档到实操落地的细节都能给你带来直接的帮助。2. 开发环境搭建与源码获取动手之前一个稳定、高效的开发环境是基石。Zephyr的开发主要依赖west这个多仓库管理工具以及相应的工具链。我的工作环境是Ubuntu 22.04 LTS以下步骤在全新系统中已验证通过。2.1 基础依赖安装首先我们需要安装一系列基础编译工具和Python依赖。Zephyr的构建系统对Python版本有一定要求建议使用Python 3.8及以上。sudo apt update sudo apt install --no-install-recommends git cmake ninja-build gperf \ ccache dfu-util device-tree-compiler wget \ python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \ make gcc gcc-multilib g-multilib libsdl2-dev libmagic1接下来安装west工具。我强烈建议使用pip安装在用户目录下避免与系统包管理器冲突。pip3 install --user -U west echo export PATH~/.local/bin:$PATH ~/.bashrc source ~/.bashrc安装完成后可以通过west --version来验证是否安装成功。2.2 获取Zephyr源码与SDKZephyr的源码通过west进行初始化和管理。我们选择一个工作目录例如~/zephyrproject然后进行初始化。cd ~ west init zephyrproject cd zephyrproject west update这个过程会克隆Zephyr的主仓库以及所有在west.yml中定义的模块包括SoC支持、硬件抽象层HAL等。对于i.MX 95其SoC支持代码位于soc/nxp/imx/imx9/目录下板级支持包BSP则在boards/nxp/imx95_evk/。west update完成后这些代码就已经在你的本地了。紧接着安装Zephyr SDK。SDK中包含了为多种架构包括Arm Cortex-A预编译的工具链这是编译Zephyr应用所必需的。cd ~ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5/zephyr-sdk-0.16.5_linux-x86_64.tar.xz wget -O - https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5/sha256.sum | shasum --check --ignore-missing tar xvf zephyr-sdk-0.16.5_linux-x86_64.tar.xz cd zephyr-sdk-0.16.5 ./setup.sh在运行setup.sh时它会询问是否将工具链路径添加到用户环境变量中选择“是”。它会修改~/.zephyrrc文件后续构建时CMake会自动找到正确的工具链。2.3 环境变量配置为了让后续的构建命令更简洁我们设置两个常用的环境变量。ZEPHYR_BASE指向Zephyr源码根目录而将工具链路径加入PATH可以确保命令行直接调用。echo export ZEPHYR_BASE~/zephyrproject/zephyr ~/.bashrc echo export PATH~/zephyr-sdk-0.16.5/arm-zephyr-eabi/bin:$PATH ~/.bashrc source ~/.bashrc至此你的开发环境就已经准备就绪了。你可以通过west build -h和arm-zephyr-eabi-gcc --version来双重验证环境配置是否正确。一个常见的坑是多个工具链路径冲突如果遇到编译错误首先检查PATH环境变量中哪个工具链被优先使用。3. i.MX 95 EVK硬件连接与软件准备硬件平台是NXP官方的i.MX 95 EVK评估板。要让板子“说话”第一步是建立串口通信。板载的调试接口J31是一个USB转多路串口的FTDI芯片连接到电脑后通常会枚举出4个串口设备。3.1 串口终端配置在Linux系统下插入USB调试线后使用dmesg | tail或ls /dev/ttyUSB*命令可以查看新增的设备。通常用于U-Boot和Linux主控台的是第三个串口例如/dev/ttyUSB2。这里有个关键细节这个顺序可能会因为系统已有USB串口设备而变化所以务必通过dmesg日志确认具体的设备号而不是想当然地使用ttyUSB2。我习惯使用minicom作为终端工具配置如下sudo minicom -s进入配置界面后选择“Serial port setup”。将串口设备Serial Device设置为/dev/ttyUSB2请根据实际情况修改波特率Bps/Par/Bits设置为115200 8N1即115200波特8位数据位1位停止位无校验硬件流控Hardware Flow Control和软件流控Software Flow Control都设置为“No”。保存为默认配置Save setup as dfl后退出。这样每次打开minicomsudo minicom就会自动连接。注意在后续操作U-Boot或加载Zephyr时请确保这个终端窗口保持打开和连接状态它是我们与开发板交互的唯一窗口。3.2 准备启动介质SD卡为了运行Zephyr我们需要一个先导环境。最方便的方法是使用NXP官方提供的“Real-time Edge”软件包预编译的SD卡镜像。这个镜像包含了U-Boot、Linux设备树以及一个最小的根文件系统更重要的是它内置了通过U-Boot命令启动和停止Cortex-A核心上RTOS如Zephyr的功能。下载镜像访问NXP官网的Real-time Edge软件发布页面找到对应i.MX 95 EVK的最新版本镜像例如Real-time_Edge_v3.x_IMX95-19X19-LPDDR5-EVK.zip下载并解压。我们需要的是里面的.wic文件如nxp-image-real-time-edge-imx95-19x19-lpddr5-evk.rootfs.wic。烧录镜像将一张至少8GB的SD卡插入读卡器并连接到电脑。首先使用lsblk命令确认SD卡在系统中的设备节点例如/dev/sdb请务必确认无误否则可能格式化错误磁盘。然后使用dd命令进行烧录sudo dd if./nxp-image-real-time-edge-imx95-19x19-lpddr5-evk.rootfs.wic of/dev/sdX bs1M statusprogress sync将/dev/sdX替换为你的SD卡设备节点。bs1M设置块大小以提高速度statusprogress可以显示烧录进度最后的sync确保所有数据写入磁盘。配置启动模式将烧录好的SD卡插入i.MX 95 EVK板的卡槽。找到板上的启动模式开关SW7将其[1:4]位拨到“1011”即1、3、4为ON2为OFF这表示从SD卡启动。最后连接12V电源适配器并上电。如果一切顺利你将在minicom终端中看到TF-ATrusted Firmware-A和U-Boot的启动日志。当出现u-boot提示符时说明U-Boot已启动并等待命令。此时我们的软件基础环境就搭建完成了。4. Zephyr应用构建与镜像生成有了基础环境我们就可以开始编译自己的Zephyr应用了。我们从最简单的hello_world开始验证整个工具链和BSP是否工作正常。4.1 构建第一个应用进入Zephyr的工作目录使用west build命令进行构建。-b参数指定目标板对于i.MX 95 EVK的Cortex-A55核心板子名称是imx95_evk/mimx9596/a55。-p always确保每次构建前都清除之前的构建缓存。cd ~/zephyrproject/zephyr west build -p always -b imx95_evk/mimx9596/a55 samples/hello_world/构建过程会持续几分钟期间会下载必要的工具链组件如果第一次构建并编译Zephyr内核、BSP以及我们的hello_world示例。如果构建成功你会在build/zephyr/目录下找到两个关键文件zephyr.elf: 包含调试信息的ELF格式文件。zephyr.bin: 纯二进制镜像文件这就是我们需要加载到开发板内存并执行的文件。4.2 构建系统深度解析Kconfig与设备树构建命令背后Zephyr的构建系统做了大量工作核心是Kconfig和设备树DTS。Kconfig负责软件功能的配置。当你执行west build时系统会读取boards/nxp/imx95_evk/imx95_evk_mimx9596_a55_defconfig这个板级默认配置以及应用目录下的prj.conf如果有。你可以通过menuconfig进行交互式配置west build -t menuconfig在这里你可以启用或禁用内核特性、驱动、协议栈等。例如为了使用LPUART驱动需要确保CONFIG_SERIALy和CONFIG_UART_MCUX_LPUARTy被启用。构建系统会最终生成一个autoconf.h头文件供源码编译时使用。设备树DTS负责硬件描述。它以一种与硬件无关的树形结构描述了SoC上所有外设的寄存器地址、中断号、时钟源、引脚复用等。对于i.MX 95SoC级定义在dts/arm64/nxp/nxp_mimx95_a55.dtsi。板级定义在boards/nxp/imx95_evk/imx95_evk_mimx9596_a55.dts和imx95_evk-pinctrl.dtsi。 构建时这些.dts和.dtsi文件会被编译成二进制的.dtb文件并最终链接进Zephyr镜像。设备树是Zephyr驱动模型的基础驱动通过compatible属性来匹配设备树节点从而获取硬件资源。理解这两者是进行后续驱动开发和定制化移植的关键。一个常见的错误是修改了设备树但忘记在Kconfig中启用对应的驱动导致设备无法初始化。5. 镜像部署与U-Boot启动实战编译出zephyr.bin后下一步就是把它弄到板子上跑起来。i.MX 95平台支持异构多核U-Boot运行在主核Core0上并提供了命令来在其他从核如Core1上启动RTOS。5.1 镜像部署到开发板我们需要将zephyr.bin文件传输到开发板的内存或存储中。有三种常用方法方法一通过TFTP网络下载推荐速度快这需要你的开发主机和开发板在同一个局域网并且主机开启了TFTP服务。在主机上将zephyr.bin放入TFTP服务器目录例如/tftpboot。在U-Boot命令行中配置网络如果尚未配置u-boot setenv ipaddr 192.168.1.100 # 开发板IP u-boot setenv serverip 192.168.1.50 # TFTP服务器IP u-boot setenv netmask 255.255.255.0使用tftp命令下载镜像到内存地址0xd0000000这是一个在Linux内核占用区域之外的安全内存地址u-boot tftp 0xd0000000 zephyr.bin方法二通过SD卡加载如果已经按照3.2节准备了SD卡其第一个分区是FAT格式。你可以将zephyr.bin复制到这个分区。在Linux主机上SD卡挂载后直接复制文件cp zephyr.bin /media/$USER/BOOT/在U-Boot中从SD卡加载到内存u-boot fatload mmc 1:1 0xd0000000 zephyr.binmmc 1:1表示第一个MMC设备SD卡的第一个分区。方法三通过串口Ymodem传输无需网络当网络和SD卡都不方便时可以使用串口直接传输虽然速度较慢。在U-Boot命令行输入u-boot loady 0xd0000000此时U-Boot进入等待接收状态。在minicom中按下CtrlA然后按Z调出命令菜单再按SSend files选择传输协议为Ymodem然后找到并选择本地的zephyr.bin文件开始传输。无论哪种方法目标都是将zephyr.bin加载到内存的指定地址如0xd0000000。5.2 U-Boot启动命令详解镜像加载到内存后就可以使用U-Boot命令启动了。i.MX 95的U-Boot支持多核操作。在从核Core1上启动Zephyr这是最常用的场景让Zephyr作为一个独立的实时任务运行在某个从核上与主核上的Linux互不干扰。u-boot dcache flush u-boot icache flush u-boot cpu 1 release 0xd0000000dcache flush和icache flush至关重要。这两个命令用于刷新数据缓存和指令缓存确保内存中的最新镜像内容被CPU正确读取。如果跳过此步核心可能会执行到旧的或错误的指令导致启动失败或运行异常。cpu 1 release 0xd0000000命令cpu用于多核操作。1是核心编号i.MX 95的Cortex-A55核心编号通常从0开始。release指令让核心1从内存地址0xd0000000开始执行。执行后U-Boot命令行会立即返回核心1开始独立运行Zephyr。在主核Core0上启动Zephyr如果你希望Zephyr独占整个系统不启动Linux可以在Core0上启动。但注意这会使U-Boot退出无法再使用U-Boot命令行。u-boot dcache flush u-boot icache flush u-boot go 0xd0000000go命令会让当前执行流Core0跳转到指定地址执行不会返回。核心状态管理cpu status查看所有Cortex-A核心的运行状态running/off。cpu 1 disable如果核心1正在运行此命令可以将其关闭下电。执行cpu 1 release后如果Zephyr镜像和串口配置正确你应该立即在串口终端中看到Zephyr的启动日志最后是Hello World! imx95_evk_mimx9596_a55的输出。恭喜你Zephyr已经在i.MX 95上成功运行了6. 关键外设驱动配置与集成指南让系统跑起来只是第一步让外设工作起来才是项目落地的关键。i.MX 95的Zephyr BSP已经支持了部分核心外设驱动其集成方式深刻体现了Zephyr的硬件抽象思想。6.1 中断控制器GIC v3在Armv8-A多核系统中通用中断控制器GIC是所有外设中断的中枢。i.MX 95使用GIC v3。在Zephyr中其驱动是drivers/interrupt_controller/intc_gicv3.c通过CONFIG_GIC_V3配置项启用。设备树配置在SoC的DTSI文件nxp_mimx95_a55.dtsi中GIC节点已经定义好包括分发器Distributor和重分发器Redistributor的寄存器地址。gic: interrupt-controller38800000 { compatible arm,gic-v3; reg 0x38800000 0x10000, /* GICD */ 0x38880000 0xc0000; /* GICR */ interrupt-controller; #interrupt-cells 4; status okay; };关键配置项CONFIG_GIC_SAFE_CONFIG。在异构多核如Linux Zephyr场景下这个配置必须启用。因为GIC硬件是全局的如果Linux已经配置了GIC分发器Zephyr再去重新配置就会导致系统崩溃。启用此选项后Zephyr驱动在初始化时会检查GICD是否已被配置如果是则跳过配置步骤确保与主OS和平共处。6.2 时钟与引脚控制SCMI架构i.MX 95在时钟和引脚复用IOMUX管理上引入了一个重要的变化采用了SCMISystem Control and Management Interface协议。这是一种基于消息的、操作系统无关的硬件管理框架通常由一个运行在安全世界如Trusted Firmware中的SCMI服务器来管理硬件资源客户端如Zephyr通过邮箱Mailbox发送请求。SCMI时钟驱动 驱动源码drivers/clock_control/clock_control_arm_scmi.c配置项CONFIG_CLOCK_CONTROL_ARM_SCMI。 设备树中它作为一个SCMI协议子节点存在firmware { scmi { compatible arm,scmi; shmem scmi_shmem0; // 共享内存区域用于传递消息 mboxes mu2 0; // 使用的邮箱单元 mbox-names tx; scmi_clk: protocol14 { // 时钟协议ID为0x14 compatible arm,scmi-clock; reg 0x14; #clock-cells 1; }; }; };当Zephyr驱动需要获取或设置某个时钟如IMX_CCM_LPUART1_CLK时它会封装一个SCMI消息通过邮箱发送给服务器并等待在共享内存中的回复。这对开发者是透明的你依然像使用传统时钟驱动一样在设备树中为外设指定时钟源即可。SCMI引脚控制驱动 驱动源码drivers/pinctrl/pinctrl_imx_scmi.c配置项CONFIG_PINCTRL_IMX_SCMI。 其设备树节点与时钟类似也是SCMI的一个子协议scmi_iomuxc: protocol19 { // IOMUX协议ID为0x19 compatible arm,scmi-pinctrl; reg 0x19; pinctrl: pinctrl { compatible nxp,imx95-pinctrl, nxp,imx93-pinctrl; }; };引脚复用配置则定义在板级的pinctrl文件中imx95_evk-pinctrl.dtsi。以LPUART1为例lpuart1 { status okay; current-speed 115200; pinctrl-0 lpuart1_default; // 引用引脚配置组 pinctrl-names default; }; lpuart1_default: lpuart1_default { group0 { pinmux iomuxc_uart1_rxd_lpuart_rx_lpuart1_rx, iomuxc_uart1_txd_lpuart_tx_lpuart1_tx; bias-pull-up; slew-rate slightly_fast; drive-strength x4; }; };pinmux属性引用的两个标识符如iomuxc_uart1_rxd_lpuart_rx_lpuart1_rx在HAL层的pinctrl定义文件mimx9596avzxn-pinctrl.dtsi中会被展开为一个包含寄存器地址和复用模式的数组。SCMI驱动会将这些配置信息通过协议发送给服务器由服务器完成实际的寄存器写入操作。实操心得从直接操作CCM和IOMUX寄存器到通过SCMI协议访问这对驱动开发者来说是一个范式转变。好处是硬件资源的管理更加统一和安全但调试时不能直接读取寄存器状态。当串口或I2C不工作时除了检查设备树节点状态status “okay”和引脚配置还需要确认SCMI服务器通常是TF-A已正确初始化并且邮箱MU通信是正常的。查看Zephyr启动日志中SCMI驱动初始化是否成功是第一步。6.3 串口驱动LPUART串口是调试和输出的生命线。i.MX 95上的低功耗UARTLPUART驱动是drivers/serial/uart_mcux_lpuart.c配置项为CONFIG_UART_MCUX_LPUART。其设备树节点定义在SoC DTSI中开发者只需在板级DTS中启用它并指定引脚。上面的6.2节已经展示了完整的LPUART1设备树配置。确保以下几点status “okay”;pinctrl-0引用了正确的引脚配置组。对应的引脚配置组如lpuart1_default中pinmux属性指向的引脚标识符在HAL中正确定义。在应用的prj.conf中确保CONFIG_SERIALy和CONFIG_UART_MCUX_LPUARTy。Zephyr启动后printk或printf的输出默认就会重定向到这个已配置的串口。你可以通过uartshell命令如果使能了Shell或编写简单的应用来测试串口收发。6.4 其他外设驱动概览除了上述核心驱动BSP还支持其他常用外设它们的集成模式类似GPIOi.MX 95可能使用RGPIO或IGPIO驱动具体取决于IP版本。在设备树中查找compatible “nxp,imx-rgpio”或”nxp,imx-gpio”的节点。I2C对于i.MX 95可能是LPI2C驱动i2c_mcux_lpi2c.c。需要正确配置时钟、引脚复用并在设备树中指定clock-frequency。定时器用于系统滴答SysTick或一般定时操作的GPT或TPM计数器驱动。为这些外设编写应用时Zephyr提供了统一的API如gpio_pin_set、i2c_write你不需要关心底层是哪个驱动只需通过device_get_binding(DT_LABEL(DT_NODELABEL(设备别名)))来获取设备实例。这种硬件抽象层HAL的设计极大地提高了代码的可移植性。7. 调试技巧与常见问题排查实录移植和开发过程不可能一帆风顺。下面是我在i.MX 95上折腾Zephyr时遇到的一些典型问题及解决方法希望能帮你少走弯路。7.1 启动失败与内存地址问题问题现象执行cpu 1 release 0xd0000000后串口无任何输出或者U-Boot提示错误。排查步骤1确认镜像加载地址。确保tftp或fatload命令中的加载地址与release或go命令的地址完全一致。使用md命令可以查看内存内容进行校验u-boot md 0xd0000000 10。排查步骤2检查缓存刷新。务必在跳转前执行dcache flush和icache flush。我遇到过无数次因为忘记刷新缓存核心执行了旧指令导致莫名其妙崩溃的情况。排查步骤3确认内存区域可用。地址0xd0000000是一个常用地址但需确保它不在U-Boot、Linux内核、设备树或RAMdisk的占用范围内。可以查阅U-Boot环境变量bootm_size或Linux内核启动日志来了解内存布局。如果冲突可以尝试另一个地址如0xe0000000。排查步骤4检查Zephyr控制台输出。Zephyr的调试串口可能不是U-Boot使用的那个。确认在Zephyr的板级配置中CONFIG_UART_CONSOLE指向的是正确的LPUART实例例如CONFIG_UART_MCUX_LPUART_1。可以尝试在构建时启用更详细的日志CONFIG_LOGy和CONFIG_LOG_IMMEDIATEy。7.2 外设无法正常工作问题现象代码中能获取到设备实例但GPIO无法控制、I2C通信失败等。排查步骤1验证设备树。这是最常见的原因。使用west build -t menuconfig后进入Devicetree选项可以浏览编译后的设备树。确保目标外设节点的status是“okay”而不是“disabled”。排查步骤2检查引脚复用。这是i.MX系列平台的特有关卡。使用west build -t devicetree生成最终的.dtb文件然后用dtc工具反编译查看dtc -I dtb -O dts build/zephyr/zephyr.dtb zephyr.dts搜索你的外设节点如lpi2c1查看其pinctrl-0属性引用的配置组并确认该配置组中的pinmux值是否正确指向了物理引脚。一个引脚同时只能有一个功能需确认没有其他外设可能在Linux设备树中冲突占用。排查步骤3确认时钟。外设总线时钟如IPG_CLK和功能时钟必须使能。在i.MX 95的SCMI架构下时钟请求是通过协议完成的。确保在设备树中外设节点的clocks属性引用了正确的时钟源例如scmi_clk IMX_CCM_LPI2C1_CLK。可以尝试在应用中早期调用clock_control_on来手动打开时钟进行测试。排查步骤4驱动初始化日志。在prj.conf中提高日志级别CONFIG_LOG_LEVEL_DBGy并确保对应驱动如CONFIG_I2C_LOG_LEVEL_DBG的调试日志被打开。观察驱动probe函数是否被调用初始化是否成功。7.3 多核间资源冲突与同步问题现象Zephyr与Linux同时运行时访问某些共享资源如GPIO、部分内存区域导致系统不稳定或崩溃。根本原因i.MX 95具有资源域控制器RDC或类似硬件单元用于在硬件层面隔离不同核心/OS对资源的访问。如果配置不当会发生访问违例。解决方案仔细规划资源划分在系统设计阶段就明确哪些外设、内存区域分配给Zephyr哪些给Linux。这通常在系统级的配置文件中定义。检查RDC/资源分配配置在Zephyr的设备树节点中有时会看到rdc属性如旧平台它定义了该外设允许哪些域访问。对于i.MX 95资源隔离可能由SCMI服务器或更底层的固件管理。你需要参考NXP提供的多核应用笔记正确配置资源划分表。使用核间通信IPC对于必须共享的数据使用硬件支持的IPC机制如MUMessage Unit邮箱、共享内存信号量等。Zephyr和Linux侧都需要实现对应的驱动和协议。避免直接访问对方控制的外设寄存器。7.4 性能优化与尺寸裁剪问题现象Zephyr镜像过大或实时性不满足要求。镜像裁剪Zephyr的模块化特性使得裁剪非常方便。使用west build -t menuconfig进入Kernel、Device Drivers等子菜单关闭不需要的功能如不必要的文件系统、网络协议栈、调试功能。特别注意CONFIG_SIZE_OPTIMIZATIONSy可以启用编译器尺寸优化。内存布局调整在boards/nxp/imx95_evk/imx95_evk_mimx9596_a55.dts中可以调整Zephyr使用的内存区域sram0的起始地址和大小确保与Linux的内存规划不重叠且满足应用需求。实时性调优确保系统时钟滴答CONFIG_SYS_CLOCK_TICKS_PER_SEC设置合理如1000Hz。对于关键任务使用优先级高的线程并考虑使用CONFIG_PREEMPT_ENABLED启用可抢占调度。使用CONFIG_IRQ_OFFLOAD可以将中断处理任务转移到线程中减少中断延迟。调试是一个迭代的过程。我的习惯是遇到问题先抓日志从Zephyr启动的最早信息看起然后回归硬件检查设备树和引脚最后再审视代码逻辑。耐心和细致的日志是解决嵌入式问题最强大的武器。

相关新闻