3. Sensor驱动开发
Sensor驱动,说白了就是让摄像头芯片“活过来”的那层代码。很多刚入行的朋友觉得这模块简单——不就是配几个寄存器嘛?嗯,我当年也这么想,直到第一次调不通图像,拿着示波器抓了一整天波形才发现是上电时序差了2毫秒。
这一章,我把这些年踩过的坑和总结的方法论都摊开来讲。从硬件时序到软件流控,咱们一步步来。
3.1 Sensor上电时序——芯片的“生命开关”
每个Sensor芯片都有自己的“起床流程”。你想想看,它内部有模拟电路、数字电路、PLL、MIPI PHY……这些模块不能同时上电,否则轻则锁死,重则烧毁。
3.1.1 典型上电时序要求
我见过最标准的时序图长这样:
| 信号 | 动作 | 时序要求 | 典型值 |
|---|---|---|---|
| AVDD (模拟电源) | 先上电 | 稳定后等待T1 | 1ms |
| DVDD (数字电源) | 第二步 | AVDD稳定后T2 | 0.5ms |
| IOVDD (IO电源) | 第三步 | DVDD稳定后T3 | 0.5ms |
| MCLK (主时钟) | 提供时钟 | 电源稳定后T4 | 1ms |
| RESET (复位) | 释放复位 | 时钟稳定后T5 | 2ms |
| I2C/SPI | 开始通信 | 复位释放后T6 | 5ms |
3.1.2 代码实现要点
上电时序在驱动里通常用GPIO控制LDO使能脚来实现。我个人习惯把时序参数定义成宏,方便调试时微调:
/* 上电时序参数(单位:us) */
#define SENSOR_POWERON_T1_AVDD2DVDD 1000
#define SENSOR_POWERON_T2_DVDD2IOVDD 500
#define SENSOR_POWERON_T3_IOVDD2MCLK 500
#define SENSOR_POWERON_T4_MCLK2RESET 1000
#define SENSOR_POWERON_T5_RESET2I2C 2000
static int sensor_power_on(struct device *dev)
{
/* 1. AVDD上电 */
gpiod_set_value(avdd_gpio, 1);
usleep_range(SENSOR_POWERON_T1_AVDD2DVDD,
SENSOR_POWERON_T1_AVDD2DVDD + 100);
/* 2. DVDD上电 */
gpiod_set_value(dvdd_gpio, 1);
usleep_range(SENSOR_POWERON_T2_DVDD2IOVDD,
SENSOR_POWERON_T2_DVDD2IOVDD + 100);
/* 3. IOVDD上电 */
gpiod_set_value(iovdd_gpio, 1);
usleep_range(SENSOR_POWERON_T3_IOVDD2MCLK,
SENSOR_POWERON_T3_IOVDD2MCLK + 100);
/* 4. 提供MCLK */
clk_prepare_enable(sensor_clk);
usleep_range(SENSOR_POWERON_T4_MCLK2RESET,
SENSOR_POWERON_T4_MCLK2RESET + 100);
/* 5. 释放复位 */
gpiod_set_value(reset_gpio, 1);
msleep(SENSOR_POWERON_T5_RESET2I2C / 1000);
return 0;
}
3.2 I2C/SPI寄存器读写——与Sensor对话的“语言”
Sensor内部有几百个寄存器,控制着曝光、增益、帧率、输出格式等等。我们要通过I2C或SPI总线去读写它们。
3.2.1 I2C vs SPI 怎么选?
| 特性 | I2C | SPI |
|---|---|---|
| 引脚数 | 2线(SDA+SCL) | 4线(MOSI+MISO+SCLK+CS) |
| 速度 | 标准400kHz,快模式1MHz | 通常10MHz~50MHz |
| 寻址方式 | 7位/10位设备地址 | 片选信号CS选择设备 |
| 适用场景 | 配置寄存器(低速) | 配置寄存器+少量数据读取 |
| 驱动复杂度 | 较低(内核有通用框架) | 中等(需处理CS时序) |
我个人习惯:配置类操作用I2C就够了,除非Sensor要求SPI。你想想看,配置寄存器又不是传图像,400kHz完全够用。
3.2.2 I2C读写实战
Linux内核里I2C读写有标准接口。我封装了一个通用函数,支持16位和8位寄存器地址:
/* 写Sensor寄存器(16位地址 + 8位数据) */
static int sensor_write_reg(struct i2c_client *client,
u16 reg_addr, u8 val)
{
struct i2c_msg msg;
u8 buf[3];
int ret;
buf[0] = (reg_addr >> 8) & 0xFF; /* 高8位地址 */
buf[1] = reg_addr & 0xFF; /* 低8位地址 */
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 != 1) {
dev_err(&client->dev,
"I2C write failed: reg=0x%04x, val=0x%02x\n",
reg_addr, val);
return -EIO;
}
return 0;
}
/* 读Sensor寄存器 */
static int sensor_read_reg(struct i2c_client *client,
u16 reg_addr, u8 *val)
{
struct i2c_msg msg[2];
u8 addr_buf[2];
int ret;
addr_buf[0] = (reg_addr >> 8) & 0xFF;
addr_buf[1] = reg_addr & 0xFF;
/* 先写地址 */
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = addr_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 != 2) {
dev_err(&client->dev,
"I2C read failed: reg=0x%04x\n", reg_addr);
return -EIO;
}
return 0;
}
3.3 Sensor寄存器配置实战——让Sensor“说出”你要的画面
寄存器配置是Sensor驱动的核心。说白了,就是告诉Sensor:你要输出多大分辨率、多少帧率、什么格式的数据。
3.3.1 常用寄存器分类
| 类别 | 典型寄存器 | 作用 |
|---|---|---|
| 芯片ID | CHIP_ID_H, CHIP_ID_L | 读取验证Sensor型号 |
| 模式选择 | MODE_SELECT | 软复位、待机、流模式切换 |
| 分辨率 | WIDTH_H, WIDTH_L, HEIGHT_H, HEIGHT_L | 设置输出图像尺寸 |
| 帧率 | VTS_H, VTS_L, HTS_H, HTS_L | 通过VTS/HTS调节帧率 |
| 增益/曝光 | GAIN, AEC_EXPO | 控制亮度和曝光时间 |
| 输出格式 | FORMAT_SELECT | RAW10/RAW12/YUV等 |
| MIPI配置 | MIPI_LANE_NUM, MIPI_CLK | 配置MIPI通道数和时钟 |
3.3.2 初始化序列实战
每个Sensor厂商都会提供一份初始化寄存器表。我习惯把它组织成结构体数组:
/* 寄存器配置项 */
struct sensor_regval {
u16 reg;
u8 val;
};
/* 1080P@30fps 初始化序列 */
static const struct sensor_regval sensor_init_1080p30[] = {
/* 软复位 */
{0x0103, 0x01},
{0x0100, 0x00},
msleep(10),
/* 设置分辨率 1920x1080 */
{0x3808, 0x07}, /* width high */
{0x3809, 0x80}, /* width low (1920) */
{0x380A, 0x04}, /* height high */
{0x380B, 0x38}, /* height low (1080) */
/* 设置帧率 30fps */
{0x380C, 0x0B}, /* HTS high */
{0x380D, 0xA0}, /* HTS low (2976) */
{0x380E, 0x04}, /* VTS high */
{0x380F, 0x60}, /* VTS low (1120) */
/* 输出格式 RAW10 */
{0x4300, 0x30}, /* format select */
/* MIPI 4-lane, 800Mbps/lane */
{0x4800, 0x24}, /* lane num = 4 */
{0x4801, 0x0F}, /* clk config */
/* 其他模拟配置(省略) */
/* ... */
/* 结束标记 */
{0xFFFF, 0xFF},
};
static int sensor_load_init_seq(struct i2c_client *client)
{
const struct sensor_regval *p = sensor_init_1080p30;
int ret;
while (p->reg != 0xFFFF) {
if (p->reg == 0x0000) {
/* 延时指令 */
msleep(p->val);
} else {
ret = sensor_write_reg(client, p->reg, p->val);
if (ret)
return ret;
}
p++;
}
return 0;
}
3.4 Sensor流控(Stream On/Off)——让数据流“听话”
流控是Sensor驱动的“开关”。Stream On让Sensor开始输出图像数据,Stream Off让它停止。看似简单,但处理不好会导致MIPI时钟紊乱、帧中断丢失等问题。
3.4.1 Stream On/Off 标准流程
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct sensor_device *sensor = to_sensor(sd);
int ret;
if (enable) {
/* Stream On */
/* 1. 确保电源和时钟已稳定 */
/* 2. 加载初始化序列 */
ret = sensor_load_init_seq(sensor->client);
if (ret)
return ret;
/* 3. 设置输出模式 */
ret = sensor_write_reg(sensor->client, 0x0100, 0x01);
if (ret)
return ret;
/* 4. 等待MIPI时钟锁定 */
msleep(100);
/* 5. 使能MIPI接收端 */
/* ... */
sensor->streaming = true;
} else {
/* Stream Off */
/* 1. 先关MIPI接收端 */
/* ... */
/* 2. 写寄存器停止输出 */
ret = sensor_write_reg(sensor->client, 0x0100, 0x00);
if (ret)
return ret;
/* 3. 等待Sensor进入待机 */
msleep(50);
/* 4. 可选:关闭电源 */
/* ... */
sensor->streaming = false;
}
return 0;
}
3.4.2 流控中的关键时序
我遇到过最头疼的问题:Stream On后MIPI时钟不稳定,导致接收端丢帧。后来发现是Sensor输出模式和MIPI接收端配置不匹配。
3.4.3 流控状态机
为了更直观地理解流控流程,我画了一张状态图:
3.5 本章小结
Sensor驱动开发,说白了就是三件事:
- 上电时序——严格按照datasheet的时序要求,差1ms都可能出问题
- 寄存器读写——I2C/SPI接口要稳定可靠,做好错误处理
- 流控管理——Stream On/Off的时序和状态机要严谨,避免MIPI链路异常
我个人觉得,Sensor驱动是Camera驱动里最“实在”的部分。没有那么多花哨的算法,就是老老实实配好每一个寄存器,控制好每一个时序。但恰恰是这些“实在”的活,最容易出问题。