OMAP 平台下嵌入式Linux 系统摄像头驱动设计和实现
摘要:
本文在介绍OMAP5910 摄像头接口及OV COMS 摄像头的基础上,分析了Linux 系统下设备驱动程序及其开发,并详细阐述和实现了基于OMAP5910 的摄像头驱动。
关键词:OMAP ARM-Linux 系统摄像头驱动
为了满足3G移动通信和多媒体领域新应用的需要,德州仪器公司提出了OMAP(OpenMultimedia Applications Platform,开放式多媒体应用平台)体系结构,一种由高性能的DSP核与ARM核结合而成的低功耗双核结构,成为一颗高度整合性SOC。OMAP芯片具有丰富的外围接口[1],使其特别适用于新一代手持设备的开发。本文讨论的是OMAP5910平台及嵌入式ARM-Linux系统下OV CMOS摄像头驱动的设计与实现。
1 OMAP5910 摄像头接口及OV 摄像头介绍
1.1 OMAP5910 摄像头接口
如图 1 所示,OMAP5910 摄像头接口提供以下四个功能[2]:(1)FIFO 缓冲器,包含128个字的FIFO,用于缓存从摄像头模块获取的数据,并通过DMA 方式或CPU 方式将数据传给MPU 公共外设总线(TIPB)。由4 个连续的8 位数据组合成一个32 位数据,以充分利用TIPB 总线宽度;(2)分频器,该功能模块用于管理时钟分割及处理用于同步/异步模式的外部时钟产生;(3)中断产生器,用于当一帧的起始和结束,一行的起始和结束以及FIFO 溢出时中断的产生;(4)TIPB 寄存器,包括了该接口的状态,控制及数据寄存器。
摄像头接口与32 位TIPB 相连,从外部摄像头接收8 位并行数据以及水平/垂直同步信号。在驱动程序中通过对摄像头接口包含的7 个寄存器的设置,实现TIPB 和摄像头的通信。
1.2 OV 摄像头介绍
OmniVision 公司的CMOS sensor 目前广泛应用于便携式手持设备应用中,其特点是[3]:灵敏度高、低电压工作、标准串行摄像头控制总线(SCCB)控制、可输出窗口尺寸可调的UXGA/SVGA/VGA/QVGA 等不同象素的图像,且图像格式可以是原始RGB/RGB(4:2:2)/YUV(4:2:2)/YCbCr(4:2:2)、图像处理可编程等。
用户可根据应用的不同选择不同型号的sensor。在驱动的实现中,主要是通过SCCB 接口设置sensor 里的设备控制寄存器以获取所需质量图像。一般来讲,对关键几个寄存器进行设置就可获得不错质量的图像。其设置流程为: 复位所有寄存器为缺省值, 对HSYNC/VSYNC 信号的极性进行设置,选择图像输出格式(YUV/RGB)、象素及HREF 极性,设置图像有效窗口的大小,对图像扫描帧率进行微调等。当然,要想获得高质量的图像,应对sensor 的所有控制寄存器进行设置和调试,才能使摄像头工作在最佳状态。
2 Linux 环境下设备驱动程序
与 Unix 系统一样,在Linux 系统里,对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口。一般来说是把设备映射为一个特殊的设备文件,用户程序可以像对其它文件一样对此设备文件进行操作。编写内核驱动程序,并不像其他操作系统那么复杂。实际上,只是为相应的设备编写几个基本函数,并向VFS(Virtual File System)注册,就可以达到目的。当应用程序需要使用设备时,VFS 会调用该设备函数。
2.1 设备驱动程序基本概念
按照类型分类,Linux 的设备可以分为三类:即字符设备、块设备和网络设备。对于用户来说,字符设备和块设备的访问接口都是一组基于文件系统的调用,其实现的区别主要在二者的内核和驱动程序的软件接口上,本文所讨论的摄像头其实质属于字符设备。
在一个运行的Linux 系统中,可以通过文件/pro/devices 查看当前所使用的设备。
除了设备类型以外,内核还需要一对主、次设备号的参数,才能唯一标识设备。内核利用主设备号将设备与所相应的驱动程序对应起来,次设备号只能由设备驱动程序使用,内核的其他部分不使用它,仅将它作为参数传递给驱动程序。一个驱动程序可以控制若干个设备,而次设备号,提供了一种区分它们的标识。
I/O 子系统通过数据结构file_operations(见include/Linux/fs.h)向其内核的其他部分提供了一个统一的标准设备接口。当应用程序对设备文件进行诸如open/close, read/ write 操作时,内核使用file_operations 结构访问驱动程序的函数。也就是说,如果应用程序对设备执行操作,内核将执行file_operations 结构中的程序。因此,从某种意义上说,写驱动程序的任务之一就是完成file_operations 结构中的函数指针。
2.2 设备驱动程序的框架
Linux 的设备驱动程序大致可分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。
(1)驱动程序的注册与注销向系统增加一个驱动程序意味着要赋予它一个主设备号,这一赋值过程应该在驱动程序(模块)的初始化中完成,它调用函数register_chrdev()或register_blkdev()向内核注册,这个函数定义在<Linux/fs.h>。接下来就是给程序一个设备驱动程序名,这个名字必须插入到目录中,并与驱动程序的主设备号和次设备号相连。在关闭字符设备或块设备时,还需要通过 unregister_chrdev()或unregister_blkdev()从内核中注销设备,并释放主设备号。
(2)设备的打开与释放打开设备是由open()完成的,open 子程序必须为将要进行的I/O 操作做好必要的准备工作。释放设备是由release()完成的,release 方法的作用正好与open 相反,这个设备方法有时也称为close。
(3)设备的读写操作字符设备使用各自的read()和write()来对设备进行数据读写。块设备使用通用blcok_read()和block_write()来进行数据读写。
(4)设备的控制操作除了读写操作外,有时还需要控制设备,可以通过设备驱动程序中的ioctl()来完成。ioctl()的用法与具体设备密切相关,对具体应用需具体分析。
(5)设备的中断和轮询处理对于不支持中断的设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可按中断方式进行。
3 基于OMAP5910 的摄像头驱动的实现
3.1 驱动实现背景
对于嵌入式开发,没有通用的驱动程序。在OMAP 构架下实现摄像头的应用工作,是通过摄像头获取视频数据送入OMAP 的DSP 进行处理,如MPEG-4 压缩,然后在ARM 端提取处理后的数据。ARM 端应用程序与摄像头驱动及DSP 驱动的交互如图2 所示。
摄像头驱动程序除了实现设备的注册注销外,按照功能分主要还实现了摄像头打开模块、摄像头控制模块(初始化在该模块中实现)和摄像头关闭模块。在ARM 端的应用程序中,首先调用open()打开摄像头,然后调用ioctl()进行摄像头参数设置及一系列初始化。程序运行完毕, 关闭摄像头。程序使用register_chrdev(CAMERA_MAJOR, "camera",&camera_fops)向系统注册,&camera_fops 是一个指向跳转表file_operations 的指针,file_operations 结构如下:
static struct file_operations camera_fops = {owner: THIS_MODULE,open: camera_open,ioctl: camera_ioctl,release: camera_release};下面就驱动的各个模块做具体分析介绍。
3.2 摄像头打开模块
camera_open()函数如下:
static int camera_open(struct inode *inode, struct file *file){if(dsp55x_work_mem_base == 0){ //检测DSP 工作内存是否分配printk("camera_open: DSP work memory has not been allocated.n");return -1;}
ImgBufBase = dsp55x_work_mem_base; //设置图像缓存的地址,即DSP 的工作内存首址…return 0;}
3.3 摄像头控制模块
用户程序调用ioctl()函数进行初始化设置,调用InitCamera()实现摄像头接口以及摄像头模块的初始化,为本驱动程序的主要部分。在正确执行这个函数后,摄像头就可以正常工作起来,开始采集数据。InitCamera()函数如下:
int InitCamera(void){if(request_irq(INT_CAMERA, camera_interrupt, 0, "camera",&Omap5910Camera))printk("camif_init: request camera irq failed.n");//摄像头中断请求InitI2c(); //初始化I2C 总线,用以向摄像头模块寄存器赋值camif_init(); //摄像头接口的初始化camera_start() ; //启动摄像头CameraI2CSettings(); //通过I2C 总线设置摄像头模块的寄存器以获取所需质量图像WaitForVsync(); //设置VSYN 信号上升沿还是下降沿同步if(CameraDmaConfigure() == -1){ //请求分配一个OMAP 的DMA 通道printk("CameraDmaConfigure Failure! n");return -1;}
dma_setupcamif(); //启动DMA 进行数据传送return 0;}
以下分别详细描述关键初始化过程:
(1)中断请求
调用 request_irq(INT_CAMERA, camera_interrupt, 0, "camera",&Omap5910Camera)请求中断,执行camera_interrupt()这个中断处理子程序。通过读取OMAP 摄像头接口中断状态寄存器(IT_STATUS)获取帧起始中断。
(2)OMAP 摄像头接口的初始化
设置寄存器ARM_RSTCT2 复位外设,设置寄存器ARM_IDLECT2 使能外设时钟,然后设置寄存器CAMIF_GPIO 使摄像头不再处于复位状态,最后分别按应用要求分别设置寄存器CAMIF_MODE 以及寄存器CAMIF_CTRLCLOCK。
(3)摄像头启动
写寄存器CAMIF_CTRLCLOCK,设置bit4 CAMEXCLK_EN 为1,OMAP 摄像头接口内部12MHz 时钟源输出CAM.EXCLK 信号给摄像头,从而启动摄像头。
(4)OV 摄像头寄存器设置
通过函数char WriteI2c(unsigned char device, unsigned char subaddr, unsigned chardata)对摄像头寄存器进行写操作。其中device 为写地址,值得一提的是OMAP 采用的I2C 总线和OV 公司的SCCB 总线有些不同,在读写地址上的不同表现在I2C 总线地址取SCCB 地址的前7 位。比如,OV7649 SCCB 写地址为0x42,但是I2C 总线写地址为0x21。subaddr 为寄存器的偏移地址,data 为设置值。例如,通过OV7649 获取QVGA 图像执行以下操作可获得不错的图像。
WriteI2c(0x21, 0x12, 0x80); //复位所有的寄存器为缺省值WriteI2c(0x21, 0x11, 0x40); //设置HSYNC/VSYNC 极性及PCLK 时钟WriteI2c(0x21, 0x12, 0x54); //选择数据输出格式为U Y V Y U Y V YWriteI2c(0x21, 0x14, 0x24); //选择QVGA(320*240)输出WriteI2c(0x21, 0x15, 0x80); //与寄存器0x12 一起控制数据输出格式WriteI2c(0x21, 0x17, 0x1A); //以下四个寄存器设置HREF 窗口大小,WriteI2c(0x21, 0x18, 0x1A+320/2);//此处设为320*240。
WriteI2c(0x21, 0x19, 0x03);WriteI2c(0x21, 0x1A, 0x03+240);WriteI2c(0x21, 0x2a, 0xA0); //以下两个寄存器用于对摄像头扫描帧率WriteI2c(0x21, 0x2b, 0x7B); //进行微调,此处设为20fps
(5)DMA 通道请求
调 用 系统函数omap_request_dma(eCameraRx, "Camera Rx", (dma_callback_t)DmaInterruptCallBack, &Omap5910CameraData, ®s) , 该函数在archARMmach-omapdma-omap.c 中定义。请求分配一个OMAP 的系统DMA 通道,当有空闲DMA 通道时,返回这个通道的硬件寄存器地址作为通道标识,写入指针®s,然后再初始化系统DMA,即初始化dma_regs_t 型结构体regs。另外,在申请到系统DMA通道后会呼叫执行DmaInterruptCallBack 函数,发出已经扫好一帧视频数据的命令,等待ARM 取这个命令并发给DSP。
(6)DMA 的启动
在函数dma_setupcamif()中对摄像头接口DMA 设置,启动DMA。
static void dma_setupcamif(void){…Omap5910CameraData.dma_regs->cdsa_l=(ImgBufBase+ImgBufSize*ImgBufNum)&0xffff; //设置DMA 数据传送的地址低16 位。DSP 端分配了两块内存用以摄像头交替扫入图像,当ImBufNum 为0 时,为数据存储的第一块空间,当ImBufNum为1 时为数据存储的第二块空间。
Omap5910CameraData.dma_regs->cdsa_u=(ImgBufBase+ImgBufSize*ImgBufNum)>>16; //DMA 数据传送地址的高16 位…Omap5910CameraData.dma_regs->ccr |= 0x0080; //设置ccr 的bit7,启动DMA…}
3.4 摄像头关闭模块
static int camera_release(struct inode *inode, struct file *file){…free_irq(INT_CAMERA, &Omap5910Camera); //释放中断请求if(Omap5910CameraData.dma_regs != NULL){omap_free_dma(Omap5910CameraData.dma_regs); //释放DMA 通道Omap5910CameraData.dma_regs = NULL;}
…}
4 结束语
本驱动在OMAP5910 平台及ARM-Linux 嵌入式系统下已成功实现OV COMS摄像头的应用。在对OV 摄像头寄存器设置的过程中,需对OV 公司提供的datasheet 有较好的理解,这样才能获得高质量的图像。