1. 项目概述与动机最近在折腾一个挺有意思的事儿把风河的实时操作系统 VxWorks 7 给移植到 Boundary Devices 的 Nitrogen8M 单板计算机上。这块板子我们团队在之前的 Linux 驱动开发项目里用得挺多性能稳定接口也全核心是一颗 NXP 的 i.MX8M 四核 Cortex-A53 处理器配上 2GB 的 DDR4 内存千兆以太网和 USB 3.0 这些高速接口也都有算是个挺不错的嵌入式开发平台。正好有个同事在做一个基于 i.MX8M 的客制化 VxWorks 7 项目这让我琢磨着能不能把 VxWorks 7 也跑在这块熟悉的板子上看看移植过程到底有多“丝滑”。毕竟从九十年代初开始我们团队就一直在跟 VxWorks 的板级支持包和驱动程序打交道对这套流程还算门儿清。VxWorks 7 的一个重大改进是引入了平台支持包的概念把很多 SoC 级别的通用支持给抽象了出来再加上用设备树来管理板级硬件配置这让向新板卡的移植工作理论上简化了不少。对于 i.MX8M 这种高度集成的 SoC 来说外围设备大多都在芯片内部启动一个基本内核的核心工作似乎就变成了“写一个正确的设备树文件”。当然老话说得好“魔鬼藏在细节里”真正动手之后才发现让内核跑起来只是万里长征第一步要让所有外设尤其是像板载 USB Hub 这种“小麻烦”都乖乖工作还得费一番功夫。这篇笔记就记录下我从零开始在 Nitrogen8M 上让 VxWorks 7 内核启动并成功驱动 USB 3.0 存储设备的全过程里面踩过的坑和总结的经验或许能给同样想玩转 VxWorks 和 i.MX 平台的朋友一些参考。2. 环境准备与工具链搭建在开始任何嵌入式系统移植之前搭建一个稳定、高效的开发环境是重中之重。这次移植工作主要涉及三个部分VxWorks 7 的源码与编译环境、用于引导的 U-Boot以及目标板 Nitrogen8M 本身的硬件资料。2.1 VxWorks 7 SR0620 开发环境部署我使用的是 Wind River 官方发布的 VxWorks 7 SR0620 版本。这个版本对 ARM Cortex-A 系列处理器的支持已经比较成熟。安装过程遵循官方文档主要是在一台 Linux 宿主机我用的 Ubuntu 20.04 LTS上安装 Wind River Workbench 4 或者直接使用命令行工具链。考虑到后续编译和调试的灵活性我选择了后者即安装 VxWorks 7 的 Source Build 版本。安装完成后关键是要正确设置环境变量。通常需要 source 安装目录下的vxworks-7-env.bash这类脚本。这个脚本会设置WIND_HOME、WIND_BASE等关键变量并将交叉编译工具链例如aarch64-wrs-vxworks-gcc的路径加入PATH。你可以通过执行aarch64-wrs-vxworks-gcc -v来验证工具链是否就绪。这里有个细节需要注意VxWorks 7 支持独立设备树DTB和集成设备树两种内核映像格式。为了简化引导流程我选择了将设备树二进制文件DTB直接编译进内核映像生成一个 U-Boot 可以直接加载的uImage格式文件。2.2 U-Boot 引导程序的获取与配置Nitrogen8M 默认出厂是运行 Linux 系统的其 U-Boot 通常已经预装在板子的存储设备上。为了确保兼容性和拥有最新的特性我决定从 Boundary Devices 的官方 GitHub 仓库下载并编译专为这块板子优化的 U-Boot 源码。git clone https://github.com/boundarydevices/u-boot.git -b nitrogen8m cd u-boot编译前需要配置。对于 i.MX8MQNitrogen8M 使用的型号通常使用make nitrogen8m_defconfig来加载默认配置。然后使用make进行编译。编译产物中我们最关心的是u-boot.imx这个文件它包含了 i.MX 系列芯片所需的头部信息可以通过dd命令或者 Boundary Devices 提供的uuu工具烧写到板子的启动介质如 eMMC 或 SD 卡上。注意在调试阶段我强烈建议使用 SD 卡启动。这样即使 U-Boot 环境被玩坏了也可以直接拔卡重烧不影响板子的“砖”化风险。将编译好的u-boot.imx写入 SD 卡特定扇区的操作Boundary Devices 的 Wiki 上有详细说明。2.3 硬件资料梳理原理图与数据手册“兵马未动粮草先行”。在嵌入式开发里“粮草”就是硬件资料。Boundary Devices 非常友好地公开了 Nitrogen8M 的完整原理图和数据手册这是本次移植能够成功的关键。你需要重点关注以下文件Nitrogen8M 原理图 PDF用于查找处理器引脚定义、电源网络、外设连接关系。特别是 USB、以太网 PHY、复位电路等部分。NXP i.MX8MQ 参考手册这是处理器的圣经所有内部寄存器、时钟架构、内存映射、外设模块的详细描述都在里面。当你需要配置某个外设时必须查阅此手册。板载外设芯片数据手册例如我们后面会遇到的 Renesas uPD720210 USB 3.0 Hub 芯片的数据手册。当标准驱动不工作时你就得翻它来找原因。我的习惯是在开始写任何代码或配置之前先把相关部分的原理图和数据手册快速浏览一遍在心里建立一个硬件连接的拓扑图。比如看到 USB Hub 连接到了处理器的哪个 USB 控制器它的复位信号又是来自处理器的哪个 GPIO 引脚。这个步骤能极大减少后续调试的盲目性。3. 创建基础 BSP 与设备树VxWorks 7 的 BSP 结构相比早期版本有了很大优化。对于 i.MX8M 这样的主流 SoCWind River 已经提供了成熟的平台支持包PSP和基础 BSP。我们的工作不是从零开始而是在这个基础上进行“板级定制”。3.1 基于官方 BSP 创建项目首先在 VxWorks 开发环境中找到 i.MX8M 系列的参考 BSP。Wind River 通常会为 NXP 的评估板如imx8m_evk提供 BSP。我们可以以此为基础复制一份并重命名为我们自己的 BSP例如nitrogen8m。cd $WIND_BASE/vxworks-7/pkgs_v2 cp -r board/evk/imx8m_evk board/evk/nitrogen8m接下来需要修改这个新 BSP 目录下的关键文件。最主要的是Makefile和kernel/config.h。在Makefile中需要更新BOARD和MODEL等变量确保它们指向你的新板子名称。在config.h中则定义了内核的基本配置如内存起始地址和大小、默认控制台串口等。对于 Nitrogen8M其内存起始地址为0x40000000大小为0x800000002GB这些信息可以从原理图或 U-Boot 的环境变量中确认。3.2 编写 Nitrogen8M 设备树源文件设备树是本次移植的核心。它用一种结构化的文本格式描述了硬件的拓扑和配置使得内核无需为每一块板子硬编码这些信息。VxWorks 7 完全支持使用设备树。我为 Nitrogen8M 创建了一个基础的设备树源文件nitrogen8m.dts。它的结构如下头部和包含指定设备树版本并包含 SoC 级别的通用定义文件。这些文件通常由芯片厂商或 VxWorks 提供描述了 i.MX8MQ 处理器内部的所有外设节点如usdhc1,usb3_0等和时钟信息。/dts-v1/; #include prjParams.h #include imx8mq.dtsi #include imx8mq-clocks.dtsi #include imx8mq-iomux.dtsi根节点/在这里定义板子模型、内存布局、别名以及chosen节点。model板子描述字符串。memory这是最关键的部分之一必须与硬件实际内存大小和地址完全一致。定义错误会导致内核无法正确初始化内存管理器直接崩溃。aliases为设备指定别名例如将serial0指向uart1这样内核就知道默认控制台是哪个串口。chosen用于传递内核命令行参数bootargs。这里设置了网络引导参数指定了主机 IP、目标板 IP、网关以及内核映像名等。外设节点覆盖通过符号引用在imx8mq.dtsi中定义的节点并添加或覆盖其属性。例如配置以太网 PHYenet0 { phy0: ethernet-phy0 { compatible atheros,ar8035; reg 0; }; };这里的compatible属性用于驱动匹配必须与 PHY 芯片的实际型号对应。reg属性是 PHY 在 MDIO 总线上的地址。3.3 配置内核与首次编译有了 BSP 和设备树接下来就是配置内核。使用 VxWorks 提供的菜单配置工具例如vxprj或者直接修改config.h来启用必要的组件。对于最小化可启动内核至少需要CPU 支持ARM Cortex-A53 SMP。内存管理MMU 支持。串口驱动用于控制台输出。选择正确的 UART 驱动如IMX_UART。定时器驱动用于系统 tick。中断控制器驱动GICv3。设备树支持必须启用INCLUDE_DTB和INCLUDE_DTB_BLOB以便将 DTB 编译进内核。配置完成后在 BSP 目录下执行编译命令。编译过程会处理设备树源文件dts通过设备树编译器DTC将其编译成二进制格式dtb并最终链接进内核映像文件vxWorks。我们最终需要的是uVxWorksU-Boot 格式的映像或vxWorks.bin原始二进制映像。我选择生成uVxWorks方便通过 TFTP 网络加载。4. 引导内核与基础功能验证编译生成内核映像后就进入了激动人心的实机测试阶段。这个过程需要宿主机、目标板和网络环境的配合。4.1 搭建 TFTP 与网络环境宿主机配置确保宿主机开启了 TFTP 服务器并将编译好的uVxWorks文件放在 TFTP 根目录下。同时宿主机需要有一个固定的 IP 地址例如192.168.10.20。目标板连接通过串口线将 Nitrogen8M 的调试串口通常是 UART1连接到宿主机使用串口终端工具如minicom,picocom或PuTTY连接波特率设为115200。通过网线将板子的以太网口与宿主机连接到同一局域网。配置 U-Boot 环境变量给板上电在 U-Boot 启动倒计时时打断进入命令行。设置目标板 IPsetenv ipaddr 192.168.10.10设置服务器 IPsetenv serverip 192.168.10.20设置引导文件setenv image uVxWorks保存环境saveenv4.2 加载并启动 VxWorks 内核在 U-Boot 命令行中使用 TFTP 命令将内核映像加载到内存中。我们需要指定一个合适的内存地址这个地址不能与内核最终运行的地址冲突并且要在 DDR 的有效范围内。我选择了0x43000000。 tftp 43000000 uVxWorks Using FEC device TFTP from server 192.168.10.20; our IP address is 192.168.10.10 Filename uVxWorks. Load address: 0x43000000 Loading: ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################## 7 MiB/s done Bytes transferred 4395496 (4311e8 hex)加载成功后使用bootm命令启动内核。因为我们把设备树集成到了内核映像里所以只需要指定内核的加载地址即可。 bootm 43000000 ## Booting kernel from Legacy Image at 43000000 ... Image Name: vxworks Image Type: AArch64 VxWorks Kernel Image (uncompressed) Data Size: 4395432 Bytes 4.2 MiB Load Address: 40100000 Entry Point: 40100000 Verifying Checksum ... OK Loading Kernel Image ... OK ## Starting vxWorks at 0x40100000, device tree at 0x00000000 ...如果一切顺利你将看到 VxWorks 那经典的 ASCII 艺术 Logo 和启动信息其中会包含板子名称、CPU 数量、内存大小等。看到这个就意味着内核已经成功在板子上跑起来了基础的内存、CPU、定时器和串口驱动都是工作的。Target Name: vxTarget _________ _________ \77777777\ /77777777/ \77777777\ /77777777/ \77777777\ /77777777/ \77777777\ /77777777/ \77777777\ \7777777/ \77777777\ \77777/ VxWorks 7 SMP 64-bit \77777777\ \777/ \77777777\ \7/ Core Kernel version: 3.1.1.0 \77777777\ - Build date: Apr 29 2020 10:55:31 \77777777\ \7777777/ Copyright Wind River Systems, Inc. \77777/ - 1984-2020 \777/ /7\ \7/ \7/ /777\ ------- Board: Boundary Devices Nitrogen8M SBC - ARMv8 CPU Count: 4 OS Memory Size: 2048MB EDR Policy Mode: Deployed Adding 9129 symbols for standalone. -在 VxWorks Shell 提示符 (-) 出现后可以尝试一些基本命令如devs查看设备列表ifShow查看网络接口如果以太网驱动正常memShow查看内存信息等来验证基础系统功能。5. 驱动板载 USB 3.0 Hub 的挑战与解决内核启动成功只能算完成了 30% 的工作。接下来要让外设工作起来。Nitrogen8M 有一个非常实用的特性板载了一个 Renesas uPD720210 USB 3.0 Hub扩展出了三个 USB-A 端口。我插入一个 U 盘期望在devs命令中看到新的块设备但毫无反应。这说明 USB 主机控制器没有识别到下游的 Hub 或设备。5.1 问题排查与调试信息分析首先我确认了内核配置中已经包含了 i.MX8M 的 USB XHCI 主机控制器驱动INCLUDE_IMX_USB。重新检查设备树发现对于usb3_0和usb3_1这两个节点我直接沿用了评估板的配置状态设为“okay”这理论上应该能启用控制器。问题没有解决下一步就是打开驱动调试信息。在 VxWorks 内核配置中找到 USB 相关的组件如INCLUDE_USB_DEBUG和INCLUDE_USB_XHCI_DEBUG将它们启用并重新编译内核。带着调试信息的内核启动后当我插入 U 盘时串口终端输出了大量的 USB 相关日志但关键信息是主机控制器检测到了端口连接事件但随后枚举设备失败或者根本没有任何事件产生。仔细分析日志并结合代码我怀疑问题出在 USB Hub 本身。如果 Hub 没有正常工作那么连接在它后面的所有设备对上层主机控制器来说都是“隐身”的。5.2 深入硬件原理复位信号是关键是时候请出原理图了。查阅 Nitrogen8M 的原理图找到了板载 USB Hub (uPD720210) 的部分。其复位引脚RESET#连接到了一个名为USB_HUB_RESET_B的网络。追踪这个网络发现它连接到了处理器的GPIO1_IO14引脚。![USB Hub 复位电路原理图片段示意] 注此处为文字描述替代图片原理图显示USB_HUB_RESET_B信号通过一个电阻上拉到 3.3V同时直接连接到 i.MX8MQ 的GPIO1_IO14引脚。根据 uPD720210 的数据手册RESET#是一个低电平有效的复位信号。这意味着当这个引脚为低电平时Hub 被复位并保持不工作状态当引脚为高电平时Hub 退出复位开始正常工作。那么这个 GPIO 引脚在上电后的默认状态是什么这需要查 i.MX8MQ 的参考手册。通常GPIO 引脚的默认上电状态可能是输入模式且内部无上拉电平不确定。或者在 U-Boot 阶段可能被配置成了某种状态。但无论如何如果这个引脚在系统启动后一直处于低电平或高阻态都可能导致 Hub 无法退出复位。5.3 修改设备树驱动 GPIO 释放 Hub解决方案很明确在系统启动早期将GPIO1_IO14配置为输出模式并输出高电平。在 Linux 中我们可以在设备树里用gpio-hog节点来实现。VxWorks 7 的设备树解析器同样支持这个特性。我修改了nitrogen8m.dts在gpio1节点下添加了一个gpio-hog子节点gpio1 { status okay; pinmux-0 iomux_usb2; // 确保引脚复用为 GPIO usb_hub_resetb { gpio-hog; gpios 14; // GPIO1 的 14 号引脚 output-high; // 设置为输出高电平 }; };同时需要确保这个 GPIO 引脚的复用功能IOMUX被正确设置为 GPIO 模式。这通常在iomuxc节点中定义。我参考了 i.MX8MQ 的引脚定义和 Nitrogen8M 的硬件设计添加或确认了iomux_usb2这个引脚配置组将其复用为 GPIO 功能。5.4 验证与结果将新的设备树编译进内核重新生成uVxWorks并加载到板子上。启动后我立刻观察到插入 USB 端口的 U 盘指示灯开始闪烁——这是一个好迹象在 VxWorks Shell 中执行devs命令输出中果然出现了新的块设备节点- devs drv refs name 1 3 / 2 3 /bd0:1 /bd0a 5 3 /bd0a 6 3 /bd0b 10 3 /input/event0 0 3 /null 3 3 /ttyS0 2 3 /tyCo/0 /ttyS0 12 3 host: value -140737484449152 0xffff8000003b9a80这里/bd0a和/bd0b就是识别出的 U 盘上的两个分区。你可以进一步使用blkShow查看块设备详细信息或者使用dosFsShow查看 DOS 文件系统信息。至此USB 3.0 主机功能和板载 Hub 的驱动问题宣告解决。实操心得嵌入式开发中遇到外设不工作调试信息是第一道工具。但当软件层面检查无误时一定要回归硬件。原理图和数据手册是解决问题的“地图”和“字典”。像复位、时钟、电源使能这类硬件控制信号常常是软件驱动能正常工作的前提条件却容易被忽略。设备树中的gpio-hog是一个在早期初始化阶段配置 GPIO 的利器非常适合处理这类板级硬件初始化问题。6. 其他外设驱动集成与系统完善让 USB 工作起来是一个重要的里程碑但一个完整的系统还需要更多外设支持。以下是我后续集成其他关键驱动时的一些要点记录。6.1 以太网驱动的深度配置Nitrogen8M 使用 Atheros AR8035 千兆以太网 PHY。在基础设备树中我们已经通过compatible “atheros,ar8035”进行了匹配。但要让网络性能最佳可能还需要调整一些 PHY 的寄存器设置例如广告能力、LED 行为等。这些可以通过设备树中phy0节点下的属性来配置例如max-speed 1000。此外确保内核配置中包含了INCLUDE_END和INCLUDE_IPNET等网络栈组件。网络启动后可以使用ifShow查看接口状态用ping命令测试网络连通性。如果需要静态 IP可以在启动参数bootargs中设置也可以在 Shell 中使用ifConfig命令动态配置。6.2 存储设备驱动eMMC/SD卡i.MX8MQ 支持多个 USDHCUltra Secured Digital Host Controller接口用于连接 eMMC 和 SD 卡。在设备树中启用对应的usdhc节点例如usdhc1对应 SD 卡槽usdhc2对应 eMMC并正确配置引脚复用、电压和总线宽度。内核需要包含INCLUDE_IMX_USDHC驱动和INCLUDE_DISK_UTIL等组件。驱动加载成功后devs命令会显示类似/sd0或/emmc0的设备。随后可以使用dosFsVolFormat进行格式化用dosFsMkfs创建文件系统并用dosFsMount挂载到目录实现持久化存储。6.3 调试与性能分析工具集成一个用于开发的系统调试工具必不可少。除了串口控制台还可以考虑集成以下组件日志系统配置INCLUDE_LOGGING和INCLUDE_WDB_LOG将日志输出到内存缓冲区或文件方便事后分析。Wind River System Viewer这是一个强大的图形化系统监控和调试工具。需要在内核中启用INCLUDE_WDB和INCLUDE_WDB_TTY等组件并通过网络或串口与 Workbench 连接。性能剖析启用INCLUDE_PROFILE_TOOL和INCLUDE_PROFILE_TASK可以分析任务执行时间、中断延迟等对优化实时性能至关重要。7. 常见问题排查与经验总结在移植和调试过程中我遇到了不少典型问题。这里整理成一个速查表希望能帮你快速定位。问题现象可能原因排查步骤与解决方案U-Boot 加载内核后无任何输出1. 串口引脚复用或波特率错误。2. 内核入口地址或加载地址错误。3. 设备树内存节点 (memory) 设置错误。1. 检查设备树中stdout-path指向的串口节点是否正确对比原理图确认引脚。用示波器测量串口 TX 引脚是否有数据波形。2. 确认bootm的地址与内核映像的加载地址一致。检查uImage头信息。3.重点检查核对memory节点的reg属性必须与硬件 DDR 配置完全一致。可参考 U-Boot 启动时打印的 DDR 初始化信息。内核启动后卡住或重启1. 中断控制器 (GIC) 初始化失败。2. 时钟驱动问题。3. 早期内存分配失败。1. 确保内核配置正确选择了 GICv3 驱动设备树中intc节点配置正确。2. 检查设备树中时钟节点的配置确保内核依赖的时钟源已启用。3. 启用更早的调试输出或使用 JTAG 调试器单步跟踪定位崩溃点。USB 设备无法识别1. USB 控制器时钟或电源未开启。2. 设备树中控制器节点状态或物理地址错误。3.板载 Hub/PHY 未初始化如本文案例。4. 驱动未编译进内核。1. 检查设备树中 USB 控制器的clocks和power-domains引用是否正确。2. 核对reg属性与参考手册中的内存映射是否一致。3.仔细阅读原理图检查所有使能、复位信号是否满足芯片要求。使用gpio-hog或早期驱动代码进行控制。4. 确认INCLUDE_IMX_USB和INCLUDE_USB_XHCI等组件已包含。网络无法 ping 通1. PHY 芯片型号不匹配。2. MDIO/MDC 引脚复用错误。3. 网络接口未配置 IP 地址。4. 交换机/路由器 VLAN 限制。1. 确认设备树中phy子节点的compatible属性与硬件完全一致。2. 检查设备树中mdio和enet节点的引脚配置组。3. 在 Shell 中使用ifConfig “ether” xxx.xxx.xxx.xxx配置 IP或用bootargs传递。4. 尝试直连宿主机并关闭宿主机的防火墙进行测试。文件系统挂载失败1. 存储设备驱动未加载。2. 设备节点未创建。3. 文件系统类型不匹配或损坏。1. 执行devs查看是否有对应的块设备如/sd0。若无检查存储控制器的设备树和驱动。2. 使用blkShow查看块设备详细信息。3. 尝试使用dosFsDiskFormat或dosFsMkfs重新格式化注意选择正确的分区。最后再分享一个调试小技巧当系统行为异常时可以尝试最小化内核配置。先从一个仅包含 CPU、内存、串口、定时器和中断控制器的绝对最小配置开始确保系统能稳定启动。然后像搭积木一样一次只添加一个驱动或组件如 USB、网络并测试其功能。这种方法能有效隔离问题避免多个复杂因素相互干扰让你快速定位到是哪个新增的模块引入了不稳定。在 Nitrogen8M 的移植过程中我就是先确保最小内核能跑再加以太网最后才攻克 USB 这个“堡垒”整个排错路径非常清晰。