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速度快,适合大批量数据传输。
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();
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]; // 第二个字节是读到的数据
}
3.4 上电时序:Sensor的命门
嗯,这里要重点讲。上电时序搞错了,Sensor要么不工作,要么输出花屏。我见过最惨的一次,因为上电时序差了2ms,整批模组都点不亮,最后发现是DVDD比AVDD晚了那么一点点。
典型的Sensor上电时序包含以下几个阶段:
- AVDD(模拟电源)上电:通常2.8V
- DVDD(数字电源)上电:通常1.2V或1.8V
- IOVDD(IO电源)上电:通常1.8V
- MCLK(主时钟)稳定
- RST(复位)释放
- PWDN(掉电)释放
每个阶段之间都有最小延迟要求,比如AVDD到DVDD至少需要1ms。这些参数,手册里都有,但很多人不看。
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完成初始化
}
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驱动的三大基本功。把这些搞定了,后面的图像配置就是水到渠成的事。