2. 开发环境搭建:交叉编译工具链安装、TFTP/NFS服务配置、串口终端使用

说实话,嵌入式Linux开发的第一步,往往就把不少人卡住了。不是代码多难写,而是环境搭不起来。我见过太多人拿着开发板,串口没输出、文件传不上去、编译出来的程序跑不了——然后就开始怀疑人生。

这一章,咱们就把这些基础环境一次性搞定。说白了,嵌入式开发环境就三样东西:交叉编译工具链(让PC编译出ARM能跑的程序)、TFTP/NFS服务(让开发板能跟PC传文件)、串口终端(让你能跟开发板“说话”)。

核心思路:PC是“生产车间”,开发板是“目标机器”。工具链负责生产,TFTP/NFS负责运输,串口负责沟通。三者缺一不可。

嵌入式Linux开发环境整体架构 PC 主机(Ubuntu) 交叉编译工具链 TFTP 服务器 NFS 服务器 串口终端(minicom) USB/网线 以太网 以太网 USB转串口 目标开发板(ARM) U-Boot 引导 Linux 内核 根文件系统 用户应用程序 通信方式 TFTP → 传内核/设备树 NFS → 挂载根文件系统 串口 → 控制台交互 开发流程 PC编译 → 传板子 → 运行

2.1 交叉编译工具链安装

交叉编译,说白了就是在x86的PC上,编译出ARM架构能执行的二进制文件。为什么不能直接在开发板上编译?你想想看,开发板的CPU性能、内存、存储都有限,编译个Linux内核可能要几个小时——谁等得起?

我个人习惯用Linaro GCC,它是ARM官方推荐的交叉编译器,支持ARMv7、ARMv8等主流架构。当然,你也可以用芯片厂商提供的工具链,比如NXP的、TI的,但Linaro的通用性最好。

安装步骤

  1. 下载工具链:从Linaro官网下载对应架构的gcc-linaro-xxx-x86_64_arm-linux-gnueabihf.tar.xz
  2. 解压到指定目录:我习惯放在 /opt/toolchains/ 下
  3. 配置环境变量:将工具链的bin目录加入PATH
  4. 验证安装:运行 arm-linux-gnueabihf-gcc --version
# 下载(以ARMv7 Cortex-A9为例)
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

# 解压
sudo mkdir -p /opt/toolchains
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-

# 使生效
source ~/.bashrc

# 验证
arm-linux-gnueabihf-gcc --version

小技巧:我习惯把 export CROSS_COMPILE=arm-linux-gnueabihf- 也写进 ~/.bashrc。这样编译内核、U-Boot时,直接 make 就行,不用每次手动指定。省事不少。

验证交叉编译

写一个简单的hello.c,交叉编译后放到开发板上跑。如果能在ARM板上打印出"Hello, Embedded!",说明工具链没问题。

// hello.c
#include <stdio.h>

int main(void)
{
    printf("Hello, Embedded!\n");
    return 0;
}

// 交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c

// 查看文件信息
file hello
// 输出: hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)

注意:交叉编译出来的程序,在PC上跑不了。别试,会报"Exec format error"。我刚开始学的时候就犯过这个傻——在PC上直接运行ARM程序,然后一脸懵。

2.2 TFTP服务配置

TFTP(Trivial File Transfer Protocol)是个轻量级的文件传输协议。在嵌入式开发中,它主要用来在U-Boot阶段把内核镜像(zImage)、设备树(dtb)从PC传到开发板的内存里。

为什么不用FTP或SCP?因为U-Boot里没有完整的TCP/IP协议栈,TFTP基于UDP,实现简单,U-Boot原生支持。

安装与配置

# 安装TFTP服务器
sudo apt-get update
sudo apt-get install tftpd-hpa

# 创建TFTP根目录
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 修改配置文件 /etc/default/tftpd-hpa
sudo vim /etc/default/tftpd-hpa

配置文件内容如下:

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/srv/tftp"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"
# 重启服务
sudo systemctl restart tftpd-hpa
sudo systemctl enable tftpd-hpa

# 检查服务状态
sudo systemctl status tftpd-hpa

避坑指南:我曾经遇到过TFTP传文件总是超时,折腾了半天才发现是防火墙没关。Ubuntu默认开了ufw,记得放行UDP 69端口:sudo ufw allow 69/udp。或者干脆在开发阶段关掉防火墙。

测试TFTP

在PC上放一个测试文件,然后在开发板的U-Boot里用tftp命令下载:

# PC端:把内核镜像放到TFTP目录
cp arch/arm/boot/zImage /srv/tftp/
cp arch/arm/boot/dts/your-board.dtb /srv/tftp/

# 开发板U-Boot端
setenv serverip 192.168.1.100   # PC的IP
setenv ipaddr 192.168.1.10      # 开发板的IP
tftp 0x82000000 zImage          # 下载到内存地址0x82000000
tftp 0x83000000 your-board.dtb

2.3 NFS服务配置

NFS(Network File System)比TFTP更进一步。它让开发板通过网络挂载PC上的一个目录,当作自己的根文件系统来用。好处是:你在PC上修改文件,开发板立即可见,不用反复烧写。

我个人的开发习惯是:前期调试阶段100%用NFS启动,等所有驱动和应用都调通了,再打包成镜像烧到板子的Flash里。

安装与配置

# 安装NFS服务器
sudo apt-get install nfs-kernel-server

# 创建NFS共享目录(存放根文件系统)
sudo mkdir -p /srv/nfs/rootfs
sudo chmod 777 /srv/nfs/rootfs

# 编辑 /etc/exports
sudo vim /etc/exports

在 /etc/exports 中添加:

/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
参数 含义
* 允许所有IP访问(开发阶段方便,生产环境要限制)
rw 读写权限
sync 同步写入,保证数据一致性
no_subtree_check 关闭子树检查,提升性能
no_root_squash 允许开发板以root权限访问,否则会有权限问题
# 重启NFS服务
sudo systemctl restart nfs-kernel-server

# 验证共享
showmount -e localhost

U-Boot中设置NFS启动

# 在U-Boot命令行设置
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.10
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1

# 设置NFS启动参数
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=192.168.1.10:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'

# 下载内核和设备树
tftp 0x82000000 zImage
tftp 0x83000000 your-board.dtb

# 启动
bootz 0x82000000 - 0x83000000

注意:NFS启动时,根文件系统目录 /srv/nfs/rootfs 里必须包含完整的Linux根文件系统结构(bin、sbin、etc、lib、usr、dev、proc、sys等)。否则内核启动到挂载根文件系统时会崩溃。我见过有人只放了一个空目录,然后问为什么启动不了——嗯,内核也需要吃饭的。

2.4 串口终端使用

串口是嵌入式开发中最基础、最可靠的调试接口。不管网络通不通、屏幕亮不亮,只要串口线接对了,你就能看到U-Boot的启动信息,就能跟开发板交互。

我个人推荐用minicom,轻量、稳定、配置简单。当然,你也可以用screen、putty(Windows下),但minicom在Linux下最顺手。

安装与配置

# 安装minicom
sudo apt-get install minicom

# 查看串口设备
ls -l /dev/ttyUSB*   # USB转串口通常是ttyUSB0
ls -l /dev/ttyS*     # 原生串口通常是ttyS0

# 配置minicom
sudo minicom -s

在minicom的配置菜单中,进入"Serial port setup",设置:

参数 典型值 说明
Serial Device /dev/ttyUSB0 串口设备文件,根据实际情况修改
Bps/Par/Bits 115200 8N1 波特率115200,8数据位,无校验,1停止位
Hardware Flow Control No 关闭硬件流控,否则可能无法输入
Software Flow Control No 关闭软件流控

小技巧:配置完成后,选择"Save setup as dfl"保存为默认配置。下次直接运行 sudo minicom 就行。我习惯再按 Ctrl+A 然后 Z 查看帮助,Ctrl+A 然后 X 退出——这些快捷键记熟了,操作起来飞快。

常见问题排查

  • 串口没输出:检查波特率是不是115200?检查串口线是不是交叉线(Tx接Rx,Rx接Tx)?我犯过最蠢的错误是用了直连线,折腾了半小时才发现。
  • 乱码:波特率不匹配。开发板一般是115200,但也有用57600或9600的,看芯片手册。
  • 无法输入:检查硬件流控是否关闭。有些USB转串口模块的RTS/CTS引脚悬空会导致输入被阻塞。
  • 权限问题:sudo usermod -a -G dialout $USER 把当前用户加入dialout组,然后注销重新登录,就不用每次sudo了。

2.5 环境验证:一条龙测试

所有服务配置完成后,我建议做一次完整的验证测试。确保工具链能编译、TFTP能传文件、NFS能挂载、串口能交互。这四样都通了,后面的开发才能顺利进行。

# 1. 验证交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c
file hello   # 确认是ARM ELF

# 2. 验证TFTP
cp hello /srv/tftp/
# 在开发板U-Boot中: tftp 0x82000000 hello

# 3. 验证NFS
# 在开发板Linux中:
mount -t nfs -o nolock 192.168.1.100:/srv/nfs/rootfs /mnt
ls /mnt   # 应该能看到PC上的文件

# 4. 验证串口
# 如果能看到U-Boot启动信息,能在终端输入命令,说明串口正常

我的经验:环境搭建这一步,宁可多花一小时验证,也不要急着写代码。我见过太多人工具链没装对就编译内核,编译报错后怀疑人生,最后发现是交叉编译器版本不对。基础打牢,后面才走得快。