
1. 项目背景与核心目标最近在折腾一块基于NXP i.MX6ULL芯片的开发板想把U-Boot从SD卡启动起来。这听起来像是嵌入式开发里最基础的操作但真上手了才发现从“知道”到“做到”之间隔着不少细节。网上教程很多但要么语焉不详要么环境对不上照着做总差那么一点。比如用dd命令烧写镜像参数怎么写SD卡分区怎么处理启动失败串口一片漆黑怎么办这些问题不解决后续的Linux内核移植、根文件系统加载都无从谈起。这个笔记就是记录我如何一步步搞定i.MX6ULL从SD卡启动U-Boot的全过程。我会把每个步骤背后的原理、实际操作中的坑以及验证方法都讲清楚。无论你是刚接触这块板子还是卡在了启动环节希望这篇内容能帮你把路走通。整个过程会涉及硬件准备、镜像获取与处理、SD卡的正确烧写、启动参数配置以及最终的验证调试。我们不止要看到“绿灯亮、串口有输出”更要明白为什么这样操作是有效的。2. i.MX6ULL启动流程深度解析ROM Code在做什么在动手烧写SD卡之前我们必须先搞清楚i.MX6ULL这颗芯片上电后到底执行了哪些操作。很多启动失败的问题根源在于对启动流程的理解有偏差。i.MX6ULL的启动是一个多阶段的过程由芯片内部的只读存储器代码ROM Code主导。2.1 启动媒介的选择与搜索顺序i.MX6ULL的ROM Code上电后第一件事就是按照预先设定的顺序去寻找可以启动的代码。这个顺序由芯片的启动配置引脚BOOT_MODE[1:0]的状态决定。最常见的情况是将板子设置为从外部媒介启动如SD卡、eMMC、NAND Flash。当配置为从SD卡启动时ROM Code会依次检查以下设备SD卡通过USDHC接口这是我们本次操作的重点。eMMC如果SD卡启动失败或无卡则会尝试eMMC。其他接口如NAND、串行NOR Flash等具体取决于板级设计。这里有一个关键点ROM Code并非直接去执行SD卡里任意位置的代码。它有一套严格的“映像”查找规则。2.2 启动映像的结构IVT、DCD与Boot DataROM Code在SD卡中寻找的是一个符合其要求的“启动映像”。这个映像不是简单的二进制堆砌而是一个有固定格式的容器。其核心结构包括映像向量表Image Vector Table, IVT这是映像的“目录”或“入口点索引”。IVT必须被放置在ROM Code已知的固定偏移地址处。对于从SD卡启动这个偏移地址通常是SD卡块设备的第1个块Block 1 即偏移512字节有时也可能是第2个块Block 2具体取决于芯片型号和Boot ROM版本。IVT里面包含了程序入口点、DCD表的地址、Boot Data的地址等重要指针。设备配置数据Device Configuration Data, DCD这是一系列用于初始化芯片外部RAM通常是DDR3/LPDDR2的寄存器配置命令。因为U-Boot本身运行需要内存而内存控制器在上电后处于未初始化状态所以必须由ROM Code在跳转到U-Boot之前先根据DCD的指令完成内存初始化。DCD配置错误是导致启动失败串口无输出或输出乱码的最常见原因之一。Boot Data包含了映像的起始地址和长度等信息。实际的程序代码即U-Boot的二进制文件u-boot.bin或u-boot.imx。注意我们通常使用的u-boot.imx文件其实就是由编译工具如mkimage将IVT、DCD和u-boot.bin打包在一起形成的完整启动映像。而u-boot.bin是纯粹的二进制代码不包含头部信息。2.3 为什么是dd命令烧写的本质理解了启动映像的结构就明白了为什么我们不能简单地把u-boot.bin复制到SD卡而必须使用dd命令进行“块设备”级别的烧写。文件系统 vs 块设备当我们用cp命令时操作对象是文件系统如FAT32、ext4里的文件。而ROM Code启动时SD卡驱动处于非常原始的状态它不会、也不能去解析复杂的文件系统。它只认“块设备”的原始扇区。精确的偏移地址dd命令的seek参数允许我们将数据写入到块设备的指定扇区偏移处。我们必须将包含IVT的完整启动映像u-boot.imx写入到ROM Code期望的固定偏移例如1KB或2KB偏移处ROM Code才能正确找到并加载它。破坏性操作dd命令会直接覆盖指定扇区的原始数据。如果烧写位置错误例如写到了分区表所在的位置会导致SD卡无法被操作系统识别。因此操作前确认SD卡设备名和偏移量至关重要。3. 实战准备工具、镜像与SD卡处理理论清楚了我们开始准备实战环境。工欲善其事必先利其器。3.1 硬件与软件工具清单硬件i.MX6ULL开发板如正点原子、野火等。Micro SD卡一张建议容量4GB以上Class 10或更高速度。SD卡读卡器。USB转串口调试线如CH340、CP2102等用于查看启动日志。电脑Linux环境可以是虚拟机或WSL2推荐Ubuntu 20.04/22.04。软件与镜像U-Boot源码与交叉编译工具链你需要获取对应你开发板厂商提供的U-Boot源码或者从NXP官方仓库克隆。交叉编译工具链通常由厂商提供如gcc-linaro-arm-linux-gnueabihf。预编译的U-Boot镜像如果你是第一次尝试强烈建议先使用开发板厂商提供的、已验证可用的u-boot.imx文件。这能排除编译环境带来的问题。串口终端软件如minicom,picocom, 或Windows下的MobaXterm、Putty。Linux下的磁盘工具fdisk或parted用于分区dd用于烧写。3.2 获取与编译U-Boot镜像可选但建议了解如果你需要自己编译U-Boot流程大致如下。这能帮助你理解镜像的生成过程。# 1. 设置交叉编译工具链环境变量 export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- # 请替换为你的工具链前缀 export PATH$PATH:/your/toolchain/path/bin # 2. 进入U-Boot源码目录配置为你的开发板 # 以正点原子I.MX6ULL ALPHA开发板为例 make mx6ull_14x14_evk_defconfig # 使用最接近的默认配置 # 3. 启动图形化或命令行配置可选用于微调 # make menuconfig # 4. 编译 make -j$(nproc) # 编译成功后会在当前目录生成关键文件 # u-boot ELF格式文件用于调试。 # u-boot.bin 纯二进制文件不包含IVT和DCD。 # u-boot.imx 由u-boot.bin加上IVT、DCD等头部信息生成是用于烧写的完整镜像。 # u-boot.srec S-Record格式文件。关键点u-boot.imx文件是通过tools/mkimage工具并参考板级配置文件如include/configs/mx6ullevk.h中关于CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR的定义即烧写到SD卡的扇区偏移将u-boot.bin打包而成的。这个偏移量例如0x4001024字节即2个扇区必须与后续dd命令的seek参数匹配。3.3 SD卡的分区与格式化处理这是一个极易出错的环节。很多教程直接让dd到/dev/sdb这非常危险可能会覆盖你的系统盘。同时一个未经处理的、带有分区表的SD卡也可能导致启动异常。安全操作流程插入SD卡并确认设备号sudo fdisk -l仔细查看输出找到你的SD卡设备。通常它会是/dev/sdb或/dev/mmcblk0。务必确认容量等信息与你插入的SD卡一致假设我们的SD卡是/dev/sdb。彻底清空SD卡清除旧分区表sudo dd if/dev/zero of/dev/sdb bs1M count10这个命令向SD卡的前10MB写入0这会清除MBR分区表和可能存在的文件系统签名。再次警告of/dev/sdb目标设备绝对不能写错创建新分区可选但推荐 如果你希望这张SD卡后续还能用于存放内核zImage和设备树.dtb文件可以创建一个FAT32分区。U-Boot本身可以从RAW扇区启动但加载内核通常需要文件系统支持。sudo fdisk /dev/sdb在fdisk交互界面中输入n创建新分区。选择p主分区。分区号1。起始扇区这里非常关键我们必须为U-Boot镜像预留空间。假设u-boot.imx大小约400KB我们预留1MB2048个扇区是安全的。所以起始扇区设置为2048。结束扇区直接回车使用默认最后一个扇区。输入t更改分区类型选择c(W95 FAT32 (LBA))。输入w写入并退出。格式化分区# 如果SD卡是/dev/sdb那么第一个分区是/dev/sdb1 sudo mkfs.vfat /dev/sdb1现在你的SD卡有一个干净的FAT32分区且前1MB空间是未分配的专门留给U-Boot。4. 核心操作使用dd命令烧写U-Boot镜像这是最具决定性的步骤。我们将把u-boot.imx写入SD卡的预留区域。4.1 确定烧写偏移量Seek值偏移量的单位是“块”Block在dd命令中通常一块是512字节。我们需要知道两个信息ROM Code查找IVT的偏移对于i.MX6ULL从SD卡启动常见的偏移是1KB (0x400) 或 2KB (0x800)即2个扇区或4个扇区。这取决于芯片的Boot ROM版本和配置。最保险的方法是查阅开发板手册或参考厂商提供的烧写脚本。正点原子的板子通常是2个扇区seek2。我们预留的空间起始扇区在上一步分区中我们将分区起始设在2048扇区所以前2048个扇区1MB都是空闲的。只要烧写位置在这个范围内且与ROM Code期望的匹配即可。假设我们采用seek2即从第2个扇区开始写跳过前1KB。4.2 执行dd命令烧写# 假设u-boot.imx在当前目录SD卡设备是/dev/sdb sudo dd ifu-boot.imx of/dev/sdb bs512 seek2 convfsync逐参数解释ifu-boot.imx: 输入文件即你的U-Boot镜像。of/dev/sdb:输出设备必须是SD卡的整体设备如/dev/sdb而不是分区如/dev/sdb1bs512: 设置读写块大小为512字节与磁盘扇区大小一致。seek2: 跳过输出文件即SD卡开头的2个块2 * 512 1024字节从这个位置开始写入。这正好对应ROM Code从1KB偏移处查找IVT。convfsync: 确保所有数据完全写入物理设备后再返回避免缓存导致的数据不完整。烧写成功的提示命令会输出类似“记录了 x0 的读入”和“记录了 x0 的写出”的信息并显示拷贝的字节数。4.3 验证烧写结果可选但推荐烧写完成后可以读回来一部分数据与原始镜像头部对比确保写入正确。# 从SD卡的1KB偏移处读取1KB数据 sudo dd if/dev/sdb ofread_back.bin bs512 skip2 count2 # 使用hexdump或od命令查看原始镜像和读回数据的头部 hexdump -C u-boot.imx | head -20 hexdump -C read_back.bin | head -20对比两者开头部分特别是前几十个字节应该完全一致。你会看到IVT的签名等信息。5. 上电调试与启动失败问题排查将烧写好的SD卡插入开发板的SD卡槽连接串口线到电脑打开串口终端波特率通常设置为115200数据位8停止位1无校验。给开发板上电。5.1 成功启动的现象如果一切顺利你将在串口终端看到类似以下的输出U-Boot 2022.04 (Jun 01 2023 - 15:30:00 0800) CPU: Freescale i.MX6ULL rev1.1 528 MHz (running at 396 MHz) CPU: Commercial temperature grade (0C to 95C) at 48C Reset cause: POR Model: Freescale i.MX6ULL 14x14 EVK Board Board: MX6ULL 14x14 EVK DRAM: 512 MiB MMC: FSL_SDHC: 0, FSL_SDHC: 1 Loading Environment from MMC... *** Warning - bad CRC, using default environment In: serial Out: serial Err: serial Net: No ethernet found. Hit any key to stop autoboot: 3看到“Hit any key to stop autoboot”的提示就说明U-Boot已经从SD卡成功启动并运行起来了。5.2 常见启动失败问题与排查思路如果串口没有任何输出或者输出乱码、卡住请按以下步骤排查检查硬件连接串口线是否接对开发板的UART_TX接调试线的RXUART_RX接TXGND接GND串口终端软件参数波特率115200 8N1是否正确开发板启动模式拨码开关BOOT_MODE是否设置为从SD卡启动请查阅你的开发板手册确认。SD卡是否插紧尝试换一张SD卡。检查镜像与烧写过程镜像是否正确首先使用厂商提供的预编译镜像进行测试排除自己编译产生问题的可能。dd命令的seek参数是否正确这是最高频的错误点。如果开发板ROM Code期望从2KB4个扇区启动而你写在了1KB2个扇区就无法启动。尝试seek2和seek4。有些板子甚至是seek1。是否烧写到了正确的设备再次用sudo fdisk -l确认SD卡设备名。SD卡是否有残留分区信息干扰确保执行了dd if/dev/zero of/dev/sdX bs1M count10清空操作。检查DCD配置内存初始化如果串口有输出但很快卡死或输出大量乱码极有可能是DCD配置不正确导致DDR内存初始化失败U-Boot代码无法在内存中运行。DCD配置与开发板使用的具体DDR芯片型号、大小、布线密切相关。不同厂商、不同版本的开发板DCD参数可能不同。解决方案确保你使用的u-boot.imx镜像中的DCD配置与你的开发板硬件完全匹配。最可靠的方法是使用开发板厂商提供的U-Boot源码和配置文件进行编译或者直接使用他们提供的预编译镜像。使用JTAG调试终极手段如果以上步骤都无法解决并且你有JTAG调试器如J-Link可以尝试连接JTAG单步跟踪ROM Code的执行查看它在加载镜像时卡在了哪一步。这对于分析DCD初始化失败等问题非常有效但门槛较高。一个实用的排查技巧尝试编译或寻找一个最简单的、只初始化串口并打印“Hello World”的裸机程序将其制作成符合IVT格式的镜像并烧写到SD卡。如果这个简单程序能运行并打印说明启动流程和烧写方法基本正确问题很可能出在U-Boot本身的DDR配置或代码上。如果简单程序也无法运行那就要重点检查启动模式、烧写偏移和硬件了。6. 进阶配置U-Boot环境变量与测试内核启动U-Boot成功启动后我们的工作只完成了一半。一个可用的启动加载器还需要能加载并启动Linux内核。6.1 理解U-Boot环境变量U-Boot有一组环境变量类似于BIOS设置决定了它的行为。例如bootcmd: 定义自动启动时执行的命令序列。bootargs: 传递给Linux内核的启动参数。ipaddr,serverip: 网络相关配置用于tftp下载。这些变量可以保存在SD卡、eMMC或SPI Flash的某个区域。第一次启动时由于该区域是空的U-Boot会使用默认的内置环境。6.2 为从SD卡加载内核做准备我们之前为SD卡创建了FAT32分区/dev/sdb1现在可以将内核和设备树文件放进去。挂载SD卡分区并拷贝文件# 在Linux电脑上操作 mkdir /mnt/sdcard sudo mount /dev/sdb1 /mnt/sdcard sudo cp zImage /mnt/sdcard/ # 你的Linux内核镜像 sudo cp imx6ull-xxx.dtb /mnt/sdcard/ # 你的设备树文件 sudo umount /mnt/sdcard在U-Boot中配置环境变量 开发板上电在U-Boot倒计时结束前按任意键进入命令行。# 设置服务器IP你的电脑IP和开发板IP setenv serverip 192.168.1.100 setenv ipaddr 192.168.1.101 # 设置从SD卡第0个SDHC设备第1分区加载内核和设备树的命令 # ‘mmc dev 0’ 切换到SD卡mmc0通常是SD卡mmc1可能是eMMC # ‘fatload’ 从FAT分区加载文件到内存 setenv loadkernel mmc dev 0; fatload mmc 0:1 ${loadaddr} zImage setenv loadfdt fatload mmc 0:1 ${fdt_addr} imx6ull-xxx.dtb # 设置启动参数告诉内核根文件系统在哪里例如在SD卡的第2个分区 setenv bootargs consolettymxc0,115200 root/dev/mmcblk0p2 rootwait rw # 定义完整的启动命令 setenv bootcmd run loadkernel; run loadfdt; bootz ${loadaddr} - ${fdt_addr} # 保存环境变量到存储介质如SD卡 saveenv输入printenv可以查看所有环境变量。输入boot或等待倒计时结束U-Boot就会执行bootcmd尝试加载并启动内核。6.3 测试与验证执行boot命令后观察串口输出。如果内核开始解压并运行最终可能会因为找不到根文件系统而卡住因为我们还没做根文件系统但这已经证明了U-Boot成功完成了加载内核的使命。你会看到内核的版本信息、设备树解析日志等。至此通过SD卡启动U-Boot的核心任务已经圆满完成。这个过程虽然步骤繁多但每一步都有其明确的意图。从理解ROM Code的启动机制到谨慎处理SD卡分区再到精确使用dd命令最后完成环境配置这本身就是嵌入式开发中最经典的入门实践。掌握了它你就拿到了打开i.MX6ULL世界大门的钥匙后续的内核移植、驱动开发、根文件系统构建都将在此基础上展开。下次当你需要为不同的存储设备如eMMC、NAND部署U-Boot时你会发现其核心逻辑是相通的只是存储介质的访问方式和烧写工具有所不同而已。