第三章 嵌入式Linux系统构建:从零搭建你的工业触摸屏大脑
说实话,嵌入式Linux系统构建这块,是很多工程师的“拦路虎”。我刚开始带项目那会儿,也在这上面栽过跟头。你想想看,一个工业触摸屏,硬件焊好了,屏幕亮了,但系统跑不起来——那感觉,就像车造好了没装发动机。
这一章,我们就来搞定这个“发动机”。我会把交叉编译、U-Boot、内核裁剪、根文件系统、设备树这些硬骨头,一个个啃下来。
3.1 交叉编译环境搭建——为什么不能在开发板上直接编译?
很多新手会问:我直接在ARM板子上写代码、编译不行吗?
嗯,理论上可以。但实际项目中,没人这么干。为什么?因为ARM板子的CPU性能太弱了。你想想,一个1GHz的Cortex-A7,编译一个Linux内核要几个小时。而你的PC,i7处理器,十几分钟搞定。
交叉编译,说白了就是在你的PC(x86架构)上,编译出能在ARM架构上运行的代码。
核心工具链:
- gcc-arm-linux-gnueabihf —— 最常用的ARM交叉编译器
- binutils —— 链接器、汇编器等工具集
- glibc/uclibc —— C运行库,决定了你的程序能调用哪些系统函数
我个人习惯用Linaro提供的预编译工具链。省事,稳定。安装步骤很简单:
# 下载工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 设置环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
# 验证是否安装成功
arm-linux-gnueabihf-gcc --version
避坑指南:我曾经遇到过工具链版本不匹配导致内核编译失败的问题。后来学乖了——内核版本和工具链版本要对应。比如Linux 4.19内核,建议用gcc 7.x系列。太新的gcc反而会出兼容性问题。
3.2 U-Boot移植与配置——让板子学会“启动”
U-Boot是什么?简单说,它就是嵌入式系统的BIOS。上电后,CPU先跑U-Boot,U-Boot再加载内核。
移植U-Boot,说白了就是让U-Boot认识你的板子。我做过一个项目,用的是NXP的i.MX6ULL芯片。当时拿到官方的U-Boot源码,直接编译烧进去——结果屏幕不亮,串口也没输出。
为什么会这样?因为U-Boot不知道你的板子上有哪些外设,不知道DDR的型号,不知道时钟频率。你需要告诉它。
移植U-Boot的关键步骤:
- 选择参考板 —— 找一块和你的板子最接近的开发板配置
- 修改设备树 —— 告诉U-Boot你的硬件布局
- 配置DDR参数 —— 这个最坑,参数错了直接死机
- 添加板级支持文件 —— board/your_company/your_board/
来看一个实际的配置示例:
# 在U-Boot源码目录下
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig
# 修改配置文件
make menuconfig
# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
注意:U-Boot的配置项非常多,不要试图全部搞懂。我建议你重点关注这几个:启动设备(SD卡/NAND/SPI Flash)、网络支持、文件系统支持。其他的保持默认就好。
3.3 Linux内核裁剪与编译——瘦身才能跑得快
工业触摸屏的内核,不需要支持显卡、声卡、蓝牙、WiFi这些花里胡哨的东西。你想想,一个工控屏,要那么多驱动干嘛?
内核裁剪的原则:用不到的,统统去掉。
我做过一个项目,客户要求启动时间不超过5秒。默认的内核有3000多个驱动模块,启动要15秒。裁剪之后,只剩下400多个模块,启动时间压缩到3.8秒。
具体怎么做?
# 下载内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.237.tar.xz
tar -xvf linux-4.19.237.tar.xz
cd linux-4.19.237
# 配置内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
# 进入菜单配置界面
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
在menuconfig界面里,我一般这样操作:
- 关闭所有不用的驱动 —— 比如Sound card support、Bluetooth subsystem
- 把必要的驱动编译进内核 —— 而不是作为模块。比如触摸屏驱动、LCD驱动
- 开启实时抢占选项 —— 工业场景需要实时性
关于Yocto和Buildroot:
这两个工具,说白了就是帮你自动化构建整个系统的。Yocto功能强大但学习曲线陡峭,Buildroot简单直接。我个人建议:小项目用Buildroot,大项目用Yocto。
Buildroot的配置方式:
make menuconfig
Target options -> Target Architecture -> ARM (little endian)
Toolchain -> Toolchain type -> External toolchain
System configuration -> Root filesystem overlay directory -> /path/to/your/overlay
make
3.4 根文件系统制作——让系统有“家”可归
内核启动后,需要挂载根文件系统。没有根文件系统,系统就卡在“VFS: Cannot open root device”这个错误上。
根文件系统里有什么?说白了,就是Linux运行所需的所有文件和目录:
- /bin —— 基本命令,如ls、cp、mv
- /sbin —— 系统管理命令,如ifconfig、mount
- /etc —— 配置文件
- /lib —— 动态链接库
- /dev —— 设备节点
- /proc、/sys —— 虚拟文件系统
制作根文件系统,我推荐用BusyBox。它就像一个瑞士军刀,把几百个Linux命令打包成一个可执行文件。
# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xvf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
# 安装到指定目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=/path/to/rootfs install
我的经验:根文件系统不要做得太大。工业触摸屏的Flash空间有限,一般128MB到512MB。我习惯把根文件系统控制在50MB以内,剩下的空间留给应用程序和日志。
3.5 设备树(Device Tree)编写——让内核认识你的硬件
设备树,英文叫Device Tree。它是什么?说白了,就是一份硬件描述文件。告诉内核:你的板子上有哪些外设,它们连在哪个引脚上,用的是什么协议。
以前没有设备树的时候,内核里全是板级文件,每个板子一个.c文件,维护起来简直是噩梦。设备树解决了这个问题——硬件描述和内核代码分离。
来看一个实际的设备树片段:
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "My Industrial Touch Panel";
compatible = "mycompany,imx6ull-touchpanel";
chosen {
stdout-path = &uart1;
};
memory {
reg = <0x80000000 0x20000000>; /* 512MB DDR */
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif>;
display = <&display0>;
status = "okay";
display0: display {
bits-per-pixel = <24>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <33000000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vfront-porch = <12>;
vback-porch = <10>;
vsync-len = <3>;
};
};
};
};
};
&iomuxc {
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
pinctrl_lcdif: lcdifgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
/* ... 其他数据引脚 ... */
>;
};
};
我曾经踩过的坑:设备树里引脚的电气属性配置错了,导致触摸屏的I2C通信不稳定。后来查了芯片手册才发现,0x1b0b1这个值里的bit位设置不对。记住:设备树里的每个数字,都要去查芯片手册确认,不能想当然。
编译设备树的方法:
# 编译设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 或者在内核源码中编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
3.6 系统集成与启动验证
所有组件都准备好了,接下来就是集成。我一般按这个顺序操作:
- 烧录U-Boot —— 到SD卡或SPI Flash
- 复制内核镜像 —— zImage到启动分区
- 复制设备树 —— .dtb文件到启动分区
- 制作根文件系统镜像 —— 用mksquashfs或ext4格式
- 设置U-Boot启动参数
U-Boot的启动参数设置:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw'
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 0x80800000 zImage; fatload mmc 0:1 0x83000000 myboard.dtb; bootz 0x80800000 - 0x83000000'
saveenv
boot
看到内核启动日志,最后出现Welcome to Buildroot或者login:提示符——嗯,那一刻,成就感满满的。
总结一下本章的核心要点:
- 交叉编译环境是基础,工具链版本要匹配内核版本
- U-Boot移植重点关注DDR参数和启动设备配置
- 内核裁剪要“狠”,用不到的驱动统统去掉
- 根文件系统用BusyBox制作,控制在50MB以内
- 设备树是硬件和内核的桥梁,每个引脚配置都要查手册确认
下一章,我们会进入触摸屏驱动的开发。到时候,我会手把手教你如何让屏幕响应你的触摸。嗯,那才是真正“看得见摸得着”的成就感。