2. Sensor驱动基础:I2C/SPI通信协议、Sensor寄存器读写、MIPI CSI-2物理层与协议层解析

在骁龙平台Camera系统中,Sensor驱动是连接硬件与ISP的桥梁。本章将深入剖析Sensor驱动的三大核心基础:I2C/SPI通信协议、Sensor寄存器读写机制以及MIPI CSI-2物理层与协议层。理解这些基础是进行后续ISP调优和驱动开发的前提。

2.1 I2C与SPI通信协议

Camera Sensor通常通过I2C或SPI总线与主控(AP)进行控制指令交互,用于配置寄存器、读取状态等。两者在引脚数、速率、拓扑结构上存在显著差异。

特性 I2C (Inter-Integrated Circuit) SPI (Serial Peripheral Interface)
信号线 SDA (数据线), SCL (时钟线) MOSI, MISO, SCLK, CS (片选)
拓扑 多主多从,总线式 一主多从,主从式
速率 标准模式 100kbps,快速模式 400kbps,高速模式 3.4Mbps 通常 10Mbps ~ 50Mbps,甚至更高
通信方式 半双工,需应答(ACK) 全双工,无应答机制
寻址 7位或10位设备地址 通过CS片选信号选择从设备
适用场景 寄存器配置、状态读取(低速控制) 固件下载、高速数据流控制
实战经验: 在骁龙平台中,绝大多数Camera Sensor的控制接口采用I2C,因其引脚少、布线简单。但部分高性能Sensor(如某些全局快门Sensor)会使用SPI进行固件批量升级,以缩短启动时间。

2.1.1 I2C通信时序与寄存器读写

I2C协议以起始条件(SCL高电平时SDA下降沿)开始,以停止条件(SCL高电平时SDA上升沿)结束。每个字节后跟随一个ACK位。Sensor寄存器读写通常采用复合格式:先发送设备地址+写位,再发送寄存器地址,最后发送数据或重新发起起始条件进行读操作。

// I2C 写寄存器示例 (伪代码)
int i2c_write_reg(struct i2c_client *client, uint16_t reg_addr, uint8_t data) {
    uint8_t buf[3];
    buf[0] = (reg_addr >> 8) & 0xFF;  // 高字节地址 (16位地址)
    buf[1] = reg_addr & 0xFF;         // 低字节地址
    buf[2] = data;                    // 写入数据
    return i2c_master_send(client, buf, 3);
}

// I2C 读寄存器示例 (伪代码)
int i2c_read_reg(struct i2c_client *client, uint16_t reg_addr, uint8_t *data) {
    uint8_t addr_buf[2];
    addr_buf[0] = (reg_addr >> 8) & 0xFF;
    addr_buf[1] = reg_addr & 0xFF;
    // 先发送寄存器地址
    i2c_master_send(client, addr_buf, 2);
    // 再读取数据
    return i2c_master_recv(client, data, 1);
}
关键点: I2C读写时序中,从设备地址通常为7位,左移1位后最低位为R/W位(0表示写,1表示读)。例如设备地址0x6C,写操作为0xD8,读操作为0xD9。

2.1.2 SPI通信时序与寄存器读写

SPI通信通过CS片选信号选中从设备,在SCLK时钟控制下,MOSI和MISO同时传输数据。SPI有四种模式(CPOL和CPHA组合),Sensor通常使用模式0(CPOL=0, CPHA=0)或模式3(CPOL=1, CPHA=1)。

// SPI 写寄存器示例 (伪代码)
int spi_write_reg(struct spi_device *spi, uint16_t reg_addr, uint8_t data) {
    uint8_t tx_buf[3];
    tx_buf[0] = (reg_addr >> 8) & 0xFF;  // 地址高字节
    tx_buf[1] = reg_addr & 0xFF;         // 地址低字节
    tx_buf[2] = data;                    // 数据
    struct spi_transfer t = {
        .tx_buf = tx_buf,
        .len = 3,
    };
    return spi_sync_transfer(spi, &t, 1);
}

// SPI 读寄存器示例 (伪代码)
int spi_read_reg(struct spi_device *spi, uint16_t reg_addr, uint8_t *data) {
    uint8_t tx_buf[3];
    uint8_t rx_buf[3];
    tx_buf[0] = (reg_addr >> 8) | 0x80;  // 读标志位 (最高位置1)
    tx_buf[1] = reg_addr & 0xFF;
    tx_buf[2] = 0x00;                     // 发送任意数据以产生时钟
    struct spi_transfer t = {
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .len = 3,
    };
    spi_sync_transfer(spi, &t, 1);
    *data = rx_buf[2];  // 第三个字节为读取的数据
    return 0;
}
注意: SPI读操作时,通常需要在地址字节中设置读标志位(如最高位置1),且发送地址后需要继续发送哑字节(dummy byte)以产生时钟,从设备会在MISO线上返回数据。

2.2 MIPI CSI-2物理层与协议层解析

MIPI CSI-2是Camera Sensor与AP之间传输图像数据的主流接口标准。它分为物理层(D-PHY/C-PHY)和协议层(CSI-2)。在骁龙平台上,D-PHY是最常见的物理层实现。

MIPI CSI-2 协议栈架构 应用层 (Application Layer) 像素数据打包、帧格式定义、用户自定义数据 协议层 (Protocol Layer) 长包/短包格式、ECC校验、CRC校验、虚拟通道 通道管理层 (Lane Management Layer) 数据分发到多Lane、Lane合并与拆分、重传机制 物理层 (Physical Layer - D-PHY/C-PHY) 差分信号传输、时钟/数据Lane、低功耗与高速模式切换 CSI-2协议栈从上到下:应用层 → 协议层 → 通道管理层 → 物理层

2.2.1 D-PHY物理层详解

D-PHY采用差分信号传输,每个Lane由一对差分线(Dp/Dn)组成。时钟Lane提供DDR(双数据率)时钟,数据Lane在时钟的上升沿和下降沿各传输1位数据。D-PHY支持两种工作模式:

  • 高速模式(HS): 用于传输图像数据,差分摆幅约200mV,速率可达80Mbps ~ 4.5Gbps per Lane。
  • 低功耗模式(LP): 用于控制信号传输(如帧同步、线同步),单端信号,摆幅约1.2V,速率约10Mbps。
参数 HS模式 LP模式
信号类型 差分 (Dp-Dn) 单端 (Dp, Dn独立)
电压摆幅 200mV (典型) 1.2V (典型)
数据传输率 80 Mbps ~ 4.5 Gbps ~10 Mbps
功耗 高 低
用途 图像数据 控制/状态信号

2.2.2 CSI-2协议层数据包格式

CSI-2协议层定义了两种数据包:短包(Short Packet)和长包(Long Packet)。短包用于传输帧起始(FS)、帧结束(FE)、线起始(LS)等同步信息;长包用于传输像素数据。

CSI-2 长包数据格式 数据标识 (DI) 字计数 (WC) ECC (纠错码) 数据负载 (像素数据) CRC (校验码) 8位 16位 8位 可变长度 16位 短包:数据标识(DI) + 数据字段(16位) + ECC(8位),无数据负载和CRC 数据标识(DI)高4位为虚拟通道ID(VC),支持最多4个虚拟通道复用同一物理链路

2.2.3 虚拟通道与数据交织

CSI-2支持最多4个虚拟通道(Virtual Channel, VC),通过数据标识(DI)字节的高4位区分。这使得多个Sensor或同一Sensor的不同数据流(如RAW、YUV、元数据)可以共享同一MIPI总线,实现数据交织传输。

骁龙平台实践: 在多摄像头场景中,常将主摄和广角分别映射到VC0和VC1,通过CSI-2虚拟通道实现单路MIPI接口同时传输两路图像数据,节省硬件引脚资源。

2.3 Sensor寄存器读写实战

在Linux内核Camera驱动中,Sensor寄存器读写通常通过封装好的I2C/SPI读写函数实现。骁龙平台使用V4L2框架,Sensor驱动需要实现s_power、s_stream、enum_mbus_code、s_fmt等回调函数,其中寄存器配置是核心操作。

// 骁龙平台 Sensor 驱动寄存器读写示例 (基于ov5645)
static int ov5645_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, "write reg error: %d\n", ret);
        return ret;
    }

    return 0;
}

static int ov5645_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
    struct i2c_msg msg[2];
    u8 buf[2];
    int ret;

    buf[0] = reg >> 8;
    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 = val;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret < 0) {
        dev_err(&client->dev, "read reg error: %d\n", ret);
        return ret;
    }

    return 0;
}
调试技巧: 在驱动开发初期,建议在读写函数中加入动态打印(dev_dbg),配合i2c-tools工具(i2cget/i2cset)验证寄存器读写是否正确。常见问题包括设备地址错误、寄存器地址位宽不匹配、I2C时钟速率过快导致从设备无响应。

2.4 本章小结