为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

CMOS摄像头驱动解析

2012-11-15 29页 pdf 208KB 48阅读

用户头像

is_796073

暂无简介

举报
CMOS摄像头驱动解析 我们解析一下MINI2440开发板的 CMOS摄像头的驱动。该驱动总共 包括以下文件:sccb.c、sccb.h、s3c2440_ov9650.c、s3c2440camif.h、 s3c2440camif.c五个文件! s3c2440camif.c用于从 cmos接口获取图像数据和将数据传输到进程 空间(在有 app读取时)。 s3c2440_ov9650.c 读取和配置 ov9650寄存器。通过 iic接口传输数据。 设备地址是 60(#define OV9650_SCCB_ADDR 0x60).比如进行初 ...
CMOS摄像头驱动解析
我们解析一下MINI2440开发板的 CMOS摄像头的驱动。该驱动总共 包括以下文件:sccb.c、sccb.h、s3c2440_ov9650.c、s3c2440camif.h、 s3c2440camif.c五个文件! s3c2440camif.c用于从 cmos接口获取图像数据和将数据传输到进程 空间(在有 app读取时)。 s3c2440_ov9650.c 读取和配置 ov9650寄存器。通过 iic接口传输数据。 设备地址是 60(#define OV9650_SCCB_ADDR 0x60).比如进行初 始化和 product id获取. sccb.c 定义了去读 ov9650的寄存器的具体方法,是时序模拟的 iic。 而 s3c2440_ov9650.c里是调用这些具体方法去读写 ov9650的寄存器 的。 所以我们先 sccb.c! 首先看看它的结构: 可以看到该程序是处理读写数据到 ov9650的过程。 首先我们应该知道 OV9650 内部有大量的寄存器需要配置,这就需 要另外的数据接口。 OV9650 的数据接口称为 SCCB(串行摄像控制 总线),它由两条数据线组成:一个是用于传输时钟信号的 SIO_C, 另一个是用于传输数据信号的 SIO_D。SCCB 的传输与 IIC 的 极其相似,只不过 II C在每传输完一个字节后,接收数据的一方要发 送一位的确认数据,而 SCCB一次要传输 9位数据,前 8位为有用数 据,而第 9位数据在写周期中是 Don’t-Care 位(即不必关心位),在 读周期中是 NA位。SCCB 定义数据传输的基本单元为相(phase), 即一个相传输一个字节数据。SCCB只包括三种传输周期,即 3相写 传输周期(三个相依次为设备从地址,内存地址,所写数据),2相 写传输周期(两个相依次为设备从地址,内存地址)和 2相读传输周 期(两个相依次为设备从地址,所读数据)。当需要写操作时,应用 3相写传输周期,当需要读操作时,依次应用 2相写传输周期和 2相 读传输周期。 因此 SCCB 一次只能读或写一个字节。下面我们就用 s3c244的 IIC总线接口分别与 OV9650的 SIO_C和 SIO_D相连接来 实现 SCCB的功能。 接下来对一些重要的函数讲一讲。 static void __inline__ sccb_start(void) { CFG_WRITE(SIO_D); Low(SIO_D); WAIT_STABLE(); //延时一会,保持一定的低电平,以此代 sccb 总线开始传输数据 这个函数原型是: #define CFG_WRITE(x) do{s3c2410_gpio_cfgpin(x,S3 C2410_GPIO_OUTPUT);smp _mb();}while(0)。通过这个宏 定义可以看出它是配置 GPE14为输出管脚。 批注[A1]: 这个函数的原型 是:#define Low(x) do{s3c2410_gpio_setpin(x,0) ; smp_mb();}while(0) 可见是输出低电平的功能! 批注[A2]: } 值得提出的是内核在对管脚的寄存器配置中用到了一个概念:内存屏 障!即 smp_mb()函数。 什么叫内存屏障?下面给出例子: #define set_mb(var, value) do { var = value; mb(); } while (0) #define mb() __asm__ __volatile__ ("" : : : "memory") 1 ) set_mb(),mb(),barrier() 函 数 追 踪 到 底 , 就 是 __asm__ __volatile__("":::"memory"),而这行代码就是内存屏障。 2)__asm__用于指示编译器在此插入汇编语句 3)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语 句重组合优化。即:原原本本按原来的样子处理这这里的汇编。 4)memory 强制 gcc编译器假设 RAM所有内存单元均被汇编指令修 改,这样 cpu中的 registers和 cache中已缓存的内存单元中的数据将 作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止 了 cpu又将 registers,cache中的数据用于去优化指令,而避免去访问 内存。 5)"":::表示这是个空指令。barrier()不用在此插入一条串行化汇编指 令。在后文将讨论什么叫串行化指令。 6)__asm__,__volatile__,memory在前面已经解释 在 linux/include/asm-i386/system.h定义: #define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory") 7)lock前缀表示将后面这句汇编语句:"addl $0,0(%%esp)"作为 cpu 的一个内存屏障。 8)addl $0,0(%%esp)表示将数值 0加到 esp寄存器中,而该寄存器指 向栈顶的内存单元。加上一个 0,esp寄存器的数值依然不变。即这 是一条无用的汇编指令。在此利用这条无价值的汇编指令来配合 lock 指令,在__asm__,__volatile__,memory的作用下,用作 cpu的内存屏 障。 9)set_task_state()带有一个 memory barrier,set_task_state()肯定是安 全的,但 __set_task_state()可能会快些。 关于 barrier()宏实际上也是优化屏障: #define barrier() __asm__ __volatile__("": : :"memory") CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句 实际上不生成任何代码,但可使 gcc在 barrier()之后刷新寄存器对变 量的分配。 不论是 gcc编译器的优化还是处理器本身采用的大量优化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues 等,都可能使得实际执行可能违 反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序 顺序来执行。 使用内存屏障强加的严格的 CPU内存事件次序,保证程序的执行看 上去象是遵循顺序一致性模型。 接下去的函数是写一个字节到芯片中: static void __inline__ sccb_write_byte(u8 data) { int i; CFG_WRITE(SIO_D); WAIT_STABLE(); /* write 8-bits octet. */ for (i=0;i<8;i++) { Low(SIO_C); WAIT_STABLE(); if (data & 0x80) { High(SIO_D); } else { Low(SIO_D); } data = data<<1; 首先置数据总线为 低电平!并等待一会儿! 批注[A3]: 然后开始传输一个 字节。很据时序,首先置时 钟为低电平!然后等待一会! 批注[A4]: 并行数据转串行数 据,我们先传输最高位!将 我们要传输的字节与 0x80位 与,没位与一次,该字节就 左移一次,以保证每位都与 0x80都位与过,以得到整个 数据传输图!通过程序可以 看出字节是在时钟低电平时 变化的!这与 IIC总线协议一 致! 批注[A5]: WAIT_CYCLE(); High(SIO_C); WAIT_CYCLE(); } /* write byte done, wait the Don't care bit now. */ { Low(SIO_C); High(SIO_D); CFG_READ(SIO_D); WAIT_CYCLE(); High(SIO_C); WAIT_CYCLE(); } } 之后读一个字节函数与此类似!记住这里是串行数据转并行,同样高 位在前!这里的读写都是以 ov9650芯片为参考物的! Sccb总线的 stop函数与开始类似! 有了上面四个基本函数,我们就可以根据 sccb总线的协议来定义读 然后将时钟信号置 高!这样循环下去直到 8位 有效数据都传输完! 批注[A6]: 写函数了。如下:(记住读写周期不一样的) void sccb_write(u8 IdAddr, u8 SubAddr, u8 data) { down(&bus_lock); sccb_start(); sccb_write_byte(IdAddr); sccb_write_byte(SubAddr); sccb_write_byte(data); sccb_stop(); up (&bus_lock); } u8 sccb_read(u8 IdAddr, u8 SubAddr) { u8 data; down(&bus_lock); sccb_start(); sccb_write_byte(IdAddr); sccb_write_byte(SubAddr); sccb_stop(); 因为我们在这里要 操作寄存器,所以在操作前 都应该先给其上锁!避免操 作过程中芯片的寄存器被修 改! 批注[A7]: 写函数只需要三 相,即设备从地址,内存地 址,要写的数据! 批注[A8]: 读操作则首先是两 相在三相:写设备从地址, 内存地址,然后设备从地址、 读的数据! 批注[A9]: sccb_start(); sccb_write_byte(IdAddr|0x01); data = sccb_read_byte(); sccb_stop(); up(&bus_lock); return data; } 至于 sccb总线的初始化与清除函数就不多讲了! 接着我们来分析 s3c2440_ov9650.c! 同样看其结构图: 首先有一个结构体: static struct ov9650_reg { u8 subaddr; u8 value; }regs[] = {。。。。。。。。。} 该结构体是配置 ov9650芯片寄存器的。这个是厂家提供的,我们无 需多管,只要知道它的变量是子地址与对应的值! 然后有打开与关闭 0v9650芯片的函数: static void __inline__ ov9650_poweron(void) { s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPIO_OUTPUT); s3c2410_gpio_setpin(S3C2410_GPG(12), 0); mdelay(20); } static void __inline__ ov9650_poweroff(void) { s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPIO_OUTPUT); s3c2410_gpio_setpin(S3C2410_GPG(12), 1); mdelay(20); } 然后就用到我们之前定义的 sccb总线的函数了: static int __inline__ ov9650_check(void) { u32 mid; mid = sccb_read(OV9650_SCCB_ADDR, 0x1c)<<8; mid |= sccb_read(OV9650_SCCB_ADDR, 0x1d); printk("SCCB address 0x%02X, manufacture ID 0x%04X, expect 0x%04X\n", OV9650_SCCB_ADDR, mid, OV9650_MANUFACT_ID); return (mid==OV9650_MANUFACT_ID)?1:0; } 然后再读取该产品的 ID号: static u32 __inline__ show_ov9650_product_id(void) { u32 pid; pid = sccb_read(OV9650_SCCB_ADDR, 0x0a)<<8; pid |= sccb_read(OV9650_SCCB_ADDR, 0x0b); printk("SCCB address 0x%02X, product ID 0x%04X, expect 0x%04X\n", OV9650_SCCB_ADDR, pid, OV9650_PRODUCT_ID); OV9650_SCCB_ ADDR定义为设备从地址: 0x60!OV9650 有两个只读寄 存器 —— 0x1C 和 0x1D , 用于存放厂家 ID,数据分别 0x7F和 0xA2 。 该函数是检测从 ov9650芯读 回来的制造厂商的 ID与我们 定义的是否一样,以决定是 否调用该驱动! 批注[A10]: return pid; } 读取一切正确后我们来配置寄存器,将刚才定义的结构体里的值依据 前面的地址一次写进寄存器中,实现 OV9650 intialization parameter table for SXGA(1024*1280) application : static void ov9650_init_regs(void) { int i; down(®s_mutex); for (i=0; i标准
的函数 可以调用,因此在这里就不过多介绍。 前面已经介绍过,摄像接口都是通过 DMA 实现数据交换的。在 DMA 数据传递中,还要让 DMA 知道如何进行传递,即一次传输多 少个字节,这需要设置预览 DMA 控制相关寄存器 CIPRCTRL 的主 突发长度和剩余突发长度,这两个值也可以通过调用标准函数来求 得。另外在完成每一帧视频采集后,会触发一个视频中断。 接着有初始化了一个缓冲结构体,开辟四块内存: struct s3c2440camif_buffer img_buff[] = { { .state = CAMIF_BUFF_INVALID, .img_size = 0, .order = 0, .virt_base = (unsigned long)NULL, .phy_base = (unsigned long)NULL }, { .state = CAMIF_BUFF_INVALID, s3c2440 能够在 内存中各开辟四块乒乓存储 区域,用于实现 P 通道和 C 通道的快速数据传递。在 P 通道中,寄存 器 CIPRCLRSA1 、 CIPRCLRSA2 、 CIPRCLRSA3 和 CIPRCLRSA4 分别用于表 示这四块内存的首地址! 批注[A11]: 给每块内存先提 供一个状态,其中状态可以 有:enum{ CAMIF_BUFF_INVALID = 0, CAMIF_BUFF_RGB565 = 1, CAMIF_BUFF_RGB24 = 2, CAMIF_BUFF_YCbCr420 = 3, CAMIF_BUFF_YCbCr422 = 4 };然后大小初始化为 0, 物理与虚拟地址待定,这待 会通过系统直接赋值! 批注[A12]: .img_size = 0, .order = 0, .virt_base = (unsigned long)NULL, .phy_base = (unsigned long)NULL }, { .state = CAMIF_BUFF_INVALID, .img_size = 0, .order = 0, .virt_base = (unsigned long)NULL, .phy_base = (unsigned long)NULL }, { .state = CAMIF_BUFF_INVALID, .img_size = 0, .order = 0, .virt_base = (unsigned long)NULL, .phy_base = (unsigned long)NULL } }; 现在我们开始从驱动模块的最开始执行的函数讲起: static int __init camif_init(void) { int ret; struct s3c2440camif_dev * pdev; struct clk * camif_upll_clk; printk(KERN_ALERT"initializing s3c2440 camera interface......\n"); pdev = &camera; /* set gpio-j to camera mode. */ s3c2410_gpio_cfgpin(S3C2440_GPJ0, S3C2440_GPJ0_CAMDATA0); ................. /* init camera's virtual memory. */ if (!request_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF, CARD_NAME)) { ret = -EBUSY; goto error1; } 配置 GPIO口的 J 部分引脚为摄像头模式! 批注[A13]: 给摄像头分配并 初始化虚拟内存,地址从 0x4F000000开始并大小 1M! 批注[A14]: /* remap the virtual memory. */ camif_base_addr = (unsigned long)ioremap_nocache((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF); if (camif_base_addr == (unsigned long)NULL) { ret = -EBUSY; goto error2; } /* init camera clock. */ pdev->clk = clk_get(NULL, "camif"); if (IS_ERR(pdev->clk)) { ret = -ENOENT; goto error3; } clk_enable(pdev->clk); camif_upll_clk = clk_get(NULL, "camif-upll"); clk_set_rate(camif_upll_clk, 24000000); mdelay(100); 重映射虚拟内存! 返回映射后的地址! 批注[A15]: 这里初始化外设 的时钟,并将该外设的时钟 插入到外设时钟链表里,以 后有用到这时钟时仅需更根 据后面的 ID即“camif”来辨 认!然后 clk_enable(pdev->clk);便使能 了时钟! 批注[A16]: 不知道怎么计算 出这个 24MHz的? 批注[A17]: /* init reference counter and its mutex. */ mutex_init(&pdev->rcmutex); pdev->rc = 0; /* init image input source. */ pdev->input = 0; /* init camif state and its lock. */ pdev->state = CAMIF_STATE_FREE; /* init command code, command lock and the command wait queue. */ pdev->cmdcode = CAMIF_CMD_NONE; init_waitqueue_head(&pdev->cmdqueue); /* register to videodev layer. */ if (misc_register(&misc) < 0) { ret = -EBUSY; goto error4; } printk(KERN_ALERT"s3c2440 camif init done\n"); 接下去初始化结 构体,包括初始化互斥锁、 输入格式,状态、命令等! 批注[A18]: 然后将该设备注 册为混杂设备!其中的行为 只有三个函数: static struct file_operations camif_fops = { .owner = THIS_MODULE, .open = camif_open, .release = camif_release, .read = camif_read, }; 批注[A19]: sccb_init(); hw_reset_camif(); has_ov9650 = s3c2440_ov9650_init() >= 0; s3c2410_gpio_setpin(S3C2410_GPG(4), 1); return 0; error4: clk_put(pdev->clk); error3: iounmap((void *)camif_base_addr); error2: release_mem_region((unsigned long)S3C2440_PA_CAMIF, S3C2440_SZ_CAMIF); error1: return ret; } 清除函数就不详细解析了!都是些释放内存,注销注册之类的函数! 现在先讲一下 open函数! static int camif_open(struct inode *inode, struct file *file) { struct s3c2440camif_dev *pdev; SCCB总线初始 化! 批注[A20]: 硬件复位摄像头!批注[A21]: 初始化 ov9650芯 片! 批注[A22]: 这条语句有什么 用?待解? 批注[A23]: struct s3c2440camif_fh *fh; int ret; if (!has_ov9650) { return -ENODEV; } pdev = &camera; fh = kzalloc(sizeof(*fh),GFP_KERNEL); // alloc memory for filehandle if (NULL == fh) { return -ENOMEM; } fh->dev = pdev; pdev->state = CAMIF_STATE_READY; init_camif_config(fh); 注意这个结构体! 内核中注释为打开文件操作 的结构体!其中的变量除了 s3c2440camif_dev,还有 master变量,这个变量内核注 释为:操作标志,仅仅操作 那些能执行“set”控制的文 件! 批注[A24]: 这个是 ov9650初 始化函数返回的值,如果没 有则为 0! 批注[A25]: 用 kzalloc申请 内存的时候, 效果等同于先 是用 kmalloc() 申请空间 , 然后用 memset() 来初始 化 ,所有申请的元素都被初 始化为 0. 批注[A26]: 现在可以将摄像 头状态置为已经准备好了! 批注[A27]: 配置摄像头,其实 是填充 s3c2440camif_dev结 构体,比如将原视频尺寸设 为 1024*1280,即 SXGA的 标准!还有编码目标的尺寸 和预览目标的尺寸! 批注[A28]: ret = init_image_buffer(); // init image buffer. if (ret < 0) { goto error1; } request_irq(IRQ_S3C2440_CAM_C, on_camif_irq_c, IRQF_DISABLED, "CAM_C", pdev); // setup ISRs if (ret < 0) { goto error2; } request_irq(IRQ_S3C2440_CAM_P, on_camif_irq_p, IRQF_DISABLED, "CAM_P", pdev); if (ret < 0) { goto error3; } clk_enable(pdev->clk); // and enable camif clock. soft_reset_camif(); 初始化视频缓冲 区!该函数首先计算出编码 通道与预览通道需要的大 小,然后取其最大的!接着 用到一个函数 get_order,这 个函数是用于当你用字节表 示内存大小时!然后填充缓 冲区的变量,有四个,比如 开辟的内存区 1中有: img_buff[0].order 、 img_buff[0].virt_base 、 img_buff[0].phy_base等, 就是物理地址、虚拟地址、 还有 order参数,这个在获 得分配的页内存函数 __get_free_pages中用到! 批注[A29]: 之后注册两个重 要的中断函数,并初始化时 钟和软复位摄像头! 批注[A30]: file->private_data = fh; fh->dev = pdev; update_camif_config(fh, 0); return 0; error3: free_irq(IRQ_S3C2440_CAM_C, pdev); error2: free_image_buffer(); error1: kfree(fh); return ret; } 然后是读函数: static ssize_t camif_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { int i; struct s3c2440camif_fh * fh; struct s3c2440camif_dev * pdev; 在这里将 fh作为 私有变量赋给 file结构体,以 后可能会用到! 批注[A31]: 记得再次更新配 置寄存器否则要是刚才的寄 存器遭到修改的话,结果就 与我们要求的相悖了! 批注[A32]: fh = file->private_data; pdev = fh->dev; if (start_capture(pdev, 0) != 0) { return -ERESTARTSYS; } disable_irq(IRQ_S3C2440_CAM_C); disable_irq(IRQ_S3C2440_CAM_P); for (i = 0; i < 4; i++) { if (img_buff[i].state != CAMIF_BUFF_INVALID) { copy_to_user(data, (void *)img_buff[i].virt_base, count); img_buff[i].state = CAMIF_BUFF_INVALID; } } enable_irq(IRQ_S3C2440_CAM_P); enable_irq(IRQ_S3C2440_CAM_C); 刚才讲到的私有 数据现在用到了!这里为什 么要这样做,待会再解释! 批注[A33]: 开始采集图像传 到用户空间! 批注[A34]: 关闭中断!批注[A35]: 将所得的数据在 内存的四个区域依次拷贝到 用户空间里!然后将内存区 域重新置为不可用状态! 批注[A36]: 接着在使能中断, 读取下一帧数据! 批注[A37]: return count; } 对于 order变量还有以下知识: order 是你在请求的或释放的页数的以 2 为底的对数(即, log2N). 例 如, 如果你要一个页 order 为 0, 如果你请求 8 页就是 3. 如果 order 太大(没有那个大小的连续区可用), 页分配失败. get_order 函 数, 它使用一个整数参数, 可以用来从一个 size 中提取 order(它必 须是 2 的幂)给主机平台. order 允许的最大值是 10 或者 11 (对应 于 1024 或者 2048 页), 依赖于体系. 但是, 一个 order-10 的分配 在除了一个刚刚启动的有很多内存的系统中成功的机会是小的. 接着是释放函数,没什么好讲的,注意释放内存就是。现在重点讲解 中断函数以及采集视频函数! 我们这里拿预览中断函数来做例子! static irqreturn_t on_camif_irq_p(int irq, void * dev) { u32 ciprstatus; u32 frame; struct s3c2440camif_dev * pdev; ciprstatus = ioread32(S3C244X_CIPRSTATUS); if ((ciprstatus & (1<<21))== 0) CIPRSTATUS反 映 P通道数据的写入状态, 即 4组缓冲的哪一组。用户 程序采集图像数据时,应根 据状态寄存器当前状态,决 定从哪一组读出数据。 批注[A38]: { return IRQ_RETVAL(IRQ_NONE); } pdev = (struct s3c2440camif_dev *)dev; /* valid img_buff[x] just DMAed. */ frame = (ciprstatus&(3<<26))>>26; frame = (frame+4-1)%4; img_buff[frame].state = CAMIF_BUFF_RGB565; if (pdev->cmdcode & CAMIF_CMD_STOP) { stop_capture(pdev); pdev->state = CAMIF_STATE_READY; } else { if (pdev->cmdcode & CAMIF_CMD_P2C) { camif_c2p(pdev); 从这个计算 计算出哪组可以读出数据! 为什么这么算请参考 S3C2440的技术手册第 542 页! 批注[A39]: 将该组的状态置 为可以读出 RGB565格式的 数据! 批注[A40]: 接下去的就是查 看有什么动作要做,比如是 不是停止采集了还是有预览 模式转为编码模式之类的判 断! 批注[A41]: } if (pdev->cmdcode & CAMIF_CMD_WND) { update_target_wnd_regs(pdev); } if (pdev->cmdcode & CAMIF_CMD_TFMT) { update_target_fmt_regs(pdev); } if (pdev->cmdcode & CAMIF_CMD_ZOOM) { update_target_zoom_regs(pdev); } invalid_image_buffer(); } pdev->cmdcode = CAMIF_CMD_NONE; wake_up(&pdev->cmdqueue); 指所有的缓冲区 状态为不可用! 批注[A42]: 唤醒等待队列!批注[A43]: return IRQ_RETVAL(IRQ_HANDLED); } 编码通道的中断函数也与此类似! 接下去讲一下数据采集函数: static int start_capture(struct s3c2440camif_dev * pdev, int stream) { int ret; u32 ciwdofst; u32 ciprscctrl; u32 ciimgcpt; ciwdofst = ioread32(S3C244X_CIWDOFST); ciwdofst |= (1<<30) // Clear the overflow indication flag of input CODEC FIFO Y |(1<<15) // Clear the overflow indication flag of input CODEC FIFO Cb |(1<<14) // Clear the overflow indication flag of input CODEC FIFO Cr |(1<<13) // Clear the overflow indication flag of input PREVIEW FIFO Cb |(1<<12);// Clear the overflow indication flag of input PREVIEW CIWDOFST:从输 入信号中截取中心部分的图 像寄存器 原理是:从输入信号中 截取中心部分的图像输出到 大小不变的输出图像缓冲 中,从而实现对图像的放大 或缩小。 ①配置该寄存器允许缩放或 不允许缩放。 ②X方向图像放大或缩小控 制,原理是截切掉左部和右 部的图像像素数。 ③Y方向图像放大或缩小控 制,原理是截切掉上部和下 部的图像像素数。 批注[A44]: FIFO Cr iowrite32(ciwdofst, S3C244X_CIWDOFST); ciprscctrl = ioread32(S3C244X_CIPRSCCTRL); ciprscctrl |= 1<<15; // preview scaler start iowrite32(ciprscctrl, S3C244X_CIPRSCCTRL); pdev->state = CAMIF_STATE_PREVIEWING; ciimgcpt = (1<<31) // camera interface global capture enable |(1<<29); // capture enable for preview scaler. iowrite32(ciimgcpt, S3C244X_CIIMGCPT); ret = 0; if (stream == 0) { pdev->cmdcode = CAMIF_CMD_STOP; ret = wait_event_interruptible(pdev->cmdqueue, pdev->cmdcode == CAMIF_CMD_NONE); } 预览模式控制寄 存器,第 15位置一代表启动 预览模式! 批注[A45]: 这个寄存器必须 最后设置,是图像采集使能 寄存器,29位是使能预览模 式,31位是使能全局摄像头 接口! 批注[A46]: 该函数的形参,在 read函数中调用到这个函数, 其中 stream是 0,所以会进入 这个 if语句中,然后就等待 中断了!只要中断已发生, 从摄像头采集到数据,就可 以立刻返回,执行到 read中 接下去的程序,将数据读回 到用户空间! 批注[A47]: return ret; } 以上便是 CMOS摄像头驱动的整个编写思路,其中还有一些函数未 能举例出来,大家可以下去好好看看! 小兀 2012年 6月 10号 如有引用请注明出处!
/
本文档为【CMOS摄像头驱动解析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索