第三章 开发环境搭建:交叉编译工具链、内核配置、设备树与驱动加载
好,咱们进入第三章。这一章是真正动手前的“地基工程”。很多初学者一上来就急着写代码,结果编译都过不了,或者烧进去系统起不来——我当年也干过这种蠢事。环境搭不对,后面全是白费功夫。
嵌入式Camera开发,说白了就是在PC上写代码,编译成ARM架构能跑的程序,再扔到板子上运行。这个“PC编译、ARM运行”的过程,就是交叉编译。咱们一步步来。
3.1 交叉编译工具链:你的“翻译官”
工具链是什么?就是一套把C代码变成机器码的工具集合:编译器、链接器、调试器、库文件等等。对于嵌入式开发,我们用的是交叉编译工具链——在x86的PC上生成ARM的二进制文件。
核心概念:交叉编译 = 宿主机(x86)上编译,目标机(ARM)上运行。
3.1.1 工具链的选择
我个人习惯用Linaro提供的GCC工具链,稳定、社区活跃。针对Camera开发,我建议选择arm-linux-gnueabihf-系列,因为大多数Camera SoC都带硬件浮点单元(FPU),用hf版本能充分发挥性能。
| 工具链前缀 | 适用场景 | 浮点支持 |
|---|---|---|
arm-linux-gnueabi- |
无硬件FPU的ARM芯片 | 软浮点 |
arm-linux-gnueabihf- |
带硬件FPU的ARM芯片(如Cortex-A系列) | 硬浮点 |
aarch64-linux-gnu- |
64位ARM芯片(如树莓派4、RK3588) | 硬浮点 |
我的经验:如果你用的是全志V3s、君正T31这类Camera芯片,选arm-linux-gnueabihf-准没错。我之前有个项目用了软浮点工具链,结果图像处理算法跑起来慢得离谱——后来才发现是浮点单元没使上劲。
3.1.2 安装与验证
安装其实很简单,下载解压就行。以Ubuntu 22.04为例:
# 下载Linaro 7.5版本(稳定,兼容性好)
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
# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt
# 添加环境变量(建议写到 ~/.bashrc 里)
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
# 验证是否安装成功
arm-linux-gnueabihf-gcc --version
看到版本号输出,就说明工具链装好了。嗯,这里要注意:一定要把路径加到~/.bashrc里,不然每次开新终端都要手动export,烦得很。
3.2 Linux内核配置:裁剪出你的Camera系统
内核配置,说白了就是告诉Linux:你要支持哪些硬件、哪些功能。Camera开发中,我们需要开启V4L2框架、ISP驱动、视频输入接口等。
3.2.1 获取内核源码
我建议直接从芯片厂商的官方仓库拉代码,比如全志的linux-sunxi、Rockchip的rk-linux。用主线内核也行,但有些Camera驱动可能没合入。
# 以全志V3s为例
git clone https://github.com/Lichee-Pi/linux.git -b zero-4.14.y
3.2.2 配置内核选项
配置内核有三种方式:make menuconfig(文本界面)、make xconfig(图形界面)、make defconfig(默认配置)。我习惯用menuconfig,轻量、快速。
Camera开发必须开启的选项:
- V4L2框架:
CONFIG_VIDEO_V4L2=y - Video Capture接口:
CONFIG_VIDEO_CAPTURE_DRIVERS=y - 具体的Camera传感器驱动:比如OV5640、GC2053等
- ISP驱动:如果SoC内置ISP,要选上对应的驱动模块
- DMA-BUF:用于零拷贝传输图像数据,
CONFIG_DMA_SHARED_BUFFER=y
我曾经踩过的坑:有一次我忘了开启CONFIG_VIDEO_V4L2,结果编译出来的内核根本没有/dev/video节点。查了两天才发现是内核配置漏了——从那以后我每次配置完都会grep一遍关键选项。
3.2.3 编译内核
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 编译内核镜像
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
编译完成后,在arch/arm/boot/下会生成zImage,在arch/arm/boot/dts/下生成.dtb文件。
3.3 设备树编写:告诉内核硬件长什么样
设备树(Device Tree)是Linux用来描述硬件信息的机制。以前老内核用板级文件(board-xxx.c),硬编码一大堆,维护起来想死。设备树把硬件描述和内核代码分开了,改硬件只需要改.dts文件,不用重新编译内核。
3.3.1 设备树的基本结构
一个典型的Camera设备树节点长这样:
/ {
model = "MyCameraBoard";
compatible = "vendor,myboard";
&i2c0 {
status = "okay";
clock-frequency = <400000>;
ov5640: camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
clocks = <&clks CLK_CAMERA_MCLK>;
clock-names = "xvclk";
reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi_ep>;
bus-width = <8>;
data-shift = <2>;
hsync-active = <1>;
vsync-active = <0>;
pclk-sample = <1>;
};
};
};
};
&csi {
status = "okay";
port {
csi_ep: endpoint {
remote-endpoint = <&ov5640_ep>;
};
};
};
};
这里有几个关键点:
- compatible:驱动匹配的关键字符串,必须和驱动里的一模一样
- reg:I2C地址,Camera传感器一般是0x3c或0x30
- clocks:主时钟(MCLK),Camera必须有时钟才能工作
- port/endpoint:描述数据连接关系,告诉内核图像数据从哪来、到哪去
我的习惯:写设备树时,先看芯片的参考手册,找到CSI接口的引脚复用关系。然后对照原理图,把GPIO、I2C、时钟这些信息填进去。别凭想象写,一定要看文档。
3.3.2 编译设备树
# 编译单个设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 或者在内核源码目录下
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
3.4 驱动编译与加载:让硬件动起来
驱动有两种编译方式:编译进内核(built-in)和编译成模块(module)。我建议Camera驱动用模块方式,方便调试——改驱动不用重新烧整个内核。
3.4.1 编写简单的Camera驱动模块
#include <linux/module.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
static int __init mycam_init(void)
{
pr_info("MyCamera driver loaded\n");
return 0;
}
static void __exit mycam_exit(void)
{
pr_info("MyCamera driver unloaded\n");
}
module_init(mycam_init);
module_exit(mycam_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple Camera Driver");
3.4.2 编译外部模块
# 编写Makefile
obj-m += mycam.o
all:
make -C /path/to/kernel/source M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
clean:
make -C /path/to/kernel/source M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean
编译后会生成mycam.ko文件。把它拷贝到板子上,用insmod加载:
# 加载驱动
insmod mycam.ko
# 查看加载是否成功
lsmod | grep mycam
# 卸载驱动
rmmod mycam
3.4.3 驱动加载的常见问题
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
insmod: ERROR: could not insert module |
内核版本不匹配 | 用板子上运行的内核源码重新编译驱动 |
Unknown symbol |
依赖的符号未导出 | 检查/proc/kallsyms,确认符号是否存在 |
加载后没有/dev/video节点 |
设备树匹配失败或V4L2注册失败 | 检查dmesg输出,确认compatible是否匹配 |
我曾经遇到的情况:有一次驱动加载成功,但/dev/video0死活不出来。查了半天,发现是设备树里compatible写成了"ovti,ov5640",而驱动里匹配的是"ovti,ov5640"——等等,我写对了啊?后来发现是驱动里有个空格没对齐。嗯,设备树和驱动的字符串匹配是严格逐字节的,多一个空格都不行。
3.5 本章知识体系总览
下面这张图把本章的核心流程串起来了,你一看就明白:
这张图把整个流程串起来了:从工具链、内核源码、设备树、驱动源码开始,经过配置和编译,生成zImage、dtb、ko文件,最后部署到板子上。每一步都环环相扣,缺一不可。
本章核心要点:
- 交叉编译工具链是基础,选对前缀(gnueabihf)很重要
- 内核配置要开启V4L2、Camera传感器、ISP等关键选项
- 设备树是硬件描述文件,compatible字符串必须和驱动严格匹配
- 驱动用模块方式编译,方便调试和更新