2. Sensor驱动基础:I2C通信协议、Sensor寄存器读写、驱动初始化流程
Camera Sensor驱动是图像采集链路的第一环,而I2C(Inter-Integrated Circuit)总线则是驱动与Sensor硬件之间最常用的控制通道。本章将深入剖析I2C通信协议在Sensor场景下的具体实现、寄存器读写操作的底层细节,以及驱动从探针到正常工作的完整初始化流程。
2.1 I2C通信协议在Sensor场景中的应用
I2C是一种同步、多主从、串行通信总线,仅需两根线(SCL时钟线、SDA数据线)即可完成设备间的数据交换。在Camera Sensor驱动中,I2C主要用于配置寄存器,例如设置曝光时间、增益、帧率、输出格式等参数。
关键特性: Sensor通常作为I2C从设备,主控端(SoC/AP)作为主设备发起通信。每个Sensor都有一个唯一的7位或10位从设备地址。
2.1.1 I2C总线物理层与协议层
| 层级 | 说明 | Sensor驱动关注点 |
|---|---|---|
| 物理层 | SCL(时钟)、SDA(数据),上拉电阻至VDD | 总线电压(1.8V/3.3V)、速率(100kHz/400kHz/1MHz) |
| 数据链路层 | 起始条件、停止条件、字节传输、应答位 | 时序是否满足Sensor datasheet要求 |
| 应用层 | 寄存器地址+数据组合的读写协议 | 16位/8位寄存器地址、连续读写支持 |
2.2 Sensor寄存器读写操作
Sensor内部寄存器通常以16位地址空间编址,每个寄存器存储8位数据。驱动通过I2C向指定地址写入配置值,或从指定地址读取状态值。
2.2.1 写寄存器流程
- 发送起始条件:主设备在SCL高电平时将SDA从高拉低。
- 发送从设备地址+写位:7位地址左移1位,最低位为0(写操作)。
- 等待ACK:Sensor拉低SDA表示应答。
- 发送寄存器高8位地址:对于16位地址,先发高字节。
- 等待ACK
- 发送寄存器低8位地址
- 等待ACK
- 发送要写入的8位数据
- 等待ACK
- 发送停止条件:SCL高电平时SDA从低拉高。
实战经验: 多数Sensor支持连续写模式,即发送寄存器起始地址后,连续发送多个字节数据,地址自动递增。这在大批量配置寄存器时可显著提升效率。
2.2.2 读寄存器流程
- 发送起始条件
- 发送从设备地址+写位(伪写操作,用于指定寄存器地址)
- 等待ACK
- 发送寄存器高8位地址
- 等待ACK
- 发送寄存器低8位地址
- 等待ACK
- 发送重复起始条件(不发送停止条件,直接再次发送起始条件)
- 发送从设备地址+读位:最低位为1(读操作)
- 等待ACK
- 读取8位数据:主设备释放SDA,Sensor驱动SDA发送数据
- 主设备发送NACK(表示读取结束)
- 发送停止条件
2.2.3 驱动代码示例:I2C读写封装
/* 写Sensor寄存器:16位地址,8位数据 */
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
u8 buf[3] = { (reg >> 8) & 0xFF, reg & 0xFF, val };
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0, /* 写操作 */
.len = 3,
.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;
}
/* 读Sensor寄存器:16位地址,返回8位数据 */
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
int ret;
u8 addr_buf[2] = { (reg >> 8) & 0xFF, reg & 0xFF };
struct i2c_msg msgs[2] = {
{
.addr = client->addr,
.flags = 0, /* 伪写,发送寄存器地址 */
.len = 2,
.buf = addr_buf,
},
{
.addr = client->addr,
.flags = I2C_M_RD, /* 读操作 */
.len = 1,
.buf = val,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0) {
dev_err(&client->dev, "I2C read reg 0x%04x failed: %d\n", reg, ret);
return ret;
}
return 0;
}
常见陷阱: 部分Sensor要求寄存器地址为8位,此时buf长度需调整为2字节(地址+数据)。务必查阅datasheet确认地址宽度。另外,连续读写时需注意I2C控制器FIFO深度,避免溢出。
2.3 驱动初始化流程
Sensor驱动初始化是从设备注册到输出图像数据的关键路径,通常分为平台层和I2C设备层两个阶段。
2.3.1 初始化阶段总览
| 阶段 | 主要操作 | 涉及函数/回调 |
|---|---|---|
| 1. 设备匹配 | I2C设备树节点与驱动匹配,probe被调用 | of_match_table, i2c_device_id |
| 2. 硬件检测 | 读取Sensor CHIP_ID寄存器,验证设备存在 | sensor_detect() |
| 3. 数据结构初始化 | 分配私有数据结构,初始化互斥锁、工作队列 | devm_kzalloc, mutex_init |
| 4. 电源与时钟 | 使能AVDD/DOVDD/DVDD电源,配置MCLK | regulator_bulk_enable, clk_prepare_enable |
| 5. 复位与初始化序列 | 拉低复位引脚,延时后释放;写入初始化寄存器表 | gpiod_set_value, msleep, sensor_write_reg |
| 6. V4L2子设备注册 | 注册到V4L2框架,设置pad、format、ctrl handler | v4l2_i2c_subdev_init, v4l2_ctrl_handler_setup |
| 7. 媒体实体注册 | 注册media entity,建立pipeline连接 | media_entity_pads_init |
2.3.2 初始化序列的典型代码结构
static int sensor_probe(struct i2c_client *client)
{
struct sensor_dev *sensor;
int ret;
/* 1. 设备树匹配检查 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C functionality not supported\n");
return -ENODEV;
}
/* 2. 分配私有数据结构 */
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
i2c_set_clientdata(client, sensor);
/* 3. 硬件检测:读取CHIP_ID */
ret = sensor_detect(client);
if (ret < 0) {
dev_err(&client->dev, "Sensor detect failed\n");
return ret;
}
/* 4. 电源使能 */
ret = sensor_power_on(sensor);
if (ret < 0)
return ret;
/* 5. 复位Sensor */
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(1000, 2000);
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
msleep(20);
/* 6. 写入初始化寄存器序列 */
ret = sensor_init_sequence(client, sensor_default_regs,
ARRAY_SIZE(sensor_default_regs));
if (ret < 0) {
dev_err(&client->dev, "Init sequence failed\n");
return ret;
}
/* 7. 注册V4L2子设备 */
v4l2_i2c_subdev_init(&sensor->sd, client, &sensor_subdev_ops);
sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
/* 8. 初始化控制句柄(曝光、增益、白平衡等) */
ret = sensor_init_controls(sensor);
if (ret < 0)
return ret;
dev_info(&client->dev, "Sensor probe success\n");
return 0;
}
2.3.3 初始化序列的时序依赖
2.4 驱动初始化中的常见问题与调试方法
调试利器: 使用 i2cget/i2cset 命令行工具可以绕过驱动直接读写Sensor寄存器,快速验证硬件连接和寄存器地址是否正确。
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| probe失败,I2C通信无应答 | 设备地址错误、电源未上电、SCL/SDA接反 | 用示波器抓取I2C波形,确认地址和ACK位 |
| CHIP_ID读取为0xFF或0x00 | Sensor未退出复位状态、时钟未稳定 | 检查复位引脚电平,增加复位后延时 |
| 初始化序列写入后无图像输出 | 寄存器配置顺序错误、缺少关键寄存器设置 | 对照datasheet逐条检查初始化序列,确认stream on寄存器 |
| 图像颜色异常 | 白平衡寄存器未配置、增益设置错误 | 读取AWB相关寄存器,确认是否处于手动模式 |
2.5 本章小结
I2C通信协议是Sensor驱动的基石,理解其起始/停止条件、应答机制以及读写时序差异,是正确操作Sensor寄存器的前提。驱动初始化流程需要严格遵循电源-时钟-复位-配置的时序依赖,任何一步的延迟不足或顺序错误都可能导致设备无法正常工作。掌握这些基础,后续的白平衡校正和ISP参数配置才能建立在可靠的硬件控制之上。