1. 为什么从单片机跳到u-boot,不是“升级”,而是换赛道
你手头那块51单片机开发板,跑着DHT11温湿度采集+LCD1602显示,代码里全是while(1)循环和延时函数,烧录靠STC-ISP,调试靠串口打印“OK”——这很扎实,是嵌入式工程师的启蒙课。但当你开始查“嵌入式linux根文件系统挂载使用nfs v3”、翻“嵌入式linux项目”开源仓库、甚至看到“嵌入式架构师”招聘JD里写着“熟悉u-boot移植与裁剪”,你就该意识到:单片机和Linux嵌入式,表面都是“嵌入”,内核却是两套完全不同的操作系统哲学。
u-boot不是另一个“单片机库”,它是运行在SoC芯片上、介于硬件与Linux内核之间的第一段可执行程序。它不处理DHT11的时序,也不管LCD1602的RS引脚电平;它要干的是:初始化DDR内存控制器、配置时钟树、加载并校验Linux内核镜像、设置启动参数(bootargs)、接管串口控制权、甚至为后续的Secure Boot做密钥验证。这些事,51单片机连“概念”都没有——它没有MMU,没有外部SDRAM控制器,没有多级中断控制器,更没有“设备树”这种抽象层。
我带过不少从江科大51单片机笔记、蓝桥杯单片机国赛走出来的学生,他们能用状态机写密码锁、用非阻塞扫描做按键消抖、用Proteus仿真串口通信,动手能力极强。但一碰u-boot,常卡在第一个坑:为什么烧进Flash的u-boot.bin,上电后串口没任何输出?不是代码写错了,而是你根本没搞懂——51单片机上电即执行ROM里的代码,而ARM Cortex-A系列SoC(比如全志H3、瑞芯微RK3399、NXP i.MX6)上电后,CPU只执行片上ROM(BootROM)里固化的一小段引导代码,它会按固定顺序(eMMC→SD卡→SPI Flash→NAND Flash)去读取一个叫“SPL”(Secondary Program Loader)的小程序,再由SPL加载u-boot主镜像。这个启动链,51单片机里不存在。
所以,“学u-boot”不是给现有单片机项目加个新功能,而是把开发环境从“裸机寄存器操作”切换到“复杂SoC平台构建”。你需要面对的不再是STC8G1K17单片机的数据手册第12页的UART寄存器定义,而是NXP i.MX6ULL参考手册里长达800页的“System Control Unit”章节,里面密密麻麻全是时钟门控、复位源、电源域管理。你写的每一行u-boot代码,都得考虑:这段初始化会不会影响后续DDR训练?这个GPIO配置是否和设备树里声明的冲突?这个环境变量保存到哪里才不会被内核覆盖?
这不是知识量的简单叠加,而是工程思维范式的迁移:单片机强调“确定性”——延时1ms就是1ms,中断响应时间可精确到几个周期;而u-boot强调“鲁棒性”——eMMC卡可能接触不良,SD卡可能格式错误,NAND Flash可能有坏块,DDR颗粒批次不同导致时序参数微调,所有这些异常,u-boot都得自己检测、绕过、降级处理。你看“嵌入式开源项目”里那些u-boot补丁,十有八九是某款国产eMMC芯片在特定温度下读ID失败的workaround,这种问题,在51单片机世界里根本不会出现。
提示:别急着下载u-boot源码编译。先问自己三个问题:你的目标板用的是哪颗SoC?这颗SoC的官方SDK里有没有提供u-boot的参考移植?它的启动介质是eMMC、SD卡还是SPI Flash?这三个问题的答案,直接决定你是“抄作业”还是“造轮子”。很多新手一上来就clone最新版u-boot master分支,结果发现连最基础的串口都点不亮——因为master分支默认支持的是最新发布的SoC,而你手上的开发板用的是三年前的i.MX6Q,需要切到对应的LTS(长期支持)分支。
2. u-boot核心设计逻辑:它到底在做什么,为什么必须存在
2.1 启动流程全景图:从SoC上电到Linux内核start_kernel
理解u-boot,必须把它放在整个启动链条里看。以主流ARM Linux嵌入式系统为例,启动过程是严格分阶段的:
BootROM阶段:SoC内部固化代码,不可修改。它只做最基础的事:关闭看门狗、初始化极简时钟(通常就PLL bypass模式)、按预设顺序扫描启动介质(如先eMMC boot0分区,再SD卡第1扇区),找到并加载名为
SPL(或MLO)的二进制文件到片上SRAM(通常128KB以内)执行。BootROM不关心文件系统,只认原始扇区数据。SPL阶段:这是u-boot的“精简版”。它体积小(<100KB),功能单一:初始化DDR控制器(最关键!),配置基本时钟,初始化启动介质(如eMMC控制器),然后从介质中读取完整的u-boot镜像(u-boot-dtb.bin)到已初始化好的DDR内存中,并跳转执行。SPL不处理网络、不支持命令行、不解析设备树,它存在的唯一目的就是“把u-boot本体搬进内存”。
u-boot main阶段:这才是我们常说的“u-boot”。它被加载到DDR的指定地址(如0x87800000),开始执行
_start入口。此时它拥有完整的内存空间、串口、定时器、GPIO等外设驱动。它的核心任务有四:- 硬件初始化:比SPL更全面。初始化所有关键外设控制器(USB PHY、网卡MAC、PCIe Root Complex)、配置复杂的时钟树(多个PLL、分频器)、设置内存映射(MMU页表,为后续内核预留空间)。
- 环境变量管理:在Flash(eMMC user area / SPI NOR)中划分一块区域(通常是4KB),存储
bootcmd(自动启动命令)、bootargs(内核启动参数)、ipaddr(IP地址)等。这些变量是用户干预启动流程的唯一接口。 - 镜像加载与校验:从各种介质(eMMC、SD卡、TFTP网络、USB存储)读取Linux内核(zImage/Image)、设备树(.dtb)、根文件系统(ramdisk或ext4分区),并进行CRC32或SHA256校验,确保完整性。
- 内核跳转:将内核镜像解压(如果是zImage)、将设备树Blob(DTB)放到约定内存地址、设置好CPU寄存器(r0=0, r1=machine type, r2=DTB地址)、关闭MMU和Cache,最后用
bx指令跳转到内核入口stext。这一刻,u-boot使命完成,控制权移交Linux。
这个链条里,u-boot是承上启下的枢纽。BootROM太死板,无法适配不同板卡;Linux内核又太“高傲”,它假设所有硬件(尤其是内存)已经准备就绪。u-boot填补了这个真空——它用C语言写的驱动框架,让同一份内核源码,能在全志H3、RK3399、i.MX6ULL上跑起来,只需更换对应的u-boot配置和设备树。
2.2 配置体系:Kconfig + defconfig + board目录,三者如何咬合
u-boot的可移植性,源于其精巧的三层配置体系。这和你在51单片机里改一个#define BAUD_RATE 9600完全不同。
Kconfig:位于
arch/和drivers/目录下的Kconfig文件,定义了所有可选功能的开关。比如CONFIG_CMD_NET控制是否编译网络命令(tftp、ping),CONFIG_DM_GPIO控制是否启用新的驱动模型(Driver Model)GPIO子系统。每个选项都有prompt(菜单名)、default(默认值)、depends on(依赖条件)。它不决定具体参数,只决定“编不编”。defconfig:这是板级配置的起点。每个开发板对应一个
configs/xxx_defconfig文件,比如rockchip_rk3399_evb_defconfig。它是一堆CONFIG_XXX=y/m/n的集合,相当于告诉Kconfig:“我要编译网络命令,不要编译USB Host,GPIO用DM模型”。make rockchip_rk3399_evb_defconfig命令就是把这份清单加载进.config。board目录:位于
board/vendor/xxx/,存放板级特有代码。这里才是“实锤”:Kconfig:定义板子特有的选项,比如CONFIG_ROCKCHIP_RK3399。Makefile:指定编译哪些.c文件,比如obj-y += rk3399.o。rk3399.c:板级初始化函数board_init_f()和board_init_r(),在这里调用rockchip_dram_init()初始化DDR,调用rockchip_pinmux_init()配置引脚复用。dts/子目录:存放设备树源文件(.dts),描述硬件连接关系,比如“网卡PHY接在RMII接口,地址是0x0”。
这三层的关系是:Kconfig提供菜单,defconfig选择菜单项,board目录实现菜单项背后的具体硬件操作。你改defconfig只是勾选了“支持网卡”,但真正让网卡PHY通电、初始化MAC控制器、配置MDIO总线的,是board/rockchip/rk3399/rk3399.c里的几十行代码。这也是为什么“移植u-boot到新板子”,核心工作量在board目录,而不是改个配置文件那么简单。
注意:千万别在
board/xxx/xxx.c里硬编码寄存器地址!现代u-boot强制要求使用设备树(Device Tree)。你看到的rockchip_dram_init()函数,内部其实是解析dts/rockchip-rk3399-evb.dts里&dmc节点的属性(如ddr_freq、phy_stride),再调用通用的rockchip_dmc_init()。这样,同一份rk3399.c代码,配合不同的.dts文件,就能适配RK3399的不同硬件变种。
2.3 设备树(Device Tree):告别“寄存器海”,拥抱硬件描述语言
这是单片机开发者最难跨越的认知鸿沟。在51单片机里,你要点亮一个LED,代码是:
P1 = 0xFE; // P1.0拉低,LED亮寄存器地址和功能一一对应,清晰直接。
而在u-boot(及Linux)里,同样的LED,你需要写一个设备树片段:
&gpio0 { led_gpio: led_gpio@0 { gpio-hog; gpios = <0 GPIO_ACTIVE_LOW>; // GPIO0_0, active low output-low; label = "user-led"; }; };然后在u-boot C代码里,通过dm_gpio_lookup_name("user-led")获取句柄,再用dm_gpio_set_value()控制。设备树把“硬件是什么”和“软件怎么用它”彻底分离了。
设备树的核心价值在于:
- 解耦:SoC厂商提供
rockchip-rk3399.dtsi(SoC级描述),板卡厂商在此基础上叠加rockchip-rk3399-evb.dts(板级描述,如网卡PHY型号、LED连接的GPIO),内核和u-boot只需解析DTB,无需修改一行C代码。 - 可维护性:如果客户要求把LED从GPIO0_0换到GPIO1_5,你只需改.dts文件,重新编译DTB,u-boot和内核二进制完全不用动。
- 标准化:所有ARM SoC遵循同一套DT规范,
/soc/gpio@ff720000节点下必然有#gpio-cells属性,驱动代码可以统一用of_get_named_gpio()获取GPIO号。
对初学者,设备树最大的陷阱是路径引用错误。比如你想在&pinctrl节点里配置一个引脚,但误写成&pinctrl@ff770000(地址错),或者想引用&dmc节点却写成&dmc0(名字错),u-boot编译时不会报错,但运行时of_find_node_by_path()返回NULL,后续操作全部失效。我踩过的坑是:在RK3399上,&dmc节点实际叫&dmc0,文档和示例不一致,导致DDR初始化失败,串口无输出。
3. 实操:从零开始,为一块i.MX6ULL开发板移植u-boot(含避坑指南)
3.1 环境准备:工具链、源码、硬件确认
硬件确认(比写代码重要十倍):
- 开发板型号:野火i.MX6ULL eMMC版(核心是NXP MCIMX6Y2DVM09A)
- 启动介质:eMMC(boot0分区存放SPL,boot1分区存放u-boot-dtb.bin)
- 调试接口:JTAG(用于烧写SPL)+ UART1(波特率115200,8N1,用于u-boot串口输出)
- 关键文档:NXP官方《i.MX 6ULL Applications Processor Reference Manual》(重点看Chapter 2 "System Architecture" 和 Chapter 28 "ROM Code")
工具链:
- 交叉编译工具链:
arm-buildroot-linux-gnueabihf(推荐Buildroot提供的,版本4.9.4,兼容性最好)。别用Ubuntu自带的gcc-arm-linux-gnueabihf,它默认生成ARMv7-a指令,而i.MX6ULL是ARMv7-a+NEON,缺少浮点支持会导致u-boot启动失败。 - JTAG烧录器:SEGGER J-Link EDU Mini(必备!SPL必须用JTAG烧进eMMC boot0分区,串口无法做到)。
- 串口终端:minicom或PuTTY,设置好硬件流控(RTS/CTS)关闭,避免丢包。
源码获取:
- 官方LTS版本:
git clone https://source.codeaurora.org/external/u-boot/u-boot -b v2020.04(2020.04是i.MX6ULL最稳定的LTS,master分支对6ULL支持不完善)。 - NXP官方BSP:
git clone https://source.codeaurora.org/external/imx/u-boot-imx -b imx_v2020.04_4.19.35_1.1.0(这个分支包含NXP针对i.MX6ULL的全部补丁,比上游u-boot更可靠)。
实操心得:第一次编译,务必用NXP官方BSP。我曾坚持用上游u-boot,结果在
CONFIG_IMX_BOOTAUX(辅助启动)配置上折腾三天,才发现NXP BSP里有个隐藏补丁修复了i.MX6ULL的Auxiliary Boot ROM bug。开源社区的“最新版”不等于“最稳版”,尤其对小众SoC。
3.2 第一步:编译SPL并烧录到eMMC boot0分区
SPL是启动链的第一道门,它失败,后面全白搭。i.MX6ULL的SPL必须烧录到eMMC的boot0分区(物理偏移0x0),大小固定为512KB。
配置SPL:
cd u-boot-imx make distclean make mx6ull_14x14_ddr3_arm2_spl_defconfig # 这是NXP官方提供的SPL配置 make -j4编译后生成
spl/u-boot-spl.bin(约32KB)和spl/u-boot-spl-ddr.bin(含DDR初始化代码,约48KB)。JTAG烧录:
- 用J-Link Commander连接开发板,执行:
J-Link> connect J-Link> device i.MX6ULL J-Link> loadbin spl/u-boot-spl-ddr.bin, 0x00907000 # 加载到OCRAM J-Link> r # 运行SPL,让它初始化DDR J-Link> mem8 0x80000000, 10 # 检查DDR首地址是否可读(应返回0x00) - 若DDR初始化成功,再烧录:
J-Link> loadbin spl/u-boot-spl-ddr.bin, 0x00000000 # 烧到eMMC boot0起始地址 J-Link> q
- 用J-Link Commander连接开发板,执行:
验证SPL:
- 断电重启,用串口监听。正常应看到:
如果卡在U-Boot SPL 2020.04 (May 10 2023 - 14:23:01 +0800) Trying to boot from MMC1Trying to boot...,说明SPL找不到u-boot主镜像,检查eMMC boot1分区是否已写入。
- 断电重启,用串口监听。正常应看到:
常见问题:SPL串口无输出。原因90%是JTAG烧录地址错误。i.MX6ULL的eMMC boot0分区起始物理地址是
0x00000000,但J-Link Commander的loadbin命令默认烧录到RAM。必须用loadbin file.bin, 0x00000000明确指定地址,否则烧到RAM里,断电即失。
3.3 第二步:编译u-boot主镜像并写入eMMC boot1分区
u-boot主镜像包含完整功能,需写入eMMC的boot1分区(物理偏移0x80000,即512KB处)。
配置u-boot:
make distclean make mx6ull_14x14_ddr3_arm2_defconfig # 对应的主镜像配置 make menuconfig # 进入图形化配置关键配置项:
System configuration→Environment→Environment is in a FAT filesystem(因eMMC boot1是FAT32格式)Device Tree Control→Default Device Tree→imx6ull-14x14-evk(选择匹配的.dts)Command line interface→Network commands→tftp、ping(开启网络功能)Drivers→Serial drivers→Enable console on serial port(确保串口可用)
编译与打包:
make -j4 # 生成u-boot-dtb.imx(专为i.MX SoC定制的镜像,含IVT头) # 生成u-boot-dtb.bin(通用二进制,可用于SD卡启动)写入eMMC boot1:
- 方法一(推荐,用SD卡中转):
- 格式化SD卡为FAT32,拷贝
u-boot-dtb.imx到根目录。 - 将SD卡插入开发板,上电进入u-boot命令行(SPL会从SD卡加载u-boot)。
- 执行:
=> mmc dev 1 # 切换到eMMC => fatload mmc 0:1 ${loadaddr} u-boot-dtb.imx # 从SD卡读取 => mmc write ${loadaddr} 0x2 0x200 # 写入boot1分区(0x2扇区起,0x200扇区长)
- 格式化SD卡为FAT32,拷贝
- 方法二(直接烧录):
- 用
dd命令:sudo dd if=u-boot-dtb.imx of=/dev/mmcblk1 bs=512 seek=256 conv=notrunc(seek=256即0x100扇区,对应boot1起始)。
- 用
- 方法一(推荐,用SD卡中转):
启动验证:
- 断电重启,应看到完整u-boot启动日志:
出现U-Boot 2020.04 (May 10 2023 - 14:23:01 +0800) CPU: Freescale i.MX6ULL rev1.0 528 MHz Model: Freescale i.MX6ULL 14x14 EVK DRAM: 512 MiB MMC: FSL_SDHC: 0, FSL_SDHC: 1 In: serial Out: serial Err: serial Net: FEC1 Hit any key to stop autoboot: 0 =>=>提示符,表示u-boot启动成功!
- 断电重启,应看到完整u-boot启动日志:
实操心得:
mmc write命令的seek参数极易出错。i.MX6ULL的eMMC boot0是0x0~0x7FFFF(512KB),boot1是0x80000~0xFFFFF(512KB)。seek=256意味着从第256个512字节扇区开始写,即0x20000字节处,这正好是boot1的起始。我曾误用seek=2,结果把u-boot写到了boot0分区中间,覆盖了SPL,导致板子变砖,只能用JTAG重刷。
3.4 第三步:定制启动流程,让u-boot自动加载Linux内核
默认u-boot会执行bootcmd环境变量里的命令。我们需要让它从eMMC加载内核和设备树。
准备启动文件:
- 将编译好的
zImage(内核)和imx6ull-14x14-evk.dtb(设备树)拷贝到eMMC的user partition(即/dev/mmcblk1p1,通常挂载为/mnt)。 - 在eMMC user partition根目录创建
boot.scr(启动脚本):# 创建boot.cmd echo "fatload mmc 1:1 \${loadaddr} zImage" > boot.cmd echo "fatload mmc 1:1 \${fdt_addr_r} imx6ull-14x14-evk.dtb" >> boot.cmd echo "bootz \${loadaddr} - \${fdt_addr_r}" >> boot.cmd # 编译为boot.scr mkimage -C none -A arm -T script -d boot.cmd boot.scrmkimage是u-boot工具链自带的,用于生成u-boot可识别的镜像头。
- 将编译好的
设置环境变量:
=> setenv bootcmd 'fatload mmc 1:1 ${loadaddr} boot.scr; source ${loadaddr}' => setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p1 rw rootwait' => saveenv # 保存到eMMC环境变量分区bootcmd:先加载boot.scr脚本,再执行它。bootargs:关键!console=ttymxc0指定串口,root=/dev/mmcblk1p1指定根文件系统位置,rootwait确保内核等待eMMC初始化完成。
测试启动:
- 重启,u-boot会自动执行
bootcmd,加载boot.scr,再执行其中的fatload和bootz,最终跳转内核。你应该看到内核解压日志:Starting kernel ... Uncompressing Linux... done, booting the kernel. [ 0.000000] Booting Linux on physical CPU 0x0
- 重启,u-boot会自动执行
注意事项:
root=参数必须和实际分区一致。mmcblk1p1表示eMMC的第1个分区(user partition),如果用SD卡启动,应改为mmcblk0p1。rootwait必不可少,否则内核可能在eMMC驱动加载完成前就去挂载根文件系统,导致panic。
4. 深度解析:u-boot核心机制与高频问题排查实战
4.1 内存布局详解:为什么u-boot和内核不能随便放
u-boot和Linux内核共享同一片DDR内存,它们的地址空间必须严格规划,否则会相互覆盖。i.MX6ULL典型布局如下(单位:字节):
| 地址范围 | 大小 | 用途 | 关键配置 |
|---|---|---|---|
0x80000000~0x80100000 | 1MB | u-boot自身代码和栈 | CONFIG_SYS_TEXT_BASE=0x80000000 |
0x80100000~0x80800000 | 7MB | u-boot malloc heap | CONFIG_SYS_MALLOC_LEN=0x700000 |
0x80800000~0x81000000 | 8MB | Linux内核镜像(zImage)加载地址 | CONFIG_LOADADDR=0x80800000 |
0x81000000~0x81800000 | 8MB | 设备树Blob(DTB)加载地址 | CONFIG_FDT_ADDR_R=0x81000000 |
0x81800000~0x82000000 | 8MB | initrd/ramdisk加载地址 | CONFIG_INITRD_TAG |
0x82000000~0x87800000 | 88MB | Linux内核运行时内存(bss, page tables等) | CONFIG_SYS_SDRAM_BASE=0x80000000,mem=512M |
这个布局由u-boot的board/freescale/mx6ull_14x14_evk/mx6ull_14x14_evk.h头文件定义。CONFIG_LOADADDR决定了fatload mmc 1:1 ${loadaddr} zImage命令把内核读到哪里;CONFIG_FDT_ADDR_R决定了设备树读到哪里;而bootz ${loadaddr} - ${fdt_addr_r}命令正是利用这两个地址跳转。
致命错误示例:如果你把CONFIG_LOADADDR设为0x80000000(和u-boot代码重叠),那么fatload会把zImage覆盖掉正在运行的u-boot代码,导致bootz执行时CPU跳转到乱码地址,板子直接死机,串口无任何输出。这种问题,只有通过JTAG调试才能定位。
4.2 网络启动(TFTP):脱离SD卡,实现快速迭代
在开发阶段,每次改完内核都要烧SD卡太慢。TFTP网络启动是效率神器。
u-boot端配置:
- 确保
CONFIG_CMD_TFT已启用。 - 设置网络参数:
=> setenv ipaddr 192.168.1.100 => setenv serverip 192.168.1.1 => setenv netmask 255.255.255.0 => saveenv
- 确保
主机端搭建TFTP服务器(Ubuntu):
sudo apt install tftpd-hpa sudo nano /etc/default/tftpd-hpa # 修改为:TFTP_DIRECTORY="/var/lib/tftpboot" sudo systemctl restart tftpd-hpa sudo cp zImage /var/lib/tftpboot/ sudo cp imx6ull-14x14-evk.dtb /var/lib/tftpboot/u-boot命令一键启动:
=> tftp ${loadaddr} zImage => tftp ${fdt_addr_r} imx6ull-14x14-evk.dtb => bootz ${loadaddr} - ${fdt_addr_r}整个过程10秒内完成,比插拔SD卡快5倍。
排查技巧:TFTP超时?先
ping 192.168.1.1,确认网络连通。如果ping不通,检查u-boot的CONFIG_FEC_MXC_PHYADDR是否设为网卡PHY的实际地址(通常为0x0或0x1),以及CONFIG_PHYLIB是否启用。我遇到过PHY地址设错,u-boot认为网卡没连接,ping命令直接返回no link。
4.3 环境变量持久化:为什么saveenv后还是恢复默认
u-boot的saveenv命令,本质是把.env数据写入Flash的指定区域。i.MX6ULL默认使用eMMC的RPMB分区或user partition的特定扇区。常见故障:
- 现象:
setenv xxx yyy; saveenv后重启,变量消失。 - 原因:
CONFIG_ENV_IS_IN_MMC已启用,但CONFIG_SYS_MMC_ENV_DEV=1(eMMC)和CONFIG_ENV_OFFSET=0x400000(偏移4MB)的组合,指向了一个未格式化的eMMC扇区。 - 解决:
- 先用
mmc info确认eMMC设备号(通常是1)。 - 用
mmc read ${loadaddr} 0x800 0x10读取CONFIG_ENV_OFFSET扇区,看是否全0xFF(未写入)。 - 如果是,手动擦除并写入:
=> mmc dev 1 => mmc erase 0x800 0x10 # 擦除4KB(0x10扇区) => env default -a # 恢复默认环境 => saveenv
- 先用
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| 串口无任何输出 | SPL未烧录或烧录错误;串口波特率/硬件流控不匹配 | J-Link Commander检查SPL加载;用逻辑分析仪抓UART波形 | 用JTAG重刷SPL;确认CONFIG_BAUDRATE=115200且终端关闭RTS/CTS |
卡在Trying to boot from MMC1 | u-boot主镜像未写入boot1分区;eMMC boot1分区损坏 | mmc dev 1; mmc part查看分区表 | 用dd或mmc write重写u-boot-dtb.imx到boot1 |
bootz后黑屏/无内核日志 | bootargs中console=参数错误;内核镜像损坏;DTB地址不匹配 | printenv bootargs;md.b ${loadaddr} 10检查zImage头 | 检查console=ttymxc0是否正确;用sha256sum校验zImage;确认CONFIG_FDT_ADDR_R与bootz命令一致 |
TFTPno link | PHY地址配置错误;网线未插或交换机问题;u-boot未初始化网卡 | mii info;ping ${serverip} | 修改board/freescale/mx6ull_14x14_evk/mx6ull_14x14_evk.c中phy_addr为实际值 |
saveenv无效 | 环境变量存储区域未擦除;Flash写保护开启 | mmc info;mmc part | 用mmc erase擦除env区域;检查eMMC的WP引脚是否接地 |
最后分享一个小技巧:u-boot启动时,按住空格键(或任意键)可中断自动启动,进入命令行。这是所有调试的起点。记住这个
=>提示符,它比任何IDE的断点都管用。我在调试一个RK3399板子时,发现u-boot总在board_init_r阶段崩溃,就是靠反复中断、逐行执行bdinfo、printenv、md.l命令,最终定位到是DDR频率参数在设备树里写错了两位数字。真正的嵌入式调试,永远始于串口的=>。