2. 开发环境搭建:交叉编译工具链安装、TFTP/NFS服务器配置、串口终端与调试工具
说实话,嵌入式Linux开发的第一步,往往就把不少人卡住了。不是代码多难,而是环境没搭对。我见过太多新手花了一整天装工具链,结果编译出来的程序在板子上跑不起来——说白了,就是环境没配干净。
这一章,咱们就把开发环境彻底捋一遍。你跟着我的步骤走,保证一次搞定。
2.1 交叉编译工具链:为什么非得用这个?
你的PC是x86架构,ARM开发板是ARM架构。PC上编译出来的程序,ARM板子不认识。反过来也一样。所以我们需要一个“翻译官”——交叉编译工具链。
它运行在PC上,但生成的目标代码是ARM指令集。嗯,就是这么个道理。
2.1.1 工具链的选择
我个人习惯用Linaro提供的GCC工具链,稳定且社区活跃。当然你也可以用芯片厂商提供的版本,比如NXP的、TI的。但记住一条原则:工具链版本不要追新。我踩过这个坑——用了个最新版GCC,结果内核编译报了一堆莫名其妙的错误,最后发现是工具链与内核版本不匹配。
| 工具链来源 | 适用场景 | 我的建议 |
|---|---|---|
| Linaro GCC | 通用ARM Cortex-A系列 | 新手首选,社区资料多 |
| 芯片厂商SDK | 特定芯片(如i.MX、STM32MP) | 项目定制时用,但版本较老 |
| Buildroot/Yocto自带 | 完整系统构建 | 适合量产项目,学习曲线陡 |
2.1.2 安装步骤
以Linaro GCC 7.5为例,我一般放在 /opt/toolchains 目录下:
# 下载工具链
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 解压到指定目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/toolchains/
# 配置环境变量(写入 ~/.bashrc)
export PATH=/opt/toolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 验证安装
arm-linux-gnueabihf-gcc --version
setenv.sh 脚本,把环境变量写进去。每次打开新终端,source一下就行。这样不同项目之间不会互相污染环境变量。
2.2 TFTP/NFS服务器:板子与PC的“快递员”
开发板没有硬盘,怎么把编译好的内核、设备树、文件系统传过去?两个经典方案:TFTP传内核,NFS挂载根文件系统。你想想看,调试阶段每次改代码都重新烧写Flash,那得多崩溃?
2.2.1 TFTP服务器配置
TFTP是个极简的文件传输协议,UDP的,没有认证。U-Boot用它来加载内核镜像。我在Ubuntu上这么配:
# 安装TFTP服务器
sudo apt-get install tftpd-hpa
# 配置文件 /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp" # 这就是你的文件存放目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"
# 创建目录并设置权限
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
# 重启服务
sudo systemctl restart tftpd-hpa
然后你把编译好的 zImage 和 .dtb 文件丢进 /srv/tftp 目录。U-Boot那边用 tftp 0x82000000 zImage 就能拉过来。
2.2.2 NFS服务器配置
NFS用来共享根文件系统。板子启动后,通过NFS挂载PC上的目录作为根目录。这样你修改PC上的文件,板子立刻生效——调试效率翻倍。
# 安装NFS服务器
sudo apt-get install nfs-kernel-server
# 编辑 /etc/exports,添加共享目录
/home/workspace/rootfs *(rw,sync,no_subtree_check,no_root_squash)
# 重启NFS服务
sudo systemctl restart nfs-kernel-server
# 验证挂载(在板子上)
mount -t nfs -o nolock 192.168.1.100:/home/workspace/rootfs /mnt
这里有个关键点:no_root_squash 参数。不加的话,板子上的root用户会被映射成nobody,写文件会报权限错误。我刚开始学的时候被这个坑惨了。
2.3 串口终端与调试工具
嵌入式开发,串口就是你的“眼睛”。没有显示器、没有键盘,全靠一根串口线跟板子交流。
2.3.1 串口终端软件选择
| 工具 | 特点 | 我的推荐场景 |
|---|---|---|
| minicom | 经典、轻量、配置稍繁琐 | 服务器环境,无图形界面 |
| screen | 极简、无需配置 | 快速测试,临时连接 |
| MobaXterm | 图形化、支持多标签、内置X11 | Windows下首选,我一直在用 |
| PuTTY | 轻量、稳定 | Windows下备选 |
我个人在Linux下用 screen 最多,因为一条命令搞定:
# 查看串口设备
ls /dev/ttyUSB*
# 连接(波特率115200,8N1)
screen /dev/ttyUSB0 115200
# 退出:Ctrl+A,然后按 :quit
2.3.2 串口参数与常见问题
串口通信参数必须一致:115200波特率、8数据位、1停止位、无校验。这是绝大多数ARM开发板的默认配置。如果乱码,先检查波特率对不对。
2.3.3 调试三板斧
开发过程中,我常用的调试手段就三个:
- 串口打印:U-Boot阶段用
printf(),内核阶段用printk()。别小看这个,90%的问题靠打印就能定位。 - LED指示灯:如果串口都还没起来,那就点LED。在U-Boot的board_init里加一句GPIO操作,看灯亮不亮,就知道代码跑到哪了。
- JTAG/SWD调试器:硬件仿真,单步执行。我一般用J-Link配合OpenOCD。但说实话,除非是启动阶段的汇编代码,否则我很少用——串口打印更快。
2.4 本章知识体系
下面这张图,把开发环境的核心组件和它们之间的关系画清楚了:
从图上你能看到:PC上通过交叉编译工具链生成ARM代码,然后通过TFTP把内核和设备树传给板子,通过NFS共享根文件系统。串口终端则负责监控板子的运行状态。这三条线缺一不可。
2.5 环境验证:跑一个Hello World
环境搭没搭好,跑个程序就知道了。写一个最简单的C程序:
// hello.c
#include <stdio.h>
int main(void)
{
printf("Hello, Embedded Linux!\n");
return 0;
}
用交叉编译器编译:
arm-linux-gnueabihf-gcc -o hello hello.c
然后通过TFTP把 hello 传到板子上,加执行权限,运行:
chmod +x hello
./hello
如果串口打印出 Hello, Embedded Linux!,恭喜你,环境全部就绪。如果报错 No such file or directory,别慌——大概率是动态链接库的问题。用 file hello 和 readelf -d hello 检查一下,或者编译时加 -static 参数。