3、Sensor驱动开发:I2C通信协议、Sensor上电时序、寄存器配置、OTP与EEPROM读取

Sensor驱动开发,说白了就是让主控芯片能跟摄像头传感器“说上话”。我刚开始做这行的时候,以为就是写几个读写函数就完事了。结果第一次点亮模组,画面全是花的,折腾了两天才发现是上电时序差了那么几毫秒。嗯,这章咱们就把这些坑一个个填上。

本章核心脉络: I2C通信是“语言”,上电时序是“规矩”,寄存器配置是“调教”,OTP/EEPROM是“身份证”。四者缺一不可。
Sensor驱动开发核心知识体系 Sensor驱动开发 I2C通信协议 上电时序 寄存器配置 OTP/EEPROM SCL/SDA 设备地址 AVDD/DVDD MCLK/XCLK 增益/曝光 帧率/模式 LSC/AWB AF/版本 💡 避坑提示:上电时序不对 → 传感器不工作;I2C地址错 → 读写失败; OTP未正确读取 → 图像偏色/暗角。每一步都是连环坑。

3.1 I2C通信协议:传感器与主控的“对话语言”

I2C是Sensor驱动里最常用的通信接口。说白了就两根线:SCL(时钟)和SDA(数据)。我见过不少新手上来就调I2C,结果示波器一抓,波形乱七八糟。其实只要抓住几个关键点,这玩意儿没那么玄乎。

3.1.1 协议基础

  • 起始条件:SCL高电平时,SDA从高拉低。这是“我要开始说话了”的信号。
  • 设备地址:Sensor一般7位地址,比如0x30。读写位(R/W)拼在最后一位。
  • 应答机制:每发完一个字节,接收方必须拉低SDA表示“收到了”。没应答就是设备没响应。
  • 停止条件:SCL高电平时,SDA从低拉高。表示“我说完了”。
我的习惯:调试I2C时,先用逻辑分析仪抓一次完整的读写波形。确认起始、地址、应答、停止都对了,再往驱动里写代码。省得反复编译烧录,浪费时间。

3.1.2 驱动代码示例

以Linux内核I2C驱动为例,读写Sensor寄存器的核心函数长这样:

/* 读Sensor寄存器 */
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
    struct i2c_msg msg[2];
    u8 buf[2];
    int ret;

    /* 第一步:写寄存器地址 */
    buf[0] = (reg >> 8) & 0xFF;  /* 高字节 */
    buf[1] = reg & 0xFF;         /* 低字节 */
    msg[0].addr = client->addr;
    msg[0].flags = 0;            /* 写 */
    msg[0].len = 2;
    msg[0].buf = buf;

    /* 第二步:读寄存器值 */
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;     /* 读 */
    msg[1].len = 1;
    msg[1].buf = buf;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret == 2) {
        *val = buf[0];
        return 0;
    }
    return -EIO;
}
我曾经踩过的坑:有些Sensor的寄存器地址是16位,有些是8位。我遇到过一款Sensor,datasheet写的是16位地址,结果实际是8位。读出来的值全是0xFF。后来用逻辑分析仪一看,才发现地址发多了。所以,拿到新Sensor先看datasheet的I2C时序图,别想当然。

3.2 上电时序:Sensor的“起床流程”

Sensor上电不是简单地把电加上就行。它有一套严格的“起床流程”:先给谁供电、等多久、再给谁供电。顺序错了或者时间不够,Sensor要么不工作,要么输出异常。

3.2.1 典型上电时序

步骤 动作 典型延时 说明
1 拉高AVDD(模拟电源) ≥1ms 模拟电路先上电
2 拉高DVDD(数字电源) ≥1ms 数字电路后上电
3 拉高IOVDD(IO电源) ≥0.5ms IO口供电
4 提供MCLK(主时钟) ≥1ms 时钟稳定后再操作
5 拉高RST(复位引脚) ≥10ms 释放复位,Sensor开始初始化
6 等待Sensor稳定 ≥20ms 内部PLL锁定、寄存器加载
关键点:上电时序的延时参数,每个Sensor的datasheet里都有明确说明。我建议你在驱动里用usleep_range()或msleep()实现,别用忙等。

3.2.2 驱动中的实现

static int sensor_power_on(struct sensor_device *sdev)
{
    /* 1. AVDD上电 */
    gpio_set_value(sdev->avdd_gpio, 1);
    usleep_range(2000, 3000);  /* 2ms */

    /* 2. DVDD上电 */
    gpio_set_value(sdev->dvdd_gpio, 1);
    usleep_range(2000, 3000);

    /* 3. IOVDD上电 */
    gpio_set_value(sdev->iovdd_gpio, 1);
    usleep_range(1000, 2000);

    /* 4. 提供MCLK */
    clk_prepare_enable(sdev->mclk);
    usleep_range(2000, 3000);

    /* 5. 释放复位 */
    gpio_set_value(sdev->rst_gpio, 1);
    msleep(20);  /* 等待Sensor稳定 */

    return 0;
}
我的经验:上电时序的延时宁多勿少。我曾经为了省那几毫秒,把延时从20ms减到5ms,结果Sensor偶尔能点亮偶尔不行。查了两天才发现是PLL没锁住。后来老老实实按datasheet来,再也没出过问题。

3.3 寄存器配置:Sensor的“调教手册”

Sensor上电后,默认配置通常不是最优的。你需要通过I2C写寄存器来调整增益、曝光、帧率、输出格式等参数。说白了,就是给Sensor一本“调教手册”,告诉它该怎么干活。

3.3.1 常见寄存器分类

寄存器类型 典型地址 作用
芯片ID 0x0000-0x0001 读取Sensor型号,用于校验
模式选择 0x0100 设置流模式/快照模式
增益控制 0x0200-0x0201 模拟增益/数字增益
曝光控制 0x0300-0x0303 行曝光时间
帧率控制 0x0400-0x0401 VTS/HTS调整
输出格式 0x0500 RAW10/RAW12/YUV等

3.3.2 初始化序列示例

static const struct sensor_reg sensor_init_regs[] = {
    /* 软件复位 */
    {0x0103, 0x01},
    {0x0103, 0x00},
    msleep(10),

    /* 设置输出格式为RAW10 */
    {0x0100, 0x00},  /* 先停止流 */
    {0x0340, 0x04},  /* 输出格式寄存器 */
    {0x0341, 0x0A},  /* RAW10 */

    /* 设置帧率30fps */
    {0x0342, 0x05},  /* HTS高字节 */
    {0x0343, 0xDC},  /* HTS低字节 */
    {0x0340, 0x04},  /* VTS高字节 */
    {0x0341, 0x38},  /* VTS低字节 */

    /* 启动流 */
    {0x0100, 0x01},
};
注意:写寄存器时,一定要先停止流(Stream Off),改完配置再重新启动流(Stream On)。我见过有人直接在流模式下改增益,结果画面闪了一下就黑了。因为Sensor内部状态机乱了。

3.4 OTP与EEPROM读取:Sensor的“身份证”

每个Sensor出厂时,都会在内部OTP(一次性可编程)或外挂EEPROM里烧录一些校准数据。比如镜头阴影校正(LSC)、白平衡增益(AWB)、自动对焦起始位置(AF)等。这些数据是Sensor的“身份证”,不读出来直接用,图像质量会大打折扣。

3.4.1 OTP vs EEPROM

特性 OTP EEPROM
存储介质 Sensor内部 外挂芯片
可写次数 1次(出厂已烧录) 多次
读取方式 I2C读特定寄存器 I2C读EEPROM地址
典型用途 LSC、AWB、AF 模组SN、版本号
容量 几KB 几十KB

3.4.2 OTP读取流程

  1. 上电后先读OTP状态寄存器:确认OTP是否已烧录且可读。
  2. 触发OTP读取:写特定寄存器让Sensor把OTP数据加载到内部缓存。
  3. 逐字节读取:通过I2C读缓存寄存器,获取校准数据。
  4. 校验CRC:很多OTP带CRC校验,确保数据没损坏。
  5. 应用校准值:把读到的LSC/AWB等参数写入对应寄存器。
static int sensor_read_otp(struct i2c_client *client)
{
    u8 otp_status;
    u8 lsc_data[256];
    int i, ret;

    /* 1. 检查OTP状态 */
    ret = sensor_read_reg(client, 0x3800, &otp_status);
    if (ret < 0 || !(otp_status & 0x01)) {
        dev_err(&client->dev, "OTP not programmed\n");
        return -EINVAL;
    }

    /* 2. 触发OTP加载 */
    sensor_write_reg(client, 0x3801, 0x01);
    msleep(5);

    /* 3. 读取LSC数据 */
    for (i = 0; i < 256; i++) {
        ret = sensor_read_reg(client, 0x4000 + i, &lsc_data[i]);
        if (ret < 0)
            return ret;
    }

    /* 4. 应用LSC校准 */
    for (i = 0; i < 256; i++) {
        sensor_write_reg(client, 0x5000 + i, lsc_data[i]);
    }

    return 0;
}
我的习惯:OTP读取成功后,我会把数据打印出来跟模组厂提供的校准表对比一下。如果对不上,说明读取流程有问题或者Sensor本身坏了。另外,OTP读取一定要放在上电初始化之后、启动流之前,顺序错了校准值就写不进去了。

3.5 本章小结

Sensor驱动开发,说白了就是跟硬件打交道。I2C通信是基本功,上电时序是门槛,寄存器配置是日常,OTP/EEPROM是细节。我做了这么多年,最大的体会就是:别嫌麻烦,每一步都按datasheet来。你省下的那点时间,最后都会变成调试的坑。

一句话总结:I2C通了、电上对了、寄存器写好了、OTP读出来了,Sensor驱动就稳了八成。剩下两成,是调试和优化。