3. Sensor驱动开发
Sensor驱动开发,说白了就是让主控芯片能跟摄像头传感器“说上话”。我刚开始做这行的时候,以为就是写几个读写函数就完事了。结果第一次点亮模组,画面全是花的,折腾了两天才发现是上电时序差了那么几毫秒。嗯,这章咱们就把这些坑一个个填上。
本章核心脉络: I2C通信是“语言”,上电时序是“规矩”,寄存器配置是“调教”,OTP/EEPROM是“身份证”。四者缺一不可。
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读取流程
- 上电后先读OTP状态寄存器:确认OTP是否已烧录且可读。
- 触发OTP读取:写特定寄存器让Sensor把OTP数据加载到内部缓存。
- 逐字节读取:通过I2C读缓存寄存器,获取校准数据。
- 校验CRC:很多OTP带CRC校验,确保数据没损坏。
- 应用校准值:把读到的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驱动就稳了八成。剩下两成,是调试和优化。