3、Sensor驱动基础:I2C/SPI通信协议、Sensor寄存器读写、上电时序

好,咱们直接进入正题。Sensor驱动开发,说白了就是两件事:让Sensor动起来把数据读出来。而动起来的第一步,就是搞定通信协议和上电时序。这一块要是踩坑了,后面调图像质量全是白费功夫。

3.1 I2C与SPI:你该选哪个?

我刚开始做驱动时,也纠结过这个问题。其实选择很简单,看你的场景。

特性 I2C SPI
引脚数 2根(SCL, SDA) 4根(SCLK, MOSI, MISO, CS)
速度 标准100kHz~3.4MHz 可达几十MHz
通信方式 半双工,带应答 全双工,无应答
典型应用 寄存器配置、低速率场景 图像数据输出、高速配置

我个人习惯是:配置寄存器用I2C,传图像数据用SPI。为什么?因为Sensor的寄存器配置量不大,I2C的应答机制能帮你确认每一笔写入是否成功。而SPI速度快,适合大批量数据传输。

小提示: 有些Sensor支持同时使用I2C和SPI。我遇到过一款Sensor,I2C只用来配置,SPI用来读RAW数据。这种设计很聪明,各取所长。

3.2 I2C读写:别被ACK/NACK坑了

I2C的读写流程,教科书上都有。但实际调试时,最容易出问题的地方是从机地址和应答信号

举个例子,Sensor的I2C地址通常是7位,比如0x30。但你在发送时,需要左移一位,最后一位是读写位。所以写地址是0x60,读地址是0x61。这个细节,我见过不止一个新手在这里卡住。

// I2C写寄存器示例(伪代码)
uint8_t addr = 0x30 << 1;  // 左移一位,得到写地址0x60
uint8_t reg = 0x03;         // 目标寄存器地址
uint8_t val = 0xAB;         // 要写入的值

i2c_start();
i2c_send_byte(addr);        // 发送写地址
if (!i2c_get_ack()) {
    // 没有ACK!Sensor没响应
    printf("NACK received, check address or power\n");
    i2c_stop();
    return -1;
}
i2c_send_byte(reg);         // 发送寄存器地址
i2c_send_byte(val);         // 发送数据
i2c_stop();
注意: 我曾经遇到一个Sensor,它的I2C地址是8位的,不是7位。手册上写的是0x60,结果我左移一位变成0xC0,怎么都调不通。后来才发现,这个Sensor直接使用8位地址,不需要左移。所以,一定要看手册的时序图,别想当然。

3.3 SPI读写:时钟极性和相位别搞反

SPI的四种模式,由CPOL(时钟极性)和CPHA(时钟相位)决定。很多驱动问题,根源就是这里配错了。

  • CPOL=0:空闲时时钟为低电平
  • CPOL=1:空闲时时钟为高电平
  • CPHA=0:在第一个时钟沿采样数据
  • CPHA=1:在第二个时钟沿采样数据

我个人的经验是:大部分Camera Sensor使用Mode 0(CPOL=0, CPHA=0)或Mode 3(CPOL=1, CPHA=1)。为什么?因为这两种模式都是在时钟上升沿采样,下降沿输出,时序比较规整。

但别信我的经验,信手册。每个Sensor的datasheet里都会明确写出SPI Mode。你想想看,如果配错了,读回来的数据全是乱的,你调一整天都找不到原因。

// SPI读寄存器示例(伪代码)
uint8_t spi_read_reg(uint8_t reg) {
    uint8_t tx_buf[2];
    uint8_t rx_buf[2];
    
    // 第一个字节:读命令(0x80) + 寄存器地址
    tx_buf[0] = 0x80 | reg;  // 假设读命令最高位为1
    tx_buf[1] = 0x00;        // 随便发,为了接收
    
    cs_low();
    spi_transfer(tx_buf, rx_buf, 2);
    cs_high();
    
    return rx_buf[1];  // 第二个字节是读到的数据
}
核心要点: SPI是全双工,你发一个字节的同时,会收到一个字节。所以读操作时,第一个字节发出去,收到的数据是无效的;第二个字节发出去,收到的才是真正的寄存器值。这个逻辑,很多初学者会搞混。

3.4 上电时序:Sensor的命门

嗯,这里要重点讲。上电时序搞错了,Sensor要么不工作,要么输出花屏。我见过最惨的一次,因为上电时序差了2ms,整批模组都点不亮,最后发现是DVDD比AVDD晚了那么一点点。

典型的Sensor上电时序包含以下几个阶段:

  1. AVDD(模拟电源)上电:通常2.8V
  2. DVDD(数字电源)上电:通常1.2V或1.8V
  3. IOVDD(IO电源)上电:通常1.8V
  4. MCLK(主时钟)稳定
  5. RST(复位)释放
  6. PWDN(掉电)释放

每个阶段之间都有最小延迟要求,比如AVDD到DVDD至少需要1ms。这些参数,手册里都有,但很多人不看。

避坑指南: 我曾经遇到一个Sensor,手册上写的是“AVDD先上,DVDD后上”,但实际测试发现,如果DVDD先上,Sensor也能工作,只是功耗会高一点。后来跟FAE确认才知道,这个Sensor内部有LDO,DVDD先上会导致LDO反向偏置,长期使用会损坏芯片。所以,严格按照手册来,别自作聪明

3.5 实战:如何验证上电时序是否正确?

光看代码不行,得用示波器抓波形。我一般会抓三个信号:AVDD、DVDD、RST

步骤很简单:

  • 把示波器探头分别接到这三个测试点上
  • 设置触发条件为AVDD上升沿
  • 给系统上电,抓取波形
  • 测量各信号之间的延迟,跟手册对比

如果发现时序不对,别急着改硬件。先看看代码里是不是有sleep()函数用错了。比如,有的驱动里用mdelay(1),但实际系统里可能因为调度延迟,变成了0.5ms。这时候,用usleep_range()会更靠谱。

// 上电时序代码示例
void sensor_power_on() {
    // 1. AVDD上电
    gpio_set_value(AVDD_EN, 1);
    usleep_range(2000, 3000);  // 等待2ms
    
    // 2. DVDD上电
    gpio_set_value(DVDD_EN, 1);
    usleep_range(1000, 1500);  // 等待1ms
    
    // 3. IOVDD上电
    gpio_set_value(IOVDD_EN, 1);
    usleep_range(1000, 1500);  // 等待1ms
    
    // 4. 提供MCLK
    clk_enable(sensor_mclk);
    usleep_range(1000, 2000);  // 等待时钟稳定
    
    // 5. 释放复位
    gpio_set_value(SENSOR_RST, 1);
    usleep_range(5000, 6000);  // 等待5ms
    
    // 6. 释放掉电
    gpio_set_value(SENSOR_PWDN, 0);
    usleep_range(10000, 12000); // 等待10ms,让Sensor完成初始化
}
小技巧: 我习惯在usleep_range()里给一个范围,而不是固定值。比如usleep_range(2000, 3000),这样既能满足最小延迟要求,又不会因为系统调度导致时序紧张。说白了,就是给自己留点余量。

3.6 寄存器读写:验证Sensor是否活着

上电完成后,第一件事就是验证I2C/SPI通信是否正常。我一般会读Sensor的芯片ID寄存器(CHIP_ID)。这个寄存器是只读的,值固定,比如0x58。如果能读到这个值,说明通信没问题。

// 验证Sensor通信
uint8_t chip_id = read_reg(0x0000);  // 假设芯片ID寄存器地址是0x0000
if (chip_id == 0x58) {
    printf("Sensor detected, chip ID: 0x%02X\n", chip_id);
} else {
    printf("Sensor not responding, read ID: 0x%02X\n", chip_id);
    // 这里需要排查:上电时序?I2C地址?时钟?
}

如果读不到正确的ID,别慌。按这个顺序排查:

  • 用示波器看I2C/SPI波形,有没有数据在跑?
  • 检查上电时序,各电源是否稳定?
  • 检查复位和掉电引脚的电平是否正确?
  • 检查I2C地址是否匹配(包括左移问题)?

我遇到过最奇葩的一次,是Sensor的I2C地址跟另一个设备冲突了。两个设备地址一样,导致通信时好时坏。后来换了一个I2C总线才解决。所以,多设备共用一个I2C总线时,一定要确认地址不冲突

好了,这一章的内容就这些。I2C/SPI通信、上电时序、寄存器读写,是Sensor驱动的三大基本功。把这些搞定了,后面的图像配置就是水到渠成的事。