3. Sensor驱动开发基础:I2C/SPI通信协议、Sensor寄存器配置、上电时序与初始化流程
驱动Sensor,说白了就是两件事:让它正常启动,然后告诉它怎么干活。
启动靠上电时序,干活靠寄存器配置。而这两件事的桥梁,就是I2C或SPI总线。
这一节,我们就把这三块东西串起来讲。我会结合自己调过的一些Sensor型号,把那些容易踩的坑提前指出来。
3.1 通信协议:I2C vs SPI,怎么选?
工业相机里,Sensor和控制端(FPGA/SoC/MCU)之间,绝大多数用I2C或SPI来读写寄存器。
我个人习惯,分辨率高、帧率高、配置寄存器多的Sensor,优先考虑SPI。为什么?因为快。
| 对比项 | I2C | SPI |
|---|---|---|
| 总线结构 | 双线(SCL + SDA),半双工 | 四线(SCLK + MOSI + MISO + CS),全双工 |
| 速率 | 标准100kHz / 快速400kHz / 高速3.4MHz | 通常10MHz~50MHz,甚至更高 |
| 从设备寻址 | 7位或10位地址,总线仲裁 | 片选信号CS,无地址冲突 |
| 硬件复杂度 | 简单,只需两个上拉电阻 | 稍复杂,每增加一个设备多一根CS线 |
| 适用场景 | 配置参数少、速率要求不高的Sensor | 高分辨率、高帧率、大量寄存器配置 |
3.2 I2C读写时序与驱动实现
I2C的协议其实不复杂。核心就几个状态:起始条件、从设备地址+读写位、应答、数据字节、停止条件。
写一个寄存器的典型流程:
- 主机发送起始条件(START)
- 主机发送7位从设备地址 + 写位(0)
- 从设备应答(ACK)
- 主机发送寄存器地址高字节(如果有16位地址)
- 从设备应答
- 主机发送寄存器地址低字节
- 从设备应答
- 主机发送要写入的数据
- 从设备应答
- 主机发送停止条件(STOP)
读寄存器稍微麻烦一点,需要先写寄存器地址,再重新发起起始条件,然后读数据。这叫“复合格式”。
// 伪代码:I2C写一个16位地址的寄存器
uint8_t i2c_write_reg(uint8_t dev_addr, uint16_t reg_addr, uint8_t data) {
i2c_start(); // 起始条件
i2c_send_byte(dev_addr << 1 | 0); // 设备地址 + 写
if (!i2c_wait_ack()) return -1;
i2c_send_byte(reg_addr >> 8); // 寄存器高8位
if (!i2c_wait_ack()) return -1;
i2c_send_byte(reg_addr & 0xFF); // 寄存器低8位
if (!i2c_wait_ack()) return -1;
i2c_send_byte(data); // 写入数据
if (!i2c_wait_ack()) return -1;
i2c_stop(); // 停止条件
return 0;
}
3.3 SPI读写时序与驱动实现
SPI比I2C简单直接。没有地址仲裁,没有应答位。主机拉低CS,然后按时钟沿发送或接收数据。
Sensor的SPI通常支持两种模式:
- Mode 0: CPOL=0, CPHA=0,时钟空闲为低,第一个边沿采样
- Mode 3: CPOL=1, CPHA=1,时钟空闲为高,第一个边沿采样
大部分工业Sensor默认用Mode 0。但一定要看手册确认,我见过一款Sensor手册写Mode 0,实际必须用Mode 3才能稳定通信。
// 伪代码:SPI写寄存器(16位地址)
uint8_t spi_write_reg(uint8_t cs_pin, uint16_t reg_addr, uint8_t data) {
uint8_t tx_buf[4];
tx_buf[0] = 0x00; // 写命令(通常bit7=0表示写)
tx_buf[1] = reg_addr >> 8; // 地址高8位
tx_buf[2] = reg_addr & 0xFF; // 地址低8位
tx_buf[3] = data; // 数据
gpio_set(cs_pin, 0); // 拉低片选
spi_transfer(tx_buf, NULL, 4); // 发送4个字节
gpio_set(cs_pin, 1); // 拉高片选
return 0;
}
3.4 上电时序:Sensor的“生命第一口奶”
Sensor不是一通电就能工作的。它需要严格的上电顺序。搞错了,轻则初始化失败,重则烧坏Sensor。
典型的Sensor上电时序包含三路电源:
- 模拟电源(AVDD): 通常2.8V或3.3V,给像素阵列和模拟电路供电
- 数字电源(DVDD): 通常1.2V或1.8V,给数字逻辑供电
- IO电源(IOVDD): 通常1.8V或3.3V,给I2C/SPI接口供电
上电顺序一般要求:AVDD先上,然后DVDD,最后IOVDD。但不同Sensor要求不同,有的要求DVDD先上,有的要求三者同时上。
| 电源轨 | 典型电压 | 上电顺序 | 纹波要求 |
|---|---|---|---|
| AVDD | 2.8V / 3.3V | 第1位 | < 50mV |
| DVDD | 1.2V / 1.8V | 第2位 | < 30mV |
| IOVDD | 1.8V / 3.3V | 第3位 | < 50mV |
3.5 初始化流程:从复位到出图
上电完成后,Sensor还需要一套完整的初始化流程才能正常输出图像。我把它总结为六步法:
- 硬件复位: 拉低复位引脚(通常叫XCLR或RST),保持至少1ms,然后拉高
- 时钟稳定: 提供主时钟(MCLK),等待PLL锁定,通常需要5~10ms
- 读取芯片ID: 通过I2C/SPI读取Sensor的ID寄存器,验证通信正常
- 加载初始配置: 写入一组基础寄存器,包括输出格式、分辨率、帧率、增益等
- 启动输出: 写入启动寄存器(通常叫“Stream On”或“Start”),Sensor开始输出图像数据
- 等待第一帧稳定: 丢弃前几帧(通常2~5帧),等AGC/AEC收敛后再取图
// 伪代码:Sensor初始化流程
int sensor_init() {
// 1. 硬件复位
gpio_set(RST_PIN, 0);
delay_ms(2);
gpio_set(RST_PIN, 1);
delay_ms(10); // 等待内部振荡器稳定
// 2. 验证通信
uint8_t chip_id = i2c_read_reg(DEV_ADDR, REG_CHIP_ID);
if (chip_id != EXPECTED_ID) {
return -1; // 通信失败
}
// 3. 加载配置表
for (int i = 0; i < config_table_len; i++) {
i2c_write_reg(DEV_ADDR, config_table[i].addr, config_table[i].data);
}
// 4. 启动输出
i2c_write_reg(DEV_ADDR, REG_STREAM, 0x01);
// 5. 等待稳定
delay_ms(100);
return 0;
}
3.6 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以把它当作Sensor驱动开发的“地图”。
3.7 避坑指南
最后,分享几个我实际项目中踩过的坑,希望能帮你省点时间:
- 上电时序别偷懒: 即使手册说“可以同时上”,我也建议加延时。电源芯片的启动时间有离散性,批量生产时容易出问题。
- 读ID一定要做: 这是验证I2C/SPI通信是否正常的“金标准”。如果ID读不对,别往下走,先查硬件连接和电平匹配。
- 配置表要加校验: 我习惯在配置表末尾加一个“校验和”寄存器,写入后读回来验证。有些Sensor的寄存器写入后不会立即生效,需要额外触发。
- 时钟频率别跑太高: I2C的400kHz在长线缆(超过10cm)时容易出问题。工业相机里,我一般把I2C降到100kHz,SPI降到10MHz以内,保证稳定性。
- 复位后要等足够久: 有些Sensor的PLL锁定时间长达20ms。别急着在复位后马上读写寄存器,等一等,用示波器确认时钟稳定了再操作。
嗯,Sensor驱动开发,说白了就是把时序搞对,把协议搞通,把寄存器写对。这三件事做好了,Sensor就能乖乖听话出图了。