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工具验证通信
完整的驱动开发需要配合硬件数据手册和实际调试,本文提供的代码框架可作为开发起点。