第四章 Bootloader与内核启动:U-Boot移植与配置、内核设备树(DTS)修改、启动参数设置
好,咱们进入第四章。这一章讲的是POS机从通电到跑起系统的关键环节——Bootloader和内核启动。说白了,就是让硬件“活过来”的那几步。
我刚开始做POS机那会儿,总觉得U-Boot就是个黑盒子,能跑就行。后来踩了坑才发现,这里面的门道深着呢。你想想看,POS机要刷IC卡、要打印小票、要连4G网络,这些外设能不能正常工作,很大程度取决于Bootloader和内核启动阶段配置得对不对。
4.1 U-Boot移植与配置
U-Boot,全称是Universal Bootloader。它就像PC上的BIOS,负责初始化硬件、加载内核。但POS机用的嵌入式芯片五花八门,U-Boot必须针对你的板子做定制。
4.1.1 移植前的准备
拿到一块新的POS机主控板,我一般先做三件事:
- 确认芯片型号:比如全志V3s、瑞芯微RK3288、或者NXP的i.MX系列。不同芯片的启动流程差异很大。
- 找到参考板:U-Boot源码里有很多现成的板级配置。比如你的芯片是RK3288,就找
rk3288_evb这个配置作为起点。 - 准备交叉编译工具链:ARM架构的POS机,一般用
arm-linux-gnueabihf-这套工具链。
board/mycompany/mypos文件夹,把参考板的配置复制过来改。这样以后维护起来清晰。
4.1.2 关键配置项
U-Boot的配置主要通过make menuconfig来完成。但有几个核心参数,我建议你直接改头文件,更可控。
以我做过的一款基于全志V3s的POS机为例,关键配置在include/configs/sun8i.h里:
/* 内存配置 */
#define CONFIG_NR_DRAM_BANKS 1
#define SDRAM_BASE 0x40000000
#define SDRAM_SIZE 0x08000000 /* 128MB */
/* 存储设备 */
#define CONFIG_MMC
#define CONFIG_SDMMC_BASE 0x01C0F000
/* 网络支持 - POS机需要联网 */
#define CONFIG_ETH_DESIGNWARE
#define CONFIG_GMAC_BASE 0x01C30000
/* 启动参数存储位置 */
#define CONFIG_ENV_IS_IN_MMC
#define CONFIG_ENV_OFFSET 0x80000
#define CONFIG_ENV_SIZE 0x20000
这里有个坑,我提醒你一下:内存大小一定要和硬件实际焊接的颗粒一致。我曾经遇到过一款POS机,硬件设计是128MB,但采购时换成了64MB的颗粒,U-Boot没改配置,结果内核启动到一半就崩了。查了两天才发现是内存超了。
4.1.3 编译与烧录
配置好之后,编译就简单了:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mypos_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
编译出来的u-boot-sunxi-with-spl.bin就是最终要烧录到SPI Flash或SD卡里的镜像。
4.2 内核设备树(DTS)修改
设备树,英文叫Device Tree,是Linux内核用来描述硬件信息的“说明书”。POS机的外设多,DTS改起来也最费劲。
4.2.1 DTS的基本结构
一个典型的DTS文件长这样:
/dts-v1/;
#include "sun8i-v3s.dtsi"
/ {
model = "MyPOS Terminal V1.0";
compatible = "mycompany,mypos", "allwinner,sun8i-v3s";
/* 内存节点 */
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x08000000>;
};
/* 串口 - POS机打印小票用 */
&uart0 {
pinctrl-0 = &uart0_pins_a;
status = "okay";
};
/* SPI - 连接打印机头 */
&spi0 {
status = "okay";
spidev@0 {
compatible = "mycompany,pos-printer";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
/* I2C - 连接触摸屏 */
&i2c0 {
status = "okay";
touch@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = &pio;
interrupts = <1 5 IRQ_TYPE_EDGE_FALLING>;
};
};
};
你看,每个外设都是一个节点。节点里描述了它的地址、中断、时钟等信息。内核启动时,会解析这个DTS,然后加载对应的驱动。
4.2.2 我踩过的DTS坑
做POS机最头疼的是外设冲突。我记得有一次,打印机和读卡器共用了同一个SPI总线,但DTS里没配好片选信号。结果一打印小票,读卡器就死机。
排查方法很简单:用cat /proc/device-tree查看内核实际解析的设备树,对比硬件原理图。如果发现某个外设的reg地址不对,或者interrupts号错了,改DTS重新编译就行。
核心原则:DTS里的每个status属性,要么是"okay",要么是"disabled"。没用的外设一定要关掉,否则内核会浪费资源去初始化它。POS机的电池续航很宝贵,别让没用的外设偷电。
4.2.3 编译DTS
修改完DTS后,需要编译成DTB文件:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
生成的sun8i-v3s-mypos.dtb就是内核要用的设备树二进制文件。把它和内核镜像、文件系统一起打包,烧录到POS机里。
4.3 启动参数设置
启动参数,就是U-Boot传给内核的命令行参数。它告诉内核:根文件系统在哪、控制台用哪个串口、内存多大等等。
4.3.1 常用启动参数
以我的POS机项目为例,U-Boot的bootargs环境变量通常这样设置:
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw
init=/sbin/init
mem=128M
lpj=499712
panic=5
quiet'
解释一下每个参数的作用:
| 参数 | 含义 | 我的建议 |
|---|---|---|
console=ttyS0,115200 |
调试串口,波特率115200 | POS机量产时建议去掉,防止被串口攻击 |
root=/dev/mmcblk0p2 |
根文件系统在SD卡第二个分区 | 如果用eMMC,改成/dev/mmcblk1p2 |
rootwait |
等待根文件系统就绪 | 必须加,否则SD卡没准备好内核就崩了 |
mem=128M |
限制内核使用128MB内存 | 和硬件实际内存一致,别写大了 |
panic=5 |
内核崩溃后5秒自动重启 | POS机无人值守,这个参数很实用 |
4.3.2 启动流程实战
U-Boot启动内核的完整命令序列是这样的:
# 加载内核和设备树到内存
mmc dev 0
fatload mmc 0:1 0x41000000 uImage
fatload mmc 0:1 0x41800000 sun8i-v3s-mypos.dtb
# 设置启动参数
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw mem=128M'
# 启动内核
bootm 0x41000000 - 0x41800000
这里bootm命令的三个参数分别是:内核地址、ramdisk地址(没有就填-)、设备树地址。
0x41000000,和内核地址冲突了。结果内核启动时,设备树被覆盖,系统直接挂掉。记住:内核和设备树要放在不同的内存区域,中间留够空间。
4.4 常见问题与调试技巧
做POS机Bootloader移植,有几个问题你大概率会遇到:
- U-Boot启动卡在“Starting kernel...”:八成是设备树有问题。检查DTS里的
compatible字符串是否和内核驱动匹配。 - 内核启动后找不到根文件系统:检查
root=参数是否正确,以及SD卡/eMMC的分区表是否和参数一致。 - 外设驱动加载失败:用
devicetree工具反编译DTB,确认节点是否被正确解析。
嗯,这一章的内容就这些。说白了,Bootloader和内核启动就是“搭桥”的过程——U-Boot把硬件初始化好,设备树把外设描述清楚,启动参数告诉内核怎么跑。这三样配合好了,你的POS机才能稳稳当当地跑起来。
下一章,咱们会深入文件系统的构建,看看怎么把应用程序、库文件、配置文件塞进POS机里。到时候见。