2、驱动开发环境搭建:交叉编译工具链配置、Linux内核源码获取与编译、设备树基础语法与编译
好,咱们正式开始动手了。这一章我带你搭环境。别小看这一步,我见过太多人卡在环境上,代码写得再好也跑不起来。说白了,环境搭不对,后面全是白费。
2.1 交叉编译工具链配置
先说说什么是交叉编译。你想想看,你的开发机是x86架构,但目标板是ARM架构。你不能直接在x86上编译出ARM能跑的程序吗?当然能,但需要一套特殊的工具链——交叉编译工具链。
我个人习惯用Linaro提供的工具链,稳定且社区支持好。以ARM Cortex-A系列为例,我常用的是gcc-arm-linux-gnueabihf。
核心要点:工具链的命名规则其实告诉了你目标平台的信息。比如arm-linux-gnueabihf,arm是架构,linux是操作系统,gnueabihf表示使用glibc且支持硬件浮点。
安装步骤很简单,我直接贴命令:
# 下载工具链
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
# 解压到指定目录
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/
# 配置环境变量
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
# 验证是否安装成功
arm-linux-gnueabihf-gcc --version
我的小技巧:别每次都手动export。我习惯把路径写到~/.bashrc里,这样每次打开终端自动生效。另外,建议用update-alternatives管理多个工具链版本,切换起来特别方便。
我在项目中遇到过一个问题:编译出来的程序在板子上跑,一运行就报Illegal instruction。查了半天,原来是工具链的浮点ABI选错了。板子支持硬件浮点,但我用了gnueabi(软浮点)的工具链。嗯,这里要注意,一定要确认目标板的浮点特性。
避坑指南:我曾经因为工具链版本太新,导致内核编译报一堆莫名其妙的错误。后来发现,内核源码对工具链版本有要求。比如Linux 4.19内核,建议用gcc 7.x系列,太新或太旧都可能出问题。
2.2 Linux内核源码获取与编译
内核源码怎么拿?我推荐直接从官方仓库clone。别去那些第三方网站下,版本混乱不说,还可能被植入后门。
# 从官方仓库获取
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 切换到指定版本
cd linux
git checkout v4.19.140
你可能会问:为什么要指定版本?因为多摄像头驱动往往依赖特定的内核API。我在做IMX219双摄同步时,发现4.19版本的V4L2框架对同步触发支持最好。新版本虽然功能多,但有些接口变了,反而麻烦。
编译内核前,先配置。我个人习惯这样:
# 先加载默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
# 然后手动调整
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
在menuconfig里,有几个关键选项必须打开:
| 配置项 | 路径 | 说明 |
|---|---|---|
| CONFIG_MEDIA_SUPPORT | Device Drivers → Multimedia support | 多媒体框架基础 |
| CONFIG_V4L2_FWNODE | Device Drivers → Multimedia support → V4L2 fwnode | 设备树解析支持 |
| CONFIG_VIDEO_V4L2_SUBDEV_API | Device Drivers → Multimedia support → V4L2 sub-device API | 子设备API,多摄像头必备 |
经验之谈:我刚开始做多摄像头时,忘了开CONFIG_VIDEO_V4L2_SUBDEV_API,结果驱动注册子设备时一直报错。找了三天才找到原因。你想想看,这种低级错误多耽误事。
配置完就可以编译了:
# 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
编译时间取决于你的机器性能。我笔记本上大概要10分钟。这段时间你可以去泡杯咖啡,或者看看设备树的资料——嗯,就是下面要讲的。
2.3 设备树基础语法与编译
设备树,说白了就是描述硬件信息的配置文件。以前老内核用板级文件(board file),每换一个板子就要改代码。设备树把硬件描述和驱动代码分开了,改硬件只需要改.dts文件。
先看一个最简单的设备树节点:
/dts-v1/;
/ {
model = "My Multi-Camera Board";
compatible = "vendor,board-rev1";
cpus {
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
};
i2c0: i2c@ff010000 {
compatible = "snps,designware-i2c";
reg = <0xff010000 0x1000>;
interrupts = <0 35 4>;
clock-frequency = <400000>;
status = "okay";
camera0: camera@10 {
compatible = "sony,imx219";
reg = <0x10>;
clocks = <&clk 12>;
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
};
camera1: camera@12 {
compatible = "sony,imx219";
reg = <0x12>;
clocks = <&clk 13>;
reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
};
};
};
这个例子展示了几个关键概念:
- 根节点:
/,整个设备树的起点 - 节点名:如
i2c0、camera0,后面可以跟@地址 - 属性:
compatible、reg、interrupts等 - 标签:如
i2c0:,方便其他节点引用
我的习惯:给每个摄像头节点加一个label属性,比如label = "cam-left"。这样在驱动里可以通过label区分是左摄像头还是右摄像头,调试时特别直观。
设备树编译很简单:
# 编译.dts为.dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译.dtb为.dts(调试用)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
我在项目中遇到过一个问题:设备树编译通过了,但内核启动时死活认不到摄像头。后来用dtc -I dtb -O dts反编译一看,发现reg属性里的地址写错了。嗯,这种错误最坑人,因为编译不报错,只有运行时才暴露。
避坑指南:我曾经在设备树里用了GPIO_ACTIVE_HIGH,但实际电路是低电平复位。结果摄像头一直处于复位状态,I2C通信全部失败。所以,写设备树前一定要对着原理图仔细核对每个引脚的电气特性。
最后,说一个多摄像头场景下的设备树要点。如果你有多个同型号摄像头,它们的compatible属性可以相同,但reg(I2C地址)必须不同。另外,每个摄像头的复位引脚、时钟源要独立,这样才能单独控制。
好了,环境搭建就这些。下一章我们开始写真正的驱动代码。到时候你会看到,今天搭的环境每一步都有用。