CMOS

741 阅读13分钟

转载:CMOS摄像头驱动

1.CMOS摄像头硬件原理

自然景观->摄像头模块->接口->S3C2440的摄像头控制器->LCD 摄像头模块:ov7740

1.1 摄像头参数

根据OV7740_CSP_DS_1.51.pdf-ov7740的datasheet中的参数可得: 支持的输出格式:RAW RGB与YUV格式

  • RAW RGB和RGB的区别?

RAW RGB就是只有红绿蓝三种颜色的数据,而RGB可以表示红绿蓝组合而成的任何一种颜色

  • RGB,YUV分别是什么?

RGB,YUV(Y:亮度信号),U(R-Y的色差信号),V(B-Y的色差信号)组成是两种完全不同的颜色空间,他们可以相互转换,转换公式为:

 Y = 0.299R + 0.587G + 0.114B
 U = -0.147R - 0.289G + 0.436B
 V = 0.615R - 0.515G - 0.100B
 R = Y + 1.14V
 G = Y - 0.39U - 0.58V
 B = Y + 2.03U

输出分辨率为:VGA(640 480),QVGA(240 320),CIF(352 288),更小的大小 1.有效感光阵列大小:656488 = 320128(30W)指感光区域内单像素点的数量,像素越多,拍摄画面幅面就越大,可拍摄的画面的细节就越多; 镜头的大小(lens size):1/5寸(指感光区域对角线距离,尺寸越大,材料成本越高);

  • 像素点颗粒的大小(pixel size): 4.2um * 4.2um(指单个感光元件的长宽尺寸,也称单像素的开口尺寸,开口尺寸越大,单位时间内进入的光量就越大,芯片整体性能就相对较高,最终拍摄画面的整体画质相对较优秀)
  • 单像素尺寸是图像传感器一个相当关键的参数,也就是都号称1200万像素的相机和手机,相机的效果远远好于手机的原因。手机由于体积限制,尽管像素很多,但每个像素都很小,拍摄瞬间进光量也小,成像质量就自然差一些了。
  • 以上三个参数,都是用来描述感光阵列,即使同为30W像素的摄像头,如果它的镜头尺寸大小越小,那么对应的像素点颗粒的大小就越小,从而感光性就越差,进而拍摄的效果就越差。
  1. 输入时钟频率(input clock frequency): 6~27MHz 即0V7740摄像头模组的工作频率范围。0V7740摄像头模组的工作时钟范围,这个将由SOC提供给CMOS
  • 扫描模式(scan mode): 连续扫描§ 扫描方式一般分为”逐行扫描”§和”隔行扫描”(I)两种。 逐行扫描:每一帧图像由电子束顺序地一行接着一行连续扫描而成; 隔行扫描:把每一帧图像通过两场扫描完成则是隔行扫描,两场扫描中,第一场(奇数场)只扫描奇数行,依次扫描1、3、5…行,而第二场(偶数场)只扫描偶数行,依次扫描2、4、6…行。 隔行扫描技术在传送信号带宽不够的情况下起了很大作用,逐行扫描和隔行扫描的显示效果主要区别在稳定性上面,隔行扫描的行间闪烁比较明显,逐行扫描克服了隔行扫描的缺点,画面平滑自然无闪烁。

1.2 内部数据的处理流程

从图中我们看出数据处理流程分为三个部分:

  1. image sensor core(ISC)----pdf第四章 图像翻转、增益大小调整、黑电平校准、饱和度的控制、OTP存储器
  2. image sensor processor(ISP)—pdf第五章 提供测试功能、镜头补偿功能、自动白平衡、颜色空间的转换功能(RAW RGB->RGB、RGB->YUV)、窗口功能(自动裁剪图片)、缩小放大功能;
  3. image output interface(ISI)—pdf第六章 功能:RAW RGB/YUV(图片数据格式)、VGA/QVGA、BT601/BT656 BT601有独立的行同步信号线、帧同步信号线,而BT656是将这两种信号内嵌到数据中;

问:以上这些处理过程,不需要我们人为的做任何设置,它们都能自动完成吗?

答:以上这些处理过程,只有极少部分是自动完成的,而剩余部分是需要我们设置后,才能完成。

问:怎么对它们进行设置呢?

答:是通过IIC总线,操作OV7740的寄存器来进行设置的。

2.硬件原理图

2.1 ov7740对外的接口如下:

这是ov7740对外暴露出来的接口,可以看到CAMERA相关的数据外,还有两根IIC的数据线和时钟线。

CAMRST     - 复位CMOS摄像头模块
CAMCLK     - 摄像头模块工作的系统时钟(24MHZ)
CAM_HREF   - 行同步信号
CAM_VSYNC  - 帧同步信号
CAM_PCLK   - 像素时钟
CAMDATA0~7 - 数据线

IIC数据线与时钟线的作用是什么?

S3C2440通过IIC总线,操作OV7740的寄存器来进行设置,控制ov7740输出正确的格式,

  • 初始化:对摄像头模块进行相应的初始化操作,让摄像头模块可以正常的输出正确的格式
  • 控制:设置亮度,旋转,缩放等操作

2.2 对应s3c2440接口

CAM数据类: CAM控制类: 摄像头工作流程:首先SOC输出CAMCLKOUT给摄像头提供时钟,然后控制CAMRST复位摄像头,再通过I2C初始化,设置摄像头,最后在HREF,VSYNC和PCLK的控制下,通过D0-D7这八根数据线将数据线发给SOC

3.CAMERA控制器

  • 摄像头采集的数据CPU一般不直接处理,主控芯片里面集成了Camera控制器FIMC(FullyInteractive Mobile Camera)来处理,摄像头先把图像数据传给Camera控制器,经过控制器处理(裁剪拉升后直接预览或者编码)之后交给CPU来处理,际上摄像头工作需要的时钟(MCLK)也是FIMC给它提供的。
  • 摄像头驱动主要分为两个部分
  1. 一部分是由模组厂家或者sensor厂家提供的初始化代码,通常是一段数组,通过I2C总线来控制,用于设置sensor数组,使用时一般不需要修改,如需调整,也由模组厂家完成
  2. 另一个部分是应用处理器端的代码,这部分需要各个平台自行开发,在Exynos4412中就是CAMIF和FIMC。
  3. CMOS摄像头模块,实际上是一个I2C设备,通过I2C设置,控制摄像头,SOC的摄像头控制器(CAMIF和FIMC)负责数据的处理
  4. 因此后面CMOS驱动的核心就是:设置OV7740控制器,使其按想要的方式输出数据以及传输数据。设置camera interface控制器,使其能够接收ov7740传进来的数据并处理
  5. 问:为什么需要复位摄像头模块? 答:IIC能够正常操作CMOS摄像头模块内部的寄存器的前提是: -- 提供符合它需求的系统时钟(CAMCLK) -- 需要给它一个复位信号
  6. 问:怎样才能复位摄像头模块? 答:通过操作CAMIF控制器中相应的寄存器,让CAMRST发出复位 信号,从而复位摄像头模块,具体操作见驱动源码。

3.1 用户手册设计的寄存器

涉及到的所有寄存器
涉及到的所有寄存器
  • 问:为什么原理图中涉及IIC(GPE管脚)而程序中并没有涉及? 答:原因是程序并不是我们手动的输入IIC管脚的输入输出数据,而是调用i2c_smbus_read_byte_data()内核所提供的的函数进行读写。(所以在实验步骤时会提前先加载IIC的驱动,以便数据的读写

4. 框架

4.1 IIC框架

cmos_ov7740_dev.c

static struct i2c_board_info cmos_ov7740_info = { 
 I2C_BOARD_INFO("cmos_ov7740", 0x21),
};

static struct i2c_client *cmos_ov7740_client;

static int cmos_ov7740_dev_init(void)
{
 struct i2c_adapter *i2c_adap;

 i2c_adap = i2c_get_adapter(0);
 cmos_ov7740_client = i2c_new_device(i2c_adap, &cmos_ov7740_info);
 i2c_put_adapter(i2c_adap);

 return 0;
}

static void cmos_ov7740_dev_exit(void)
{
 i2c_unregister_device(cmos_ov7740_client);
}

module_init(cmos_ov7740_dev_init);
module_exit(cmos_ov7740_dev_exit);

MODULE_LICENSE("GPL");

cmos_ov7740_drv.c

static const struct i2c_device_id cmos_ov7740_id_table[] = {
 { "cmos_ov7740", 0 },
 {}
};

/* 1.1. 分配、设置一个i2c_driver */
static struct i2c_driver cmos_ov7740_driver = {
 .driver = {
  .name = "cmos_ov7740",
  .owner = THIS_MODULE,
 },
 .probe  = cmos_ov7740_probe,
 .remove  = __devexit_p(cmos_ov7740_remove),
 .id_table = cmos_ov7740_id_table,
};

static int cmos_ov7740_drv_init(void)
{
 /* 1.2.注册 */
 i2c_add_driver(&cmos_ov7740_driver);

 return 0;
}

static void cmos_ov7740_drv_exit(void)
{
 i2c_del_driver(&cmos_ov7740_driver);
}

module_init(cmos_ov7740_drv_init);
module_exit(cmos_ov7740_drv_exit);

MODULE_LICENSE("GPL");

当设备与驱动匹配后,调用probe函数:

static int __devinit cmos_ov7740_probe(struct i2c_client *client,
      const struct i2c_device_id *id)
{
 printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
 /* 2.2.注册 */
video_register_device(&cmos_ov7740_vdev, VFL_TYPE_GRABBER, -1);

 return 0

退出:

static int __devexit cmos_ov7740_remove(struct i2c_client *client)
{
 printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

 video_unregister_device(&cmos_ov7740_vdev);
 return 0;
}
/* 2.1. 分配、设置一个video_device结构体 */
static struct video_device cmos_ov7740_vdev = {
 .fops  = &cmos_ov7740_fops,
 .ioctl_ops  = &cmos_ov7740_ioctl_ops,
 .release  = cmos_ov7740_release,
 .name  = "cmos_ov7740",
};

cmos_ov7740_fops结构体及函数:

static const struct v4l2_file_operations cmos_ov7740_fops = {
 .owner   = THIS_MODULE,
 .open         = cmos_ov7740_open,
 .release      = cmos_ov7740_close,
 .unlocked_ioctl       = video_ioctl2,
 .read   = cmos_ov7740_read,
};

* A1 */
static int cmos_ov7740_open(struct file *file)
{
 return 0;
}

/* A18 关闭 */
static int cmos_ov7740_close(struct file *file)
{
    
 return 0;
}

/* 应用程序通过读的方式读取摄像头的数据 */
static ssize_t cmos_ov7740_read(struct file *filep, char __user *buf, size_t count, loff_t *pos)
{
 return 0;
}

cmos_ov7740_ioctl_ops结构体及其函数:

static const struct v4l2_ioctl_ops cmos_ov7740_ioctl_ops = {
        // 表示它是一个摄像头设备
        .vidioc_querycap      = cmos_ov7740_vidioc_querycap,

        /* 用于列举、获得、测试、设置摄像头的数据的格式 */
        .vidioc_enum_fmt_vid_cap  = cmos_ov7740_vidioc_enum_fmt_vid_cap,
        .vidioc_g_fmt_vid_cap     = cmos_ov7740_vidioc_g_fmt_vid_cap,
        .vidioc_try_fmt_vid_cap   = cmos_ov7740_vidioc_try_fmt_vid_cap,
        .vidioc_s_fmt_vid_cap     = cmos_ov7740_vidioc_s_fmt_vid_cap,
        
        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
        .vidioc_reqbufs       = cmos_ov7740_vidioc_reqbufs,

 /* 说明: 因为我们是通过读的方式来获得摄像头数据,因此查询/放入队列/取出队列这些操作函数将不在需要 */
#if 0
        .vidioc_querybuf      = myuvc_vidioc_querybuf,
        .vidioc_qbuf          = myuvc_vidioc_qbuf,
        .vidioc_dqbuf         = myuvc_vidioc_dqbuf,
#endif

        // 启动/停止
        .vidioc_streamon      = cmos_ov7740_vidioc_streamon,
        .vidioc_streamoff     = cmos_ov7740_vidioc_streamoff,   
};

/* A2 参考 uvc_v4l2_do_ioctl */
static int cmos_ov7740_vidioc_querycap(struct file *file, void  *priv,
     struct v4l2_capability *cap)
{
 return 0;
}

/* A3 列举支持哪种格式
 * 参考: uvc_fmts 数组
 */
static int cmos_ov7740_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
     struct v4l2_fmtdesc *f)
{
 return 0;
}

/* A4 返回当前所使用的格式 */
static int cmos_ov7740_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
     struct v4l2_format *f)
{
 return 0;
}

/* A5 测试驱动程序是否支持某种格式, 强制设置该格式 
 * 参考: uvc_v4l2_try_format
 *       myvivi_vidioc_try_fmt_vid_cap
 */
static int cmos_ov7740_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
   struct v4l2_format *f)
{
 return 0;
}

/* A6 参考 myvivi_vidioc_s_fmt_vid_cap */
static int cmos_ov7740_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
     struct v4l2_format *f)
{
 return 0;
}

static int cmos_ov7740_vidioc_reqbufs(struct file *file, void *priv,
     struct v4l2_requestbuffers *p)
{
 return 0;
}

/* A11 启动传输 
 * 参考: uvc_video_enable(video, 1):
 *           uvc_commit_video
 *           uvc_init_video
 */
static int cmos_ov7740_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
 return 0;
}

/* A17 停止 
 * 参考 : uvc_video_enable(video, 0)
 */
static int cmos_ov7740_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{
 return 0;
}

/*
 注意:
  该函数是必须的,否则在insmod的时候,会出错
*/
static void cmos_ov7740_release(struct video_device *vdev)
{
}

4.2 数据采集流程

  1. 查询是否是一个摄像头设备
  2. 列举,获得,测试,设置摄像头的数据格式
  3. 缓冲区操作:申请/查询/放入/取出队列
  4. 启动,关闭摄像头设备
static const struct v4l2_ioctl_ops cmos_ov7740_ioctl_ops = {
  // 表示它是一个摄像头设备
  .vidioc_querycap      = cmos_ov7740_vidioc_querycap,
  /* 用于列举摄像头数据格式 */
 .vidioc_enum_fmt_vid_cap  = cmos_ov7740_vidioc_enum_fmt_vid_cap,
  /*获得摄像头数据格式*/
  .vidioc_g_fmt_vid_cap     = cmos_ov7740_vidioc_g_fmt_vid_cap,
  /*测试摄像头数据格式*/
  .vidioc_try_fmt_vid_cap   = cmos_ov7740_vidioc_try_fmt_vid_cap,
  /*设置摄像头数据格式*/
  .vidioc_s_fmt_vid_cap     = cmos_ov7740_vidioc_s_fmt_vid_cap,    
  /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
  .vidioc_reqbufs       = cmos_ov7740_vidioc_reqbufs,
 /* 说明: 因为我们是通过读的方式来获得摄像头数据,因此查询/放入队列/取出队列这些操作函数将不在需要 */
#if 0
  /*查询*/
  .vidioc_querybuf      = myuvc_vidioc_querybuf,
  /*放入队列*/
  .vidioc_qbuf          = myuvc_vidioc_qbuf,
  /*取出队列*/
  .vidioc_dqbuf         = myuvc_vidioc_dqbuf,
#endif
  // 启动/停止
  .vidioc_streamon      = cmos_ov7740_vidioc_streamon,
  .vidioc_streamoff     = cmos_ov7740_vidioc_streamoff,   
};

4.3 硬件操作

1.寄存器映射

 GPJCON = ioremap(0x560000d0, 4);
 GPJDAT = ioremap(0x560000d4, 4);
 GPJUP = ioremap(0x560000d8, 4);

 CISRCFMT = ioremap(0x4F000000, 4);
 CIWDOFST = ioremap(0x4F000004, 4);
 CIGCTRL = ioremap(0x4F000008, 4);
 CIPRCLRSA1 = ioremap(0x4F00006C, 4);
 CIPRCLRSA2 = ioremap(0x4F000070, 4);
 CIPRCLRSA3 = ioremap(0x4F000074, 4);
 CIPRCLRSA4 = ioremap(0x4F000078, 4);
 CIPRTRGFMT = ioremap(0x4F00007C, 4);
 CIPRCTRL = ioremap(0x4F000080, 4);
 CIPRSCPRERATIO = ioremap(0x4F000084, 4);
 CIPRSCPREDST = ioremap(0x4F000088, 4);
 CIPRSCCTRL = ioremap(0x4F00008C, 4);
 CIPRTAREA = ioremap(0x4F000090, 4);
 CIIMGCPT = ioremap(0x4F0000A0, 4);

 SRCPND = ioremap(0X4A000000, 4);
 INTPND = ioremap(0X4A000010, 4);
 SUBSRCPND = ioremap(0X4A000018, 4);
  1. 设置相应的GPIO用于CAMIF
cmos_ov7740_gpio_cfg();
static void cmos_ov7740_gpio_cfg(void)
{
 /* 设置相应的GPIO用于CAMIF */
 *GPJCON = 0x2aaaaaa;
 *GPJDAT = 0;
 /* 使能上拉电阻 */
 *GPJUP = 0;
}
  1. 复位CAMIF控制器
cmos_ov7740_camif_reset();
static void cmos_ov7740_camif_reset(void)
{
 /* 传输方式为BT601 */
 *CISRCFMT |= (1<<31);

 /* 复位CAMIF控制器 */
 *CIGCTRL |= (1<<31);
 mdelay(10);
 *CIGCTRL &= ~(1<<31);
 mdelay(10); 
}

4.设置,使能时钟(使能HCLK、使能并设置CAMCLK = 24MHz)

cmos_ov7740_clk_cfg();

static void cmos_ov7740_clk_cfg(void)
{
 struct clk *camif_clk;
 struct clk *camif_upll_clk;

 /* 使能CAMIF的时钟源 */
 camif_clk = clk_get(NULL, "camif");
 if(!camif_clk || IS_ERR(camif_clk))
 {
  printk(KERN_INFO "failed to get CAMIF clock source\n");
 }
 clk_enable(camif_clk);

 /* 使能并设置CAMCLK = 24MHz */
 camif_upll_clk = clk_get(NULL, "camif-upll");
 clk_set_rate(camif_upll_clk, 24000000);
 mdelay(100);
}

5.复位摄像头模块

cmos_ov7740_reset
/*
 注意:
  1.S3C2440提供的复位时序(CAMRST)为:0->1->0(0:表示正常工作的电平、1:表示复位电平)
    但是,实验证明,该复位时序与我们的OV7740需要的复位时序(1->0->1)不符合。
  2.因此,我们就应该结合OV7740的具体复位时序,来设置相应的寄存器。
*/
static void cmos_ov7740_reset(void)
{
 *CIGCTRL |= (1<<30);
 mdelay(30);
 *CIGCTRL &= ~(1<<30);
 mdelay(30);
 *CIGCTRL |= (1<<30);
 mdelay(30); 
}

6.通过IIC总线初始化摄像头模块

typedef struct cmos_ov7740_i2c_value {
 unsigned char regaddr;
 unsigned char value;
}ov7740_t;

/* init: 640x480,30fps的,YUV422输出格式 */
ov7740_t ov7740_setting_30fps_VGA_640_480[] =
{
 {0x12, 0x80},
 {0x47, 0x02},
 {0x17, 0x27},
 {0x04, 0x40},
 {0x1B, 0x81},
 {0x29, 0x17},
 {0x5F, 0x03},
 {0x3A, 0x09},
 {0x33, 0x44},
 {0x68, 0x1A},

 {0x14, 0x38},
 {0x5F, 0x04},
 {0x64, 0x00},
 {0x67, 0x90},
 {0x27, 0x80},
 {0x45, 0x41},
 {0x4B, 0x40},
 {0x36, 0x2f},
 {0x11, 0x01},
 {0x36, 0x3f},
 {0x0c, 0x12},

 {0x12, 0x00},
 {0x17, 0x25},
 {0x18, 0xa0},
 {0x1a, 0xf0},
 {0x31, 0xa0},
 {0x32, 0xf0},

 {0x85, 0x08},
 {0x86, 0x02},
 {0x87, 0x01},
 {0xd5, 0x10},
 {0x0d, 0x34},
 {0x19, 0x03},
 {0x2b, 0xf8},
 {0x2c, 0x01},

 {0x53, 0x00},
 {0x89, 0x30},
 {0x8d, 0x30},
 {0x8f, 0x85},
 {0x93, 0x30},
 {0x95, 0x85},
 {0x99, 0x30},
 {0x9b, 0x85},

 {0xac, 0x6E},
 {0xbe, 0xff},
 {0xbf, 0x00},
 {0x38, 0x14},
 {0xe9, 0x00},
 {0x3D, 0x08},
 {0x3E, 0x80},
 {0x3F, 0x40},
 {0x40, 0x7F},
 {0x41, 0x6A},
 {0x42, 0x29},
 {0x49, 0x64},
 {0x4A, 0xA1},
 {0x4E, 0x13},
 {0x4D, 0x50},
 {0x44, 0x58},
 {0x4C, 0x1A},
 {0x4E, 0x14},
 {0x38, 0x11},
 {0x84, 0x70}
};

static struct i2c_client *cmos_ov7740_client;
static int __devinit cmos_ov7740_probe(struct i2c_client *client,const struct i2c_device_id *id)
 
cmos_ov7740_client = client;
cmos_ov7740_init();
static void cmos_ov7740_init(void)
{
 unsigned int mid;
 int i;

 /* 读 */
 mid = i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0a)<<8;
 mid |= i2c_smbus_read_byte_data(cmos_ov7740_client, 0x0b);
 printk("manufacture ID = 0x%4x\n", mid);

 /* 写 */
 for(i = 0; i < OV7740_INIT_REGS_SIZE; i++)
 {
  i2c_smbus_write_byte_data(cmos_ov7740_client, ov7740_setting_30fps_VGA_640_480[i].regaddr, ov7740_setting_30fps_VGA_640_480[i].value);
  mdelay(2);
 }
}

中断注册:


/* 2.3.7 注册中断 */
 if (request_irq(IRQ_S3C2440_CAM_C, cmos_ov7740_camif_irq_c, IRQF_DISABLED , "CAM_C", NULL))
  printk("%s:request_irq failed\n", __func__);

 if (request_irq(IRQ_S3C2440_CAM_P, cmos_ov7740_camif_irq_p, IRQF_DISABLED , "CAM_P", NULL))
  printk("%s:request_irq failed\n", __func__);

本文使用 mdnice 排版