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 就能拉过来。

注意: 防火墙!防火墙!防火墙!我曾经在客户现场折腾了半小时,TFTP就是连不上,最后发现是ufw没关。调试阶段建议直接关掉防火墙,或者开放69/UDP端口。

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开发板的默认配置。如果乱码,先检查波特率对不对。

核心经验: 串口线也有讲究。USB转串口模块,CH340G和CP2102最稳定。PL2303我遇到过丢数据的情况。另外,线长不要超过1.5米,否则信号衰减会导致通信不稳定。

2.3.3 调试三板斧

开发过程中,我常用的调试手段就三个:

  1. 串口打印:U-Boot阶段用 printf(),内核阶段用 printk()。别小看这个,90%的问题靠打印就能定位。
  2. LED指示灯:如果串口都还没起来,那就点LED。在U-Boot的board_init里加一句GPIO操作,看灯亮不亮,就知道代码跑到哪了。
  3. JTAG/SWD调试器:硬件仿真,单步执行。我一般用J-Link配合OpenOCD。但说实话,除非是启动阶段的汇编代码,否则我很少用——串口打印更快。

2.4 本章知识体系

下面这张图,把开发环境的核心组件和它们之间的关系画清楚了:

嵌入式Linux开发环境架构 主机PC (x86) 交叉编译工具链 TFTP服务器 NFS服务器 串口终端 ARM目标板 TFTP传内核/设备树 NFS挂载根文件系统 串口查看启动日志

从图上你能看到: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 参数。

避坑指南: 我曾经在验证环境时,板子上死活跑不了编译好的程序。折腾半天发现,工具链是armv7的,板子是armv5的CPU。指令集不兼容,当然跑不了。所以选工具链时,一定要确认目标板的CPU架构和浮点运算单元(硬浮点/软浮点)。