转载: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像素的摄像头,如果它的镜头尺寸大小越小,那么对应的像素点颗粒的大小就越小,从而感光性就越差,进而拍摄的效果就越差。
- 输入时钟频率(input clock frequency): 6~27MHz 即0V7740摄像头模组的工作频率范围。0V7740摄像头模组的工作时钟范围,这个将由SOC提供给CMOS
- 扫描模式(scan mode): 连续扫描§ 扫描方式一般分为”逐行扫描”§和”隔行扫描”(I)两种。 逐行扫描:每一帧图像由电子束顺序地一行接着一行连续扫描而成; 隔行扫描:把每一帧图像通过两场扫描完成则是隔行扫描,两场扫描中,第一场(奇数场)只扫描奇数行,依次扫描1、3、5…行,而第二场(偶数场)只扫描偶数行,依次扫描2、4、6…行。 隔行扫描技术在传送信号带宽不够的情况下起了很大作用,逐行扫描和隔行扫描的显示效果主要区别在稳定性上面,隔行扫描的行间闪烁比较明显,逐行扫描克服了隔行扫描的缺点,画面平滑自然无闪烁。
1.2 内部数据的处理流程
从图中我们看出数据处理流程分为三个部分:
- image sensor core(ISC)----pdf第四章 图像翻转、增益大小调整、黑电平校准、饱和度的控制、OTP存储器
- image sensor processor(ISP)—pdf第五章 提供测试功能、镜头补偿功能、自动白平衡、颜色空间的转换功能(RAW RGB->RGB、RGB->YUV)、窗口功能(自动裁剪图片)、缩小放大功能;
- 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给它提供的。
- 摄像头驱动主要分为两个部分
- 一部分是由模组厂家或者sensor厂家提供的初始化代码,通常是一段数组,通过I2C总线来控制,用于设置sensor数组,使用时一般不需要修改,如需调整,也由模组厂家完成
- 另一个部分是应用处理器端的代码,这部分需要各个平台自行开发,在Exynos4412中就是CAMIF和FIMC。
- CMOS摄像头模块,实际上是一个I2C设备,通过I2C设置,控制摄像头,SOC的摄像头控制器(CAMIF和FIMC)负责数据的处理
- 因此后面CMOS驱动的核心就是:设置OV7740控制器,使其按想要的方式输出数据以及传输数据。设置camera interface控制器,使其能够接收ov7740传进来的数据并处理
- 问:为什么需要复位摄像头模块? 答:IIC能够正常操作CMOS摄像头模块内部的寄存器的前提是: -- 提供符合它需求的系统时钟(CAMCLK) -- 需要给它一个复位信号
- 问:怎样才能复位摄像头模块? 答:通过操作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 数据采集流程
- 查询是否是一个摄像头设备
- 列举,获得,测试,设置摄像头的数据格式
- 缓冲区操作:申请/查询/放入/取出队列
- 启动,关闭摄像头设备
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);
- 设置相应的GPIO用于CAMIF
cmos_ov7740_gpio_cfg();
static void cmos_ov7740_gpio_cfg(void)
{
/* 设置相应的GPIO用于CAMIF */
*GPJCON = 0x2aaaaaa;
*GPJDAT = 0;
/* 使能上拉电阻 */
*GPJUP = 0;
}
- 复位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 排版