2. Sensor驱动基础:I2C通信协议、MIPI CSI-2接口、Sensor寄存器读写操作、V4L2子设备驱动模型
在RK平台Camera系统中,Sensor驱动是连接硬件与上层ISP的桥梁。本章聚焦于驱动开发的核心基础:I2C通信协议、MIPI CSI-2接口、Sensor寄存器读写操作以及V4L2子设备驱动模型。掌握这些内容,是后续进行ISP图像调优联动的先决条件。
2.1 I2C通信协议
I2C(Inter-Integrated Circuit)是Sensor与主控SoC之间最常用的控制总线,用于配置Sensor内部寄存器,如曝光时间、增益、帧率、输出格式等。I2C采用主从架构,RK SoC作为主设备,Sensor作为从设备。
2.1.1 I2C总线物理层与协议层
| 特性 | 描述 |
|---|---|
| 信号线 | SCL(时钟线)、SDA(数据线),均为开漏输出,需上拉电阻 |
| 地址 | 7位或10位从机地址,Sensor通常使用7位地址(如0x30) |
| 速率 | 标准模式100kHz,快速模式400kHz,高速模式3.4MHz |
| 传输方向 | 主设备控制时钟,主发从收(写)或主收从发(读) |
| 起始条件 | SCL高电平时,SDA由高变低 |
| 停止条件 | SCL高电平时,SDA由低变高 |
2.1.2 I2C写操作时序
写操作是驱动中最常用的操作,用于向Sensor的指定寄存器地址写入配置值。标准写操作流程如下:
- 起始信号:主设备发送START条件。
- 从机地址+写位:发送7位从机地址,最后一位为0(写操作)。
- ACK应答:从机拉低SDA应答。
- 寄存器地址:发送16位或8位寄存器地址(高字节在前)。
- ACK应答:从机应答。
- 写入数据:发送8位数据。
- ACK应答:从机应答。
- 停止信号:主设备发送STOP条件。
关键点:多数Sensor支持连续写(burst write),即发送寄存器地址后连续发送多个字节数据,地址自动递增,可大幅提高配置效率。
2.1.3 I2C读操作时序
读操作用于读取Sensor的状态寄存器或当前配置值。由于I2C总线是半双工,读操作需要先写寄存器地址,再重新发起读传输:
- 起始信号:主设备发送START条件。
- 从机地址+写位:发送从机地址,写位为0。
- ACK应答:从机应答。
- 寄存器地址:发送要读取的寄存器地址。
- ACK应答:从机应答。
- 重复起始信号:主设备再次发送START条件。
- 从机地址+读位:发送从机地址,读位为1。
- ACK应答:从机应答。
- 读取数据:主设备读取8位数据,主设备发送NACK表示读取结束。
- 停止信号:主设备发送STOP条件。
驱动开发提示:在RK平台中,I2C读写操作通常通过Linux内核的I2C子系统API完成,如i2c_master_send()和i2c_master_recv(),或使用i2c_transfer()进行复合传输。
2.2 MIPI CSI-2接口
MIPI CSI-2(Camera Serial Interface 2)是移动设备中最主流的摄像头图像数据传输接口。Sensor通过CSI-2将原始图像数据(RAW Bayer、YUV等)传输到RK SoC的ISP或DVP模块。
2.2.1 CSI-2物理层与协议层
| 层级 | 说明 |
|---|---|
| PHY层(D-PHY) | 差分信号传输,包含时钟通道(CLK)和数据通道(DATA0~DATA3),每个通道一对差分线 |
| Lane数量 | 1~4 Lane,常用2 Lane或4 Lane,带宽随Lane数线性增加 |
| 协议层 | 定义数据包格式:短包(帧起始/结束、行起始/结束)和长包(图像数据) |
| 数据类型 | RAW8/10/12/14、YUV422、RGB565等,通过Data Type(DT)字段标识 |
| 虚拟通道 | 支持4个虚拟通道(VC0~VC3),可复用同一物理链路传输多路数据 |
2.2.2 CSI-2数据包结构
长包(Long Packet)是传输图像数据的主要形式,其结构如下:
- 包首(Packet Header):4字节,包含数据类型(DT)、虚拟通道(VC)、字计数(WC)和ECC校验。
- 数据负载(Data Payload):图像像素数据,字节对齐。
- 包尾(Packet Footer):2字节CRC校验。
常见问题:CSI-2链路不稳定通常表现为图像花屏、丢帧或无法出图。排查方向包括:时钟频率匹配、Lane数配置、差分信号质量(SI)、以及PHY的均衡和预加重设置。
2.3 Sensor寄存器读写操作
Sensor的寄存器读写是驱动开发中最频繁的操作。RK平台通常通过I2C或SPI总线访问Sensor寄存器。下面以I2C为例,展示典型的读写实现。
2.3.1 寄存器读写函数实现
/* sensor_i2c.c - Sensor I2C读写示例 */
#include <linux/i2c.h>
#include <linux/delay.h>
/* 写16位寄存器地址,8位数据 */
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
struct i2c_msg msg;
u8 buf[3];
buf[0] = (reg >> 8) & 0xFF; /* 寄存器高字节 */
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 reg 0x%04x failed: %d\n", reg, ret);
return ret;
}
return 0;
}
/* 读16位寄存器地址,8位数据 */
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
int ret;
struct i2c_msg msg[2];
u8 reg_buf[2];
u8 data_buf[1];
reg_buf[0] = (reg >> 8) & 0xFF;
reg_buf[1] = reg & 0xFF;
/* 先写寄存器地址 */
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = reg_buf;
/* 再读数据 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = data_buf;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 2) {
dev_err(&client->dev, "I2C read reg 0x%04x failed: %d\n", reg, ret);
return ret < 0 ? ret : -EIO;
}
*val = data_buf[0];
return 0;
}
2.3.2 批量寄存器配置
Sensor初始化通常需要配置大量寄存器。驱动中常使用结构体数组定义初始化序列:
/* sensor_init.h - 寄存器配置表 */
struct sensor_regval {
u16 reg;
u8 val;
};
/* 初始化序列示例 */
static const struct sensor_regval sensor_init_regs[] = {
{0x0103, 0x01}, /* 软件复位 */
{0x0100, 0x00}, /* 进入待机模式 */
{0x3034, 0x1A}, /* PLL配置 */
{0x3035, 0x21},
{0x3036, 0x69},
{0x3037, 0x03},
{0x3038, 0x00},
{0x3039, 0x00},
{0x303A, 0x00},
{0x303B, 0x1C},
{0x3002, 0x00}, /* 输出格式配置 */
{0x3016, 0x08}, /* MIPI配置 */
{0x3018, 0x44},
{0x3020, 0x00},
{0x3021, 0x23},
{0x3022, 0x01},
{0x3820, 0x46}, /* 裁剪与窗口配置 */
{0x3821, 0x00},
{0x3830, 0x00},
{0x3836, 0x00},
{0x3837, 0x00},
{0x0100, 0x01}, /* 进入流模式 */
};
/* 批量写入函数 */
static int sensor_write_array(struct i2c_client *client,
const struct sensor_regval *regs, int size)
{
int i, ret;
for (i = 0; i < size; i++) {
ret = sensor_write_reg(client, regs[i].reg, regs[i].val);
if (ret < 0) {
dev_err(&client->dev, "Failed to write reg 0x%04x\n", regs[i].reg);
return ret;
}
/* 某些寄存器写入后需要延时 */
if (regs[i].reg == 0x0103) /* 软件复位后等待 */
msleep(10);
}
return 0;
}
最佳实践:在RK平台驱动中,建议将寄存器配置表与驱动代码分离,使用DTS(Device Tree)或单独的头文件管理,便于ISP调优阶段快速调整参数。
2.4 V4L2子设备驱动模型
V4L2(Video for Linux 2)是Linux内核中视频设备的标准框架。在RK平台中,Sensor驱动通常实现为V4L2子设备(subdev),通过Media Controller框架与ISP、MIPI DPHY等设备协同工作。
2.4.1 V4L2子设备核心结构
| 结构体/接口 | 作用 |
|---|---|
v4l2_subdev |
子设备核心结构,包含名称、内部ops、控制句柄等 |
v4l2_subdev_ops |
操作函数集,包含core、video、pad、sensor等ops |
v4l2_subdev_video_ops |
视频操作:s_stream(流开关)、g_mbus_config(总线配置)等 |
v4l2_subdev_pad_ops |
Pad操作:get/set_fmt(格式协商)、enum_mbus_code(枚举格式)等 |
v4l2_subdev_sensor_ops |
Sensor专用操作:g_skip_frames(跳帧数)、g_skip_status等 |
media_entity |
媒体实体,用于Media Controller拓扑连接 |
2.4.2 Sensor驱动注册流程
在RK平台中,Sensor驱动的注册通常遵循以下步骤:
- I2C驱动注册:通过
module_i2c_driver()注册I2C驱动,在probe()中完成初始化。 - 分配子设备:使用
v4l2_device_register_subdev()注册V4L2子设备。 - 设置操作函数:填充
v4l2_subdev_ops,实现s_stream、set_fmt等回调。 - 媒体实体初始化:设置
media_entity的Pad数量和类型(源端/接收端)。 - 电源与时钟管理:在
probe()中获取regulator、clk、gpio等资源。 - 初始化Sensor:调用寄存器配置表,完成Sensor初始化。
2.4.3 关键回调函数实现示例
/* sensor_v4l2.c - V4L2子设备回调实现 */
/* s_stream: 开启/关闭视频流 */
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct sensor *sensor = to_sensor(sd);
int ret;
if (enable) {
/* 配置MIPI时序 */
sensor_set_mipi_timing(sensor);
/* 写入流开启寄存器 */
ret = sensor_write_reg(sensor->client, 0x0100, 0x01);
if (ret < 0)
return ret;
sensor->streaming = true;
dev_dbg(sensor->dev, "Stream ON\n");
} else {
/* 写入流关闭寄存器 */
ret = sensor_write_reg(sensor->client, 0x0100, 0x00);
if (ret < 0)
return ret;
sensor->streaming = false;
dev_dbg(sensor->dev, "Stream OFF\n");
}
return 0;
}
/* set_fmt: 设置输出格式 */
static int sensor_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *fmt)
{
struct sensor *sensor = to_sensor(sd);
struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format;
/* 只支持RAW10格式 */
if (mbus_fmt->code != MEDIA_BUS_FMT_SBGGR10_1X10)
mbus_fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;
/* 限制分辨率范围 */
mbus_fmt->width = clamp(mbus_fmt->width, 640U, 1920U);
mbus_fmt->height = clamp(mbus_fmt->height, 480U, 1080U);
/* 更新Sensor内部寄存器 */
sensor_set_resolution(sensor, mbus_fmt->width, mbus_fmt->height);
return 0;
}
/* 子设备操作函数集 */
static const struct v4l2_subdev_video_ops sensor_video_ops = {
.s_stream = sensor_s_stream,
.g_frame_interval = sensor_g_frame_interval,
.s_frame_interval = sensor_s_frame_interval,
};
static const struct v4l2_subdev_pad_ops sensor_pad_ops = {
.enum_mbus_code = sensor_enum_mbus_code,
.get_fmt = sensor_get_fmt,
.set_fmt = sensor_set_fmt,
};
static const struct v4l2_subdev_ops sensor_subdev_ops = {
.video = &sensor_video_ops,
.pad = &sensor_pad_ops,
};
2.4.4 Media Controller拓扑
在RK平台中,完整的Camera数据通路涉及多个子设备:Sensor → MIPI DPHY → ISP → 视频节点。Media Controller框架通过media_create_pad_link()建立这些设备之间的连接。
调试技巧:使用media-ctl工具可以查看和修改Media Controller拓扑,是排查数据通路问题的利器。例如:media-ctl -p打印当前拓扑,media-ctl -l '"sensor":0->"rkisp-isp":0[1]'建立连接。
2.5 本章知识结构图
2.6 本章小结
本章系统介绍了Sensor驱动开发的四大核心基础:
- I2C通信协议:掌握了I2C的物理层、协议层以及读写操作时序,这是配置Sensor的基础。
- MIPI CSI-2接口:理解了CSI-2的D-PHY物理层、数据包结构和虚拟通道概念,这是图像数据传输的通道。
- 寄存器读写操作:通过代码示例学习了单寄存器读写和批量配置的实现方法,这是驱动开发的基本功。
- V4L2子设备驱动模型:掌握了V4L2子设备的核心结构、注册流程和关键回调函数,这是驱动与内核框架对接的标准方式。