2. GPIO驱动开发环境搭建:交叉编译工具链配置、内核源码树准备、设备树编译与烧录
嵌入式Linux GPIO驱动开发与普通PC上的驱动开发有本质区别:目标硬件(ARM/RISC-V/MIPS)资源受限,无法直接运行编译工具链。因此,交叉编译是嵌入式驱动开发的基石。本章将手把手搭建完整的开发环境,涵盖工具链安装、内核源码树准备、设备树编译与烧录验证,确保后续章节的驱动代码能正确编译并运行在目标板上。
2.1 交叉编译工具链配置
交叉编译工具链由 编译器(gcc)、链接器(ld)、汇编器(as)、库(glibc/uClibc) 及 调试器(gdb) 组成。针对ARM目标,常用工具链有:
| 工具链名称 | 目标架构 | 提供商 | 前缀 | 适用场景 |
|---|---|---|---|---|
| arm-linux-gnueabihf-gcc | ARMv7-A (硬浮点) | Linaro / 芯片厂商 | arm-linux-gnueabihf- | STM32MP1、i.MX6ULL、AM335x |
| aarch64-linux-gnu-gcc | ARMv8-A (64位) | Linaro / 芯片厂商 | aarch64-linux-gnu- | 树莓派4/5、RK3588、Jetson |
| riscv64-linux-gnu-gcc | RISC-V 64位 | SiFive / 社区 | riscv64-linux-gnu- | K210、全志D1、StarFive |
2.1.1 下载与解压工具链
以STM32MP157为例,从 ST官方 或 Linaro 获取预编译工具链:
# 创建开发目录
mkdir -p ~/gpio_workspace/toolchain
cd ~/gpio_workspace/toolchain
# 下载Linaro ARM gcc (7.5.0版本,稳定可靠)
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
# 解压
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
# 查看解压后的目录
ls gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/
# 输出: arm-linux-gnueabihf bin include lib libexec share
2.1.2 配置环境变量
将工具链的 bin 目录加入 PATH,并设置 CROSS_COMPILE 前缀:
# 编辑 ~/.bashrc 或 ~/.zshrc
export PATH=$PATH:~/gpio_workspace/toolchain/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
# 使配置生效
source ~/.bashrc
# 验证工具链
arm-linux-gnueabihf-gcc --version
# 输出: arm-linux-gnueabihf-gcc (Linaro GCC 7.5-2019.12) 7.5.0
arm-linux-gnueabihf-gcc 提示 No such file or directory,通常是因为 32位库缺失(主机为64位系统)。执行 sudo apt install libc6:i386 lib32ncurses5 lib32stdc++6 解决。
2.1.3 测试交叉编译
编写一个简单的C程序,验证工具链能否正确生成ARM可执行文件:
// test_gpio.c
#include <stdio.h>
int main(void) {
printf("GPIO Driver Development Environment OK!\n");
return 0;
}
# 交叉编译
arm-linux-gnueabihf-gcc test_gpio.c -o test_gpio_arm
# 查看生成文件的架构
file test_gpio_arm
# 输出: test_gpio_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
看到 ARM, EABI5 字样,说明交叉编译工具链配置成功。
CROSS_COMPILE 环境变量在后续编译内核模块时会被Makefile自动使用,务必正确设置。建议将上述 export 命令写入 ~/.bashrc 永久生效。
2.2 内核源码树准备
GPIO驱动需要依赖内核头文件、模块编译框架和Kconfig配置。因此必须准备一份与目标板运行内核版本一致的 内核源码树,并完成至少一次编译。
2.2.1 获取内核源码
从芯片厂商或内核官网获取源码:
# 创建内核源码目录
mkdir -p ~/gpio_workspace/kernel
cd ~/gpio_workspace/kernel
# 以STM32MP157的Linux 5.10.10为例(ST官方分支)
git clone -b v5.10.10-stm32mp https://github.com/STMicroelectronics/linux.git stm32mp1-kernel
# 进入源码目录
cd stm32mp1-kernel
2.2.2 配置内核
使用目标板对应的 默认配置文件(defconfig):
# 清理之前的编译产物
make mrproper
# 加载STM32MP157的默认配置
make ARCH=arm stm32mp1_defconfig
# 可选:使用menuconfig进行微调(如开启GPIO调试)
make ARCH=arm menuconfig
在menuconfig中,建议确认以下选项已开启:
| 配置项 | 路径 | 说明 |
|---|---|---|
CONFIG_GPIOLIB |
Device Drivers → GPIO Support | GPIO核心库,必须开启 |
CONFIG_GPIO_SYSFS |
Device Drivers → GPIO Support → /sys/class/gpio/… | 用户空间GPIO访问接口(调试用) |
CONFIG_GPIO_CDEV |
Device Drivers → GPIO Support → GPIO character device | 字符设备接口(推荐) |
CONFIG_OF_GPIO |
Device Drivers → GPIO Support → OF GPIO support | 设备树GPIO解析支持 |
2.2.3 编译内核与模块
# 设置并行编译(根据CPU核心数调整,例如4核用-j4)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 dtbs
# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules
# 安装模块到临时目录(可选)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=./modules modules_install
编译成功后,会在 arch/arm/boot/ 下生成 zImage,在 arch/arm/boot/dts/ 下生成 *.dtb 文件。
make M=drivers/gpio 仅编译GPIO子系统,大幅缩短时间。
2.3 设备树编译与烧录
设备树(Device Tree)是描述硬件资源的 数据结构,Linux内核通过它来识别GPIO控制器、中断控制器、外设地址等。驱动开发中,修改设备树后必须重新编译并烧录到目标板。
2.3.1 设备树源文件(.dts)结构
一个典型的GPIO节点定义如下:
// stm32mp157.dtsi (SoC级)
&gpioa {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x50002000 0x400>;
clocks = <&rcc GPIOA>;
};
// 板级文件(如stm32mp157d-atk.dts)
&gpioa {
status = "okay";
};
// 用户自定义外设节点
my_gpio_device {
compatible = "mycompany,gpio-sample";
gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>; // PA5
interrupt-parent = <&gpioa>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
};
gpio-controller:标记该节点为GPIO控制器#gpio-cells:GPIO描述符需要的参数个数(通常为2:引脚号 + 标志)interrupt-controller:标记该节点为中断控制器#interrupt-cells:中断描述符需要的参数个数(通常为2:中断号 + 触发类型)gpios/interrupts:引用具体的GPIO引脚和中断配置
2.3.2 编译设备树
设备树编译器(dtc)将 .dts 编译为二进制 .dtb:
# 方法一:通过内核Makefile编译(推荐)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 方法二:手动使用dtc编译
cd ~/gpio_workspace/kernel/stm32mp1-kernel
./scripts/dtc/dtc -I dts -O dtb -o arch/arm/boot/dts/stm32mp157d-atk.dtb arch/arm/boot/dts/stm32mp157d-atk.dts
# 验证dtb文件
file arch/arm/boot/dts/stm32mp157d-atk.dtb
# 输出: Device Tree Blob version 17, size=..., boot CPU=0x0
2.3.3 烧录设备树到目标板
根据目标板的启动方式(SD卡、eMMC、NAND Flash、TFTP),烧录方法不同。以 SD卡启动 为例:
# 1. 将SD卡插入主机,查看设备节点
lsblk
# 假设SD卡为 /dev/sdb
# 2. 挂载SD卡的boot分区(通常为FAT32格式)
sudo mount /dev/sdb1 /mnt/boot
# 3. 备份原设备树(可选)
sudo cp /mnt/boot/stm32mp157d-atk.dtb /mnt/boot/stm32mp157d-atk.dtb.bak
# 4. 复制新编译的设备树
sudo cp arch/arm/boot/dts/stm32mp157d-atk.dtb /mnt/boot/
# 5. 同步并卸载
sync
sudo umount /mnt/boot
- 确保设备树文件名与uboot中设置的
fdtfile环境变量一致,否则内核无法加载。 - 若使用TFTP网络启动,将
.dtb文件复制到TFTP服务器目录即可。 - 烧录后务必
sync并安全卸载,避免数据损坏。
2.4 开发环境整体架构
下图展示了交叉编译环境、内核源码树、设备树与目标板之间的数据流关系:
2.5 环境验证清单
完成上述步骤后,使用以下清单逐项确认环境就绪:
| # | 验证项 | 验证方法 | 预期结果 |
|---|---|---|---|
| 1 | 交叉编译器可用 | arm-linux-gnueabihf-gcc --version |
显示版本号,无报错 |
| 2 | 内核源码树完整 | ls arch/arm/boot/zImage |
文件存在(已编译) |
| 3 | 设备树编译成功 | file arch/arm/boot/dts/stm32mp157d-atk.dtb |
显示 Device Tree Blob |
| 4 | 烧录后目标板启动 | 串口终端查看内核日志 | 内核启动,无设备树错误 |
| 5 | GPIO子系统加载 | ls /sys/class/gpio/ |
显示 gpiochip0 等目录 |