2. 开发环境搭建:交叉编译工具链、内核源码树、设备树编译工具、TFTP/NFS服务配置

做嵌入式传感器驱动开发,环境搭建是第一道坎。很多人觉得这事简单,照着教程敲几条命令就行。但我见过太多人卡在这一步——工具链版本不对、内核树没配好、设备树编译不过,折腾一整天连个LED都点不亮。

今天我把这套环境拆开揉碎了讲。你跟着我的节奏来,保证一次搞定。

2.1 交叉编译工具链

说白了,交叉编译就是在PC上编译出能在ARM、RISC-V这类嵌入式芯片上跑的程序。你的开发机是x86架构,目标板是ARM架构,编译器得能生成ARM的机器码。

核心原则:工具链版本必须与内核版本匹配。我用过gcc 4.9配Linux 4.14,也用过gcc 9.3配Linux 5.10。跨大版本混用,轻则编译警告,重则运行时莫名其妙崩溃。

2.1.1 获取工具链

我个人习惯用Linaro提供的预编译工具链,省心。ARM Cortex-A系列用gcc-arm-linux-gnueabihf,Cortex-M系列用gcc-arm-none-eabi。

# 下载ARM 32位工具链
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

# 解压到/opt目录
sudo 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

小技巧:把export命令写到~/.bashrc里,每次开终端自动生效。我习惯再加一句export ARCH=arm和export CROSS_COMPILE=arm-linux-gnueabihf-,后面编译内核和设备树时能省不少事。

2.1.2 验证工具链

装完先跑个hello world,确认能用。

arm-linux-gnueabihf-gcc -v
# 看到gcc version 7.5.0就对了

# 写个测试程序
echo '#include <stdio.h>
int main() { printf("Hello Sensor!\n"); return 0; }' > test.c

arm-linux-gnueabihf-gcc -o test test.c
file test
# 输出: test: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)

看到ARM, EABI5,说明编译出来的确实是ARM指令集。我曾经遇到有人用x86的gcc编译,放到板子上直接报Exec format error——嗯,这种低级错误我也犯过。

2.2 内核源码树

驱动开发离不开内核源码。不是要你把整个内核都看懂,但至少得把编译环境搭好。

2.2.1 获取内核源码

我建议从官方主线拉取,别用发行版自带的。主线代码更新快,文档也全。

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v5.10  # 选一个LTS版本,稳定

注意:内核源码树不要放在共享目录或NTFS分区里,否则编译时符号链接会出问题。我吃过这个亏,编译到一半报错,查了半天才发现是文件系统不支持符号链接。

2.2.2 配置和编译内核

先清理,再配置,最后编译。三步走,别跳。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

-j4是并行编译,4核CPU用4线程。你要是8核机器,可以试试-j8,但别超过物理核心数,否则反而慢。

编译完会在arch/arm/boot/下生成zImage。这就是我们要的内核镜像。

2.2.3 编译内核模块

传感器驱动通常以内核模块形式存在。编译模块需要内核源码树,而且必须和运行内核版本一致。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=./modules

模块会安装到./modules/lib/modules/5.10.0/目录下。把这个目录打包,传到板子上解压到/lib/modules/就行。

2.3 设备树编译工具

设备树(Device Tree)是描述硬件信息的文件。传感器驱动通过设备树获取寄存器地址、中断号、GPIO引脚等信息。

2.3.1 安装dtc工具

设备树编译器叫dtc,把.dts源文件编译成.dtb二进制文件。

# 方法一:从内核源码编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

# 方法二:单独安装dtc
sudo apt-get install device-tree-compiler
dtc --version  # 确认版本

个人经验:我习惯用内核源码里的dtc,版本和内核完全匹配。单独安装的dtc有时会解析不了新版内核的dts语法。

2.3.2 编译设备树

假设你写了一个传感器设备树文件my-sensor.dts:

/dts-v1/;
#include "imx6ul.dtsi"

/ {
    model = "My Sensor Board";
    compatible = "my,imx6ul-sensor";

    &i2c1 {
        clock-frequency = <100000>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_i2c1>;

        temp_sensor: tmp102@48 {
            compatible = "ti,tmp102";
            reg = <0x48>;
            interrupt-parent = <&gpio1>;
            interrupts = <18 IRQ_TYPE_EDGE_FALLING>;
        };
    };
};

编译命令:

dtc -I dts -O dtb -o my-sensor.dtb my-sensor.dts

反编译查看:

dtc -I dtb -O dts -o my-sensor.dts my-sensor.dtb

这个操作我调试时经常用。板子上跑的是.dtb,但我想确认它是不是我改的那个版本,反编译一看便知。

2.4 TFTP/NFS服务配置

开发阶段最烦的就是反复烧写镜像。用TFTP加载内核,NFS挂载根文件系统,改完代码直接编译、重启、测试,效率翻倍。

2.4.1 TFTP服务器

TFTP用于从网络加载内核和设备树。配置很简单:

# 安装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"

# 创建目录并设置权限
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp

# 重启服务
sudo systemctl restart tftpd-hpa

把zImage和.dtb文件复制到/srv/tftp/目录下。板子启动时设置bootargs:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs,proto=tcp rw ip=192.168.1.10'
setenv bootcmd 'tftp 0x12000000 zImage; tftp 0x13000000 my-sensor.dtb; bootz 0x12000000 - 0x13000000'
boot

避坑指南:我曾经把TFTP目录权限设成755,结果板子下载文件时一直超时。查了半天才发现是tftp用户对目录没有写权限——虽然下载不需要写,但某些TFTP实现会先创建临时文件。改成777就好了。

2.4.2 NFS服务器

NFS让板子通过网络挂载PC上的根文件系统。改完驱动,在PC上编译,重启板子就能看到效果。

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

# 编辑 /etc/exports,添加共享目录
/srv/nfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

# 创建根文件系统目录
sudo mkdir -p /srv/nfs
# 把制作好的根文件系统解压到这里
sudo tar -xvf rootfs.tar -C /srv/nfs/

# 重启NFS服务
sudo systemctl restart nfs-kernel-server

板子端uboot设置:

setenv nfsargs 'root=/dev/nfs rw nfsroot=192.168.1.100:/srv/nfs,v3,tcp ip=192.168.1.10:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
setenv bootargs 'console=ttymxc0,115200 ${nfsargs}'
saveenv
boot

2.5 环境验证

全部配置完,做个快速验证。写一个简单的内核模块,编译后通过NFS传到板子,加载测试。

// hello_sensor.c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello Sensor Driver!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye Sensor Driver!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

Makefile:

obj-m := hello_sensor.o
KDIR := /home/user/linux
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean

编译、加载、看日志:

make
cp hello_sensor.ko /srv/nfs/root/
# 在板子上
insmod hello_sensor.ko
dmesg | tail -5
# 应该看到: Hello Sensor Driver!

到这里,你的开发环境就全通了。后面写传感器驱动时,改代码→编译→重启板子→看效果,整个流程跑下来不超过30秒。这才是嵌入式开发的正确姿势。

2.6 知识体系总览

下面这张图把本章的核心内容串起来了。你对照着检查,缺哪个补哪个。

传感器驱动开发环境搭建 交叉编译工具链 • Linaro预编译工具链 • ARM/ARM64/RISC-V • 版本匹配原则 • 环境变量配置 内核源码树 • git拉取主线代码 • LTS版本选择 • defconfig配置 • 模块编译 设备树编译工具 • dtc编译器安装 • .dts → .dtb • 反编译调试 • 传感器节点编写 TFTP/NFS服务 • TFTP内核加载 • NFS根文件系统 • uboot参数设置 • 网络启动流程 开发环境集成验证 编写测试模块 → 交叉编译 → TFTP加载 → NFS挂载 → insmod验证 ✅ 开发环境就绪,可以开始写驱动了 💡 四个模块缺一不可。工具链负责编译,内核树提供接口,设备树描述硬件,TFTP/NFS加速调试。

2.7 常见问题速查

问题现象 可能原因 解决方案
编译报错arm-linux-gnueabihf-gcc: command not found 环境变量未设置或路径错误 检查PATH是否包含工具链bin目录
内核编译报No rule to make target 'modules' 内核源码未配置或未编译基础镜像 先执行make defconfig && make zImage
设备树编译报syntax error dts文件语法错误或包含路径不对 检查#include路径,使用-I指定搜索目录
TFTP下载超时 防火墙未关闭或目录权限不足 sudo ufw disable,检查/srv/tftp权限
NFS挂载失败mount: RPC: Unable to receive NFS服务未启动或exports配置错误 sudo systemctl restart nfs-kernel-server,检查/etc/exports

最后说一句:环境搭建这事,一次配好,后面全是收益。别嫌麻烦,也别跳过任何一步。我见过太多人图省事跳过NFS配置,结果每次改驱动都要烧写SD卡,一天下来光等烧写就浪费两小时。磨刀不误砍柴工,真的。