一、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驱动程序开发的关键要点:

  1. 硬件接口理解: 深入理解I2C配置接口和数据接口(并行/MIPI)
  2. 时序控制: 正确的上电时序、复位时序和时钟配置
  3. 寄存器操作: 准确的寄存器读写和配置表管理
  4. V4L2框架: 熟练使用V4L2子设备接口和控制框架
  5. 图像管道: 理解ISP、曝光、增益等图像处理参数
  6. 调试技巧: 使用示波器检查时钟、使用I2C工具验证通信

完整的驱动开发需要配合硬件数据手册和实际调试,本文提供的代码框架可作为开发起点。

更多推荐