3. Sensor驱动编写:sensor初始化流程、寄存器读写操作、电源管理序列、时钟配置
Sensor驱动是Camera驱动栈的最底层,直接与硬件传感器芯片交互。本章聚焦于驱动开发中最核心的四个环节:初始化流程编排、寄存器读写抽象、电源上电序列控制以及时钟树配置。掌握这些基础,才能为后续的图像格式转换与NEON优化提供稳定的数据源。
实战前提:本章所有代码示例基于Linux内核V4L2框架,Sensor型号以SONY IMX335为参考。实际开发中请替换为对应Sensor的Datasheet参数。
3.1 Sensor初始化流程
Sensor从上电到输出有效图像数据,需要经历一个严格的硬件状态机转换。典型的初始化流程包含以下阶段:
- 硬件复位:拉低XCLR引脚,保持至少10ms后释放。
- 电源上电:按照AVDD、DVDD、IOVDD的顺序依次供电,每步间隔5ms。
- 时钟输入:提供MCLK(主时钟),通常为24MHz或27MHz。
- I2C通信建立:等待Sensor内部PLL锁定,约需20ms。
- 寄存器初始化序列:写入一组预定义的寄存器配置表。
- 软件触发:设置Streaming寄存器,使Sensor开始输出帧。
关键时序参数(IMX335典型值):
- 复位低电平保持时间:≥10ms
- 电源上升时间:≤100μs
- MCLK稳定到I2C可通信:≥20ms
- 寄存器配置到Streaming输出:≥50ms
3.2 寄存器读写操作
Sensor驱动通过I2C或SPI总线与硬件通信。寄存器读写是驱动中最频繁的操作,必须保证原子性和错误处理。以下是一个标准的内核I2C读写封装:
/* sensor_i2c.c - I2C读写抽象层 */
#include <linux/i2c.h>
#include <linux/delay.h>
#define SENSOR_I2C_RETRY_TIMES 3
#define SENSOR_I2C_WAIT_MS 10
/* 16位地址 + 16位数据 寄存器写入 */
int sensor_write_reg(struct i2c_client *client, u16 reg, u16 val)
{
int ret, i;
u8 buf[4];
buf[0] = (reg >> 8) & 0xFF;
buf[1] = reg & 0xFF;
buf[2] = (val >> 8) & 0xFF;
buf[3] = val & 0xFF;
for (i = 0; i < SENSOR_I2C_RETRY_TIMES; i++) {
ret = i2c_master_send(client, buf, 4);
if (ret == 4)
return 0;
msleep(SENSOR_I2C_WAIT_MS);
}
dev_err(&client->dev, "write reg 0x%04x failed: %d\n", reg, ret);
return -EIO;
}
EXPORT_SYMBOL(sensor_write_reg);
/* 16位地址 + 16位数据 寄存器读取 */
int sensor_read_reg(struct i2c_client *client, u16 reg, u16 *val)
{
int ret, i;
u8 addr_buf[2] = { (reg >> 8) & 0xFF, reg & 0xFF };
u8 data_buf[2];
for (i = 0; i < SENSOR_I2C_RETRY_TIMES; i++) {
ret = i2c_master_send(client, addr_buf, 2);
if (ret != 2)
continue;
ret = i2c_master_recv(client, data_buf, 2);
if (ret == 2) {
*val = (data_buf[0] << 8) | data_buf[1];
return 0;
}
msleep(SENSOR_I2C_WAIT_MS);
}
dev_err(&client->dev, "read reg 0x%04x failed: %d\n", reg, ret);
return -EIO;
}
EXPORT_SYMBOL(sensor_read_reg);
| 接口 | 参数 | 重试机制 | 返回值 |
|---|---|---|---|
| sensor_write_reg | client, reg, val | 3次重试,间隔10ms | 0成功,-EIO失败 |
| sensor_read_reg | client, reg, *val | 3次重试,间隔10ms | 0成功,-EIO失败 |
| sensor_write_array | client, table, size | 逐条写入,失败即返回 | 0成功,负值错误码 |
避坑指南:
- 部分Sensor使用8位寄存器地址,需根据Datasheet调整buf长度。
- I2C通信失败后必须重试,不能直接返回,否则可能导致初始化卡死。
- 批量写入时建议使用I2C多字节写模式,减少总线开销。
3.3 电源管理序列
Sensor对电源时序极其敏感,错误的供电顺序可能导致芯片损坏或无法正常工作。标准的三路电源管理序列如下:
在内核驱动中,电源管理通常通过regulator框架实现:
/* sensor_power.c - 电源管理序列实现 */
#include <linux/regulator/consumer.h>
struct sensor_power {
struct regulator *avdd;
struct regulator *dvdd;
struct regulator *iovdd;
};
int sensor_power_on(struct sensor_power *pw)
{
int ret;
/* 1. AVDD先上电 */
ret = regulator_enable(pw->avdd);
if (ret) return ret;
msleep(5);
/* 2. DVDD */
ret = regulator_enable(pw->dvdd);
if (ret) goto err_dvdd;
msleep(5);
/* 3. IOVDD最后 */
ret = regulator_enable(pw->iovdd);
if (ret) goto err_iovdd;
msleep(5);
return 0;
err_iovdd:
regulator_disable(pw->dvdd);
err_dvdd:
regulator_disable(pw->avdd);
return ret;
}
void sensor_power_off(struct sensor_power *pw)
{
regulator_disable(pw->iovdd);
msleep(5);
regulator_disable(pw->dvdd);
msleep(5);
regulator_disable(pw->avdd);
}
3.4 时钟配置
Sensor需要外部主时钟(MCLK)驱动内部PLL,生成像素时钟、行时钟和帧时钟。时钟配置错误会导致输出图像花屏或无数据。典型的时钟树结构如下:
时钟配置的核心是PLL参数计算。以下是一个典型的PLL配置函数:
/* sensor_clk.c - PLL时钟配置 */
#define IMX335_MCLK 24000000 /* 24MHz */
#define IMX335_PCLK_TARGET 72000000 /* 目标像素时钟 72MHz */
struct pll_config {
u16 pre_pll_div; /* 预分频 */
u16 pll_mult; /* 倍频系数 */
u16 post_div; /* 后分频 */
};
int sensor_calc_pll(struct pll_config *cfg, u32 mclk, u32 target_pclk)
{
u32 vco_freq;
u32 best_diff = 0xFFFFFFFF;
u16 best_mult = 0, best_post = 0;
u16 m, p;
/* 遍历倍频系数 100~300,后分频 1~8 */
for (m = 100; m <= 300; m++) {
for (p = 1; p <= 8; p++) {
vco_freq = mclk * m / 2; /* 预分频固定为2 */
if (vco_freq < 400000000 || vco_freq > 800000000)
continue;
u32 pclk = vco_freq / p;
u32 diff = abs((s32)(pclk - target_pclk));
if (diff < best_diff) {
best_diff = diff;
best_mult = m;
best_post = p;
}
}
}
if (best_diff > 1000000) {
pr_err("sensor: cannot find valid PLL config\n");
return -EINVAL;
}
cfg->pre_pll_div = 2;
cfg->pll_mult = best_mult;
cfg->post_div = best_post;
pr_info("sensor: PLL config: pre=%d, mult=%d, post=%d, pclk=%u\n",
cfg->pre_pll_div, cfg->pll_mult, cfg->post_div,
mclk * best_mult / 2 / best_post);
return 0;
}
| Sensor型号 | MCLK (MHz) | PCLK (MHz) | 帧率 (fps) |
|---|---|---|---|
| IMX335 | 24 | 72 | 30 |
| OV5640 | 24 | 84 | 30 |
| AR0230 | 27 | 74.25 | 60 |
| IMX290 | 37.125 | 74.25 | 60 |
调试技巧:如果Sensor输出图像异常,首先检查PCLK频率是否与预期一致。可以使用示波器测量Sensor的PCLK引脚,或通过读取Sensor内部PLL锁定状态寄存器确认。
3.5 完整初始化序列示例
将以上模块整合,形成完整的Sensor初始化函数:
/* sensor_init.c - 完整初始化序列 */
int sensor_init(struct i2c_client *client)
{
struct sensor_power pw;
struct pll_config pll;
int ret;
/* 1. 获取regulator句柄 */
pw.avdd = regulator_get(&client->dev, "avdd");
pw.dvdd = regulator_get(&client->dev, "dvdd");
pw.iovdd = regulator_get(&client->dev, "iovdd");
/* 2. 电源上电 */
ret = sensor_power_on(&pw);
if (ret) goto err_power;
/* 3. 硬件复位 */
gpio_set_value(client->irq, 0);
msleep(20);
gpio_set_value(client->irq, 1);
msleep(20);
/* 4. 配置PLL */
ret = sensor_calc_pll(&pll, IMX335_MCLK, IMX335_PCLK_TARGET);
if (ret) goto err_pll;
/* 5. 写入PLL寄存器 */
sensor_write_reg(client, 0x3000, pll.pre_pll_div);
sensor_write_reg(client, 0x3002, pll.pll_mult);
sensor_write_reg(client, 0x3004, pll.post_div);
msleep(20); /* 等待PLL锁定 */
/* 6. 写入初始化寄存器表 */
ret = sensor_write_array(client, imx335_init_regs,
ARRAY_SIZE(imx335_init_regs));
if (ret) goto err_regs;
/* 7. 启动Streaming */
sensor_write_reg(client, 0x0100, 0x01);
dev_info(&client->dev, "sensor initialized successfully\n");
return 0;
err_regs:
err_pll:
sensor_power_off(&pw);
err_power:
regulator_put(pw.avdd);
regulator_put(pw.dvdd);
regulator_put(pw.iovdd);
return ret;
}
初始化序列检查清单:
- ✅ 电源顺序:AVDD → DVDD → IOVDD,间隔≥5ms
- ✅ 复位时序:低电平保持≥10ms,释放后等待≥20ms
- ✅ PLL锁定等待:≥20ms
- ✅ 寄存器写入:逐条确认无I2C错误
- ✅ Streaming使能:最后一步,使能后Sensor立即输出帧