驱动开发环境搭建:Linux内核配置、V4L2框架介绍、I2C/SPI通信基础
做Camera驱动,第一件事不是写代码,而是把环境搭对。我见过太多新手,代码写得挺漂亮,结果编译不过、内核跑不起来,最后发现是内核配置漏了东西。今天咱们就把这摊事理清楚。
Linux内核配置——别让内核成为你的绊脚石
你想想看,一个Camera sensor挂在I2C总线上,数据通过CSI接口进来,如果内核里连V4L2、I2C、Media Controller这些子系统都没打开,驱动写得再好也白搭。
我个人习惯,拿到一个新平台,先做三件事:
- 确认内核版本(
uname -r) - 检查当前配置(
zcat /proc/config.gz或cat /boot/config-$(uname -r)) - 用
make menuconfig逐项核对关键选项
下面这张表,是我做项目时必开的配置项,你可以直接拿来当checklist:
| 配置项 | 路径 | 说明 |
|---|---|---|
CONFIG_VIDEO_V4L2 |
Device Drivers → Multimedia support | V4L2核心框架,不开这个啥都别谈 |
CONFIG_VIDEO_V4L2_SUBDEV_API |
同上 | 子设备API,sensor驱动必须依赖 |
CONFIG_MEDIA_CONTROLLER |
同上 | 媒体控制器,Pipeline管理的基础 |
CONFIG_I2C |
Device Drivers → I2C support | I2C总线支持,sensor控制通道 |
CONFIG_SPI |
Device Drivers → SPI support | 部分sensor或EEPROM用SPI通信 |
CONFIG_VIDEO_OV5640(举例) |
Device Drivers → Multimedia support → Camera sensor drivers | 具体sensor驱动,按实际型号选 |
我曾经踩过的坑: 有一次移植OV5640驱动,折腾了两天sensor就是不出图。最后发现内核配置里 CONFIG_MEDIA_CONTROLLER 没开,导致Pipeline创建失败。嗯,从那以后我每次配内核都会用 grep 扫一遍关键选项。
V4L2框架介绍——Camera驱动的骨架
V4L2,全称Video for Linux 2,是Linux下视频设备驱动的标准框架。说白了,它定义了一套统一的接口,让应用程序可以不管底层是什么sensor、什么桥接芯片,都能用同一套API操作摄像头。
V4L2的核心概念,我总结成四个字:设备、队列、格式、控制。
- 设备节点:
/dev/videoX、/dev/v4l-subdevX,用户空间通过它们操作硬件 - 缓冲区队列:
VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_DQBUF,数据流的核心 - 格式协商:
VIDIOC_S_FMT、VIDIOC_G_FMT,分辨率、像素格式的约定 - 控制接口:
VIDIOC_S_CTRL、VIDIOC_G_CTRL,调节曝光、增益、白平衡等参数
下面这张SVG图,展示了V4L2框架中各个组件的关系:
关键理解: V4L2把Camera驱动拆成了两个角色——桥接驱动(负责数据流、DMA、ISP配置)和子设备驱动(负责sensor寄存器配置)。这种分层设计,让你换sensor时只需要重写子设备驱动,桥接部分基本不动。
I2C/SPI通信基础——控制通道的命脉
Camera sensor的控制通道,99%的情况是I2C,偶尔用SPI。为什么?因为sensor只需要配置寄存器,数据量小,I2C两根线就够了。
I2C通信要点
I2C在Camera驱动里就干一件事:写寄存器、读寄存器。但这里有个坑——sensor的寄存器地址长度和值长度各不相同。
我整理了一份常见sensor的I2C地址格式:
| Sensor型号 | 寄存器地址宽度 | 数据宽度 | 典型I2C地址 |
|---|---|---|---|
| OV5640 | 16位 | 8位 | 0x3C |
| IMX219 | 16位 | 8位 | 0x20 |
| AR0144 | 8位 | 8位 | 0x48 |
| MT9V034 | 16位 | 16位 | 0x90 |
在Linux驱动里,I2C读写通常用 i2c_transfer 或 i2c_smbus_read/write 系列函数。我个人习惯封装一个通用读写函数:
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
struct i2c_msg msg;
u8 buf[3];
int ret;
buf[0] = reg >> 8; // 高字节
buf[1] = reg & 0xFF; // 低字节
buf[2] = val; // 数据
msg.addr = client->addr;
msg.flags = 0; // 写操作
msg.len = 3;
msg.buf = buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "I2C write failed: %d\n", ret);
return ret;
}
return 0;
}
小技巧: 调试I2C通信时,先用 i2cdetect -y <bus> 确认sensor是否在总线上。如果检测不到,八成是硬件问题——上拉电阻没焊、地址不对、或者sensor没上电。我在项目里至少有一半的I2C问题出在硬件上,不是软件。
SPI通信要点
SPI在Camera领域用得少,但有些高速sensor或EEPROM会用。SPI比I2C快,但线多(4根),适合大数据量传输。
Linux内核里SPI驱动用 spi_write、spi_read、spi_write_then_read 这些接口。注意SPI是全双工的,读写可以同时进行。
static int sensor_spi_write(struct spi_device *spi, u8 reg, u8 val)
{
u8 tx_buf[2] = {reg, val};
return spi_write(spi, tx_buf, 2);
}
我曾经踩过的坑: 有一次用SPI接口的sensor,死活读不到正确的ID。折腾了半天,发现是SPI的时钟极性(CPOL)和相位(CPHA)配置错了。sensor手册里写的是模式0(CPOL=0, CPHA=0),我默认用了模式3。嗯,从那以后我每次都会仔细看sensor手册里的SPI时序图。
环境搭建的完整流程
最后,我把整个环境搭建的步骤串起来,你照着做就行:
- 获取内核源码:从芯片厂商(如Rockchip、Allwinner、NXP)获取BSP内核,或从kernel.org下载主线内核
- 配置内核:
make ARCH=arm64 defconfig(按平台选),然后用make menuconfig打开V4L2、I2C、Media Controller等选项 - 编译内核:
make -j$(nproc),生成zImage/Image和dtb文件 - 编译设备树:在dts文件中添加sensor节点,指定I2C地址、复位引脚、电源等
- 烧录并启动:将内核、dtb、根文件系统烧录到目标板,启动后用
dmesg | grep -i sensor检查驱动是否加载 - 验证通信:用
i2cdetect确认I2C设备,用v4l2-ctl --list-devices查看V4L2设备节点
环境搭好了,后面写驱动、调ISP才有根基。别急,一步一步来。