CMOS图像传感器驱动程序原理
·
一、CMOS传感器基础原理
1.1 工作原理
CMOS(互补金属氧化物半导体)图像传感器通过光电转换将光信号转换为电信号。每个像素单元包含:
- 光电二极管: 捕获光子并产生电荷
- 传输晶体管: 将电荷传输到浮置扩散节点
- 复位晶体管: 重置像素电压
- 源跟随器: 放大信号
- 行选择晶体管: 控制像素读出
1.2 接口类型
常见的CMOS传感器接口包括:
- 并行接口: PCLK、HREF、VSYNC、D[7:0]
- MIPI CSI-2: 高速串行接口(主流)
- DVP: 数字视频端口
- I2C/SCCB: 配置接口
二、驱动程序架构
2.1 Linux V4L2框架
Video4Linux2 (V4L2) 是Linux内核中的视频设备驱动框架:
用户空间应用程序
↓
V4L2 API (ioctl)
↓
V4L2 Core
↓
V4L2 Subdev / Media Controller
↓
CMOS传感器驱动
↓
I2C总线 + ISP/CSI接口
2.2 关键数据结构
/* V4L2子设备结构 */
struct v4l2_subdev {
struct media_entity entity;
struct list_head list;
struct module *owner;
const struct v4l2_subdev_ops *ops;
void *dev_priv; // 指向传感器私有数据
};
/* 传感器私有数据结构 */
struct sensor_priv {
struct v4l2_subdev subdev;
struct i2c_client *client;
struct v4l2_ctrl_handler ctrl_handler;
/* 传感器状态 */
bool streaming;
u32 width;
u32 height;
u32 fps;
/* 控制参数 */
u32 exposure;
u32 gain;
/* GPIO控制 */
struct gpio_desc *reset_gpio;
struct gpio_desc *pwdn_gpio;
/* 时钟 */
struct clk *xvclk;
};
三、驱动初始化流程
3.1 设备探测(Probe)
static int sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct sensor_priv *sensor;
int ret;
/* 1. 分配私有数据结构 */
sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->client = client;
/* 2. 获取GPIO资源 */
sensor->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(sensor->reset_gpio)) {
dev_err(dev, "Failed to get reset gpio\n");
return PTR_ERR(sensor->reset_gpio);
}
sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn",
GPIOD_OUT_HIGH);
/* 3. 获取时钟 */
sensor->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(sensor->xvclk)) {
dev_err(dev, "Failed to get xvclk\n");
return PTR_ERR(sensor->xvclk);
}
/* 4. 初始化V4L2子设备 */
v4l2_i2c_subdev_init(&sensor->subdev, client, &sensor_subdev_ops);
sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
/* 5. 初始化控制句柄 */
ret = sensor_init_controls(sensor);
if (ret)
goto err_cleanup;
/* 6. 硬件复位 */
ret = sensor_power_on(sensor);
if (ret)
goto err_free_handler;
/* 7. 检测传感器ID */
ret = sensor_detect(sensor);
if (ret) {
dev_err(dev, "Sensor detection failed\n");
goto err_power_off;
}
/* 8. 注册V4L2子设备 */
ret = v4l2_async_register_subdev(&sensor->subdev);
if (ret) {
dev_err(dev, "Failed to register subdev\n");
goto err_power_off;
}
dev_info(dev, "Sensor probe success\n");
return 0;
err_power_off:
sensor_power_off(sensor);
err_free_handler:
v4l2_ctrl_handler_free(&sensor->ctrl_handler);
err_cleanup:
return ret;
}
3.2 电源管理
static int sensor_power_on(struct sensor_priv *sensor)
{
int ret;
/* 1. 使能时钟 */
ret = clk_prepare_enable(sensor->xvclk);
if (ret) {
dev_err(&sensor->client->dev, "Failed to enable clock\n");
return ret;
}
/* 2. 拉低PWDN引脚(Power Down) */
if (sensor->pwdn_gpio)
gpiod_set_value_cansleep(sensor->pwdn_gpio, 0);
usleep_range(1000, 2000);
/* 3. 拉低RESET引脚 */
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(5000, 10000);
/* 4. 拉高RESET引脚,释放复位 */
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(10000, 20000);
return 0;
}
static void sensor_power_off(struct sensor_priv *sensor)
{
/* 拉低RESET */
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
/* 拉高PWDN */
if (sensor->pwdn_gpio)
gpiod_set_value_cansleep(sensor->pwdn_gpio, 1);
/* 关闭时钟 */
clk_disable_unprepare(sensor->xvclk);
}
四、I2C寄存器读写
4.1 基础读写函数
/* 写单个寄存器 */
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
struct i2c_msg msg;
u8 buf[3];
int ret;
buf[0] = reg >> 8; /* 寄存器地址高字节 */
buf[1] = reg & 0xff; /* 寄存器地址低字节 */
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 < 0) {
dev_err(&client->dev, "Write reg 0x%04x failed\n", reg);
return ret;
}
return 0;
}
/* 读单个寄存器 */
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;
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 = val;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
dev_err(&client->dev, "Read reg 0x%04x failed\n", reg);
return ret;
}
return 0;
}
4.2 寄存器表批量写入
/* 寄存器配置表 */
struct reg_value {
u16 reg;
u8 val;
};
/* 示例:OV5640初始化寄存器表 */
static const struct reg_value ov5640_init_regs[] = {
{0x3103, 0x11}, /* 系统时钟来自PLL */
{0x3008, 0x82}, /* 软复位 */
{0x3008, 0x42}, /* 关闭软复位 */
{0x3103, 0x03}, /* 系统时钟配置 */
{0x3017, 0x00}, /* FREX, VSYNC, HREF, PCLK, D[9:6] 输出使能 */
{0x3018, 0x00}, /* D[5:0], GPIO[1:0] 输出使能 */
/* PLL配置 */
{0x3034, 0x18}, /* PLL倍频系数 */
{0x3035, 0x11}, /* PLL分频系数 */
{0x3036, 0x54}, /* PLL倍频 */
/* ... 更多寄存器 ... */
};
static int sensor_write_array(struct i2c_client *client,
const struct reg_value *regs, int num)
{
int i, ret;
for (i = 0; i < num; i++) {
ret = sensor_write_reg(client, regs[i].reg, regs[i].val);
if (ret < 0)
return ret;
/* 某些寄存器需要延时 */
if (regs[i].reg == 0x3008)
usleep_range(10000, 20000);
}
return 0;
}
五、图像格式与分辨率配置
5.1 格式设置
static int sensor_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *format)
{
struct sensor_priv *sensor = to_sensor(sd);
struct v4l2_mbus_framefmt *mbus_fmt = &format->format;
const struct reg_value *mode_regs;
int num_regs;
int ret;
/* 1. 检查格式支持 */
if (mbus_fmt->code != MEDIA_BUS_FMT_UYVY8_2X8 &&
mbus_fmt->code != MEDIA_BUS_FMT_YUYV8_2X8) {
dev_err(&sensor->client->dev, "Unsupported format\n");
return -EINVAL;
}
/* 2. 调整分辨率到支持的大小 */
if (mbus_fmt->width == 1920 && mbus_fmt->height == 1080) {
mode_regs = ov5640_1080p_regs;
num_regs = ARRAY_SIZE(ov5640_1080p_regs);
} else if (mbus_fmt->width == 1280 && mbus_fmt->height == 720) {
mode_regs = ov5640_720p_regs;
num_regs = ARRAY_SIZE(ov5640_720p_regs);
} else {
/* 默认VGA */
mbus_fmt->width = 640;
mbus_fmt->height = 480;
mode_regs = ov5640_vga_regs;
num_regs = ARRAY_SIZE(ov5640_vga_regs);
}
/* 3. 写入分辨率配置寄存器 */
ret = sensor_write_array(sensor->client, mode_regs, num_regs);
if (ret < 0)
return ret;
/* 4. 保存当前配置 */
sensor->width = mbus_fmt->width;
sensor->height = mbus_fmt->height;
return 0;
}
5.2 帧率控制
static int sensor_set_frame_rate(struct sensor_priv *sensor, u32 fps)
{
u16 vts; /* Vertical Total Size */
u8 vts_high, vts_low;
int ret;
/* 根据帧率计算VTS
* VTS = (PCLK / (HTS * FPS))
* 其中:
* PCLK - 像素时钟
* HTS - Horizontal Total Size (行总时间)
* FPS - 目标帧率
*/
vts = (84000000) / (2400 * fps);
vts_high = (vts >> 8) & 0xff;
vts_low = vts & 0xff;
/* 写入VTS寄存器 */
ret = sensor_write_reg(sensor->client, 0x380e, vts_high);
if (ret < 0)
return ret;
ret = sensor_write_reg(sensor->client, 0x380f, vts_low);
if (ret < 0)
return ret;
sensor->fps = fps;
return 0;
}
六、曝光与增益控制
6.1 曝光时间设置
static int sensor_set_exposure(struct v4l2_ctrl *ctrl)
{
struct sensor_priv *sensor = container_of(ctrl->handler,
struct sensor_priv,
ctrl_handler);
u32 exposure = ctrl->val; /* 曝光时间(行数) */
u8 exp_high, exp_mid, exp_low;
int ret;
/* 曝光时间寄存器为20位
* 0x3500[3:0] - 高4位
* 0x3501[7:0] - 中8位
* 0x3502[7:0] - 低8位
*/
exp_high = (exposure >> 16) & 0x0f;
exp_mid = (exposure >> 8) & 0xff;
exp_low = exposure & 0xff;
/* 分组写入,避免中间状态 */
ret = sensor_write_reg(sensor->client, 0x3208, 0x00); /* 开始分组 */
if (ret < 0)
return ret;
sensor_write_reg(sensor->client, 0x3500, exp_high);
sensor_write_reg(sensor->client, 0x3501, exp_mid);
sensor_write_reg(sensor->client, 0x3502, exp_low);
ret = sensor_write_reg(sensor->client, 0x3208, 0x10); /* 结束分组 */
if (ret < 0)
return ret;
ret = sensor_write_reg(sensor->client, 0x3208, 0xa0); /* 启动分组 */
return ret;
}
6.2 增益控制
static int sensor_set_gain(struct v4l2_ctrl *ctrl)
{
struct sensor_priv *sensor = container_of(ctrl->handler,
struct sensor_priv,
ctrl_handler);
u32 gain = ctrl->val; /* 增益值 */
u16 reg_gain;
u8 gain_high, gain_low;
int ret;
/* 将线性增益转换为寄存器值
* gain = (reg_gain / 16)
* reg_gain范围: 16-1023 (1x-64x)
*/
if (gain < 16)
gain = 16;
if (gain > 1023)
gain = 1023;
reg_gain = gain;
gain_high = (reg_gain >> 8) & 0x03;
gain_low = reg_gain & 0xff;
/* 写入增益寄存器 */
ret = sensor_write_reg(sensor->client, 0x350a, gain_high);
if (ret < 0)
return ret;
ret = sensor_write_reg(sensor->client, 0x350b, gain_low);
return ret;
}
6.3 V4L2控制初始化
static int sensor_init_controls(struct sensor_priv *sensor)
{
struct v4l2_ctrl_handler *handler = &sensor->ctrl_handler;
int ret;
/* 初始化控制句柄 */
ret = v4l2_ctrl_handler_init(handler, 4);
if (ret)
return ret;
/* 曝光控制 */
v4l2_ctrl_new_std(handler, &sensor_ctrl_ops,
V4L2_CID_EXPOSURE, 1, 0xffff, 1, 0x0450);
/* 增益控制 */
v4l2_ctrl_new_std(handler, &sensor_ctrl_ops,
V4L2_CID_GAIN, 16, 1023, 1, 16);
/* 水平翻转 */
v4l2_ctrl_new_std(handler, &sensor_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
/* 垂直翻转 */
v4l2_ctrl_new_std(handler, &sensor_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
if (handler->error) {
ret = handler->error;
v4l2_ctrl_handler_free(handler);
return ret;
}
sensor->subdev.ctrl_handler = handler;
return 0;
}
static const struct v4l2_ctrl_ops sensor_ctrl_ops = {
.s_ctrl = sensor_set_ctrl,
};
static int sensor_set_ctrl(struct v4l2_ctrl *ctrl)
{
switch (ctrl->id) {
case V4L2_CID_EXPOSURE:
return sensor_set_exposure(ctrl);
case V4L2_CID_GAIN:
return sensor_set_gain(ctrl);
case V4L2_CID_HFLIP:
return sensor_set_hflip(ctrl);
case V4L2_CID_VFLIP:
return sensor_set_vflip(ctrl);
default:
return -EINVAL;
}
}
七、数据流控制
7.1 启动/停止流
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
struct sensor_priv *sensor = to_sensor(sd);
int ret;
if (enable) {
/* 启动数据流 */
if (sensor->streaming)
return 0;
/* 写入初始化寄存器 */
ret = sensor_write_array(sensor->client,
ov5640_init_regs,
ARRAY_SIZE(ov5640_init_regs));
if (ret < 0)
return ret;
/* 启动streaming */
ret = sensor_write_reg(sensor->client, 0x4202, 0x00);
if (ret < 0)
return ret;
sensor->streaming = true;
dev_info(&sensor->client->dev, "Stream started\n");
} else {
/* 停止数据流 */
if (!sensor->streaming)
return 0;
ret = sensor_write_reg(sensor->client, 0x4202, 0x0f);
if (ret < 0)
return ret;
sensor->streaming = false;
dev_info(&sensor->client->dev, "Stream stopped\n");
}
return 0;
}
八、设备树配置示例
&i2c1 {
status = "okay";
camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
clocks = <&camera_clk>;
clock-names = "xvclk";
clock-frequency = <24000000>;
reset-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pwdn-gpios = <&gpio1 19 GPIO_ACTIVE_HIGH>;
port {
ov5640_to_mipi_csi2: endpoint {
remote-endpoint = <&mipi_csi2_in>;
clock-lanes = <0>;
data-lanes = <1 2>;
link-frequencies = /bits/ 64 <456000000>;
};
};
};
};
九、总结
CMOS驱动程序开发的关键要点:
- 硬件接口理解: 深入理解I2C配置接口和数据接口(并行/MIPI)
- 时序控制: 正确的上电时序、复位时序和时钟配置
- 寄存器操作: 准确的寄存器读写和配置表管理
- V4L2框架: 熟练使用V4L2子设备接口和控制框架
- 图像管道: 理解ISP、曝光、增益等图像处理参数
- 调试技巧: 使用示波器检查时钟、使用I2C工具验证通信
完整的驱动开发需要配合硬件数据手册和实际调试,本文提供的代码框架可作为开发起点。
更多推荐



所有评论(0)