前言:对于ov9650的学习做一个记录,目标是在我的fl2440的板子上移植ov9650的驱动并实现在lcd上显示摄像头拍摄的内容,虽然最后还是花屏,但是还需要做一个记录,记下自己学习到的一点东西。希望发到网上然后得到广大网友的帮助,如果能对你们学习的过程中有帮助我也是荣幸。
内核:linux-3.0
开发板:fl2440
交叉编译器:arm-linux-gcc 2012.08
下篇地址:http://blog.csdn.net/liuzijiang1123/article/details/48247605
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我用的3.0的内核里面并不自带ov9650的驱动,飞凌公司自带的驱动是2.6.32内核。(由于内核版本差别比较大,所以内核自身的一些api也有变化,编译出现了很多问题)
ps:这个问题其实就是最大的一个问题,找不到合适的驱动嵌入合适的内核中,最后失败的原因我估计也是跟这个问题有一定联系的。
自己解决的办法:
a.首先肯定是重新编译它的驱动,由于版本差别过大很多api都不一样了,改了一些错误,后面出现的错误也越来越多,导致放弃,后面直接找了2.6.32的内核
经行编译,运行内核的时候总是卡在一个地方进不去,这个待解决。
b.网上查询关于ov9650的驱动源码,各个网站留言求代码,得都的一些代码( ov9650近几年才开源,所以网上相关文章并不是很多),可是平台不一样,有一些自己也看不懂,最后决定用天嵌公司的ov9650的源码,进行移植。
对于驱动的移植有两种方法,一驱动集成到内核,二是生产.ko文件,在内核运行后再加载进去。经后面的对于花屏的测试的经验,还是第二种方法好,生产.ko文件,不用反复编译内核,一直擦出nand flash容易损坏。
ps:下面我就把内核移植的过程贴一下给新手看看。
一共6个文件:s3c2440camif.c s3c2440camif.h s3c2440_ov9650.c sccb.c sccb.h Makefile
第一种方法不适合代码调试的阶段,但是也是有用的,所以得学会。
首先还是将这6个文件放在一起文件夹cmos130下
将这个文件夹放在 ./drivers/media/video/cmos130/
然后在video这个目录下的Kconfig文件中添加ov9650的选项,因为你make menuconfig进行配置的适合需要读取Kconfig
---------------------------------------------------------------------------------------------------------------------------------------------------------------
config VIDEO_OV9650
tristate "OmniVision OV9650/OV9652 sensor support"
depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
---help---
This is a V4L2 sensor-level driver for the Omnivision
OV9650 and OV9652 camera sensors.
source "drivers/media/video/cmos130/Kconfig"
--------------------------------------------------------------------------------------------------------------------------------------
ps:---tristate 代表三种状态:1.[ ]不选择,2.[*]选择直接编译进内核,加载驱动到内核里,3.[m]动态加载驱动;
denpends on 后面的是它所依赖的,必须选上它们才能出现* 或者是出现个选项
在video下的Makefile中添加:
***********************************************************************************************************************
obj-$(CONFIG_S3C2440_CMOS_CAMERA130) += cmos130/ (进去coms130这个文件中寻找.o文件)
***********************************************************************************************************************
进去cmos130,vim Kconfig
***********************************************************************************************************************
menu CMOS_Camera_syd168
config S3C2440_CMOS_CAMERA130
tristate "OV9650 on the S3C2440 driver for SMDK2440"
depends on VIDEO_DEV && SMDK2440_CPU2440
default y if (VIDEO_DEV && SMDK2440_CPU2440)
endmenu
************************************************************************************************************************
然后你就可以make menuconfig,选上ov9650的驱动了。
下面是图解:
下面是Makefile 中的内容:
s3c2440camera-objs := s3c2440_ov9650.o sccb.o s3c2440camif.o obj-$(CONFIG_S3C2440_CMOS_CAMERA130) += s3c2440camera.o
第二种方法:
这个方法只需要改变Makefile中的内容即可
ARCH=arm920t CROSS_COMPILE ?=/opt/buildroot-2011.11/${ARCH}/usr/bin/arm-linux- PWD := $(shell pwd) KERNELDIR ?= ${PWD}/../fl2440/kernel/linux-3.0/ obj-m := s3c2440camera.o s3c2440camera-objs :=s3c2440_ov9650.o sccb.o s3c2440camif.o default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules @make clear clear: @rm -f *.o *.cmd *.mod.c @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f @rm -f .*ko.* *ko.* .*.o.cmd clean: rm -f *.ko
make 后即可生成.ko文件,然后insmod s3c2440camera.ko进内核就行 卸载的时候是rmmod s3c2440camera
这个是学习中最精华的部分。
首先我们需要有一定的基础知识,下面是根据我的理解给出的需要理解的一些基础知识和概念。
sccb是OmniVision 公司定制的串行摄像头控制总线(Serial Camera Control Bus) ,它用于对摄像头的寄存器进行读写,以达到对摄像头输出图像的控制。
两线SCCB接口有两条通迅连接线,即SIO_D(数据线)和 SIO_C(时钟线),下面是双总线功能原理图:
数据都是在时钟线高电平的时候有效,起始是数据线的时钟从高变低,终止是数据线时钟从低到高。
它与i2c的协议大致相同,不过有一些细微的差别:
从机地址因为I2C是7位地址,最后一位是读写位,而SCCB是8位地址,比如ov9650,它是SCCB协议,他的地址是0x60,那么如果挂接到I2C总线上,他的地址就变成0x30了,这样算的:
SCCB地址::: 0x60: 0 1 1 0_0 0 0 0 这个0还是地址位
I2C地址 ::: 0x30: 0 1 1 0_0 0 0 <0>最后的0是读写位,那么地址变成了7 位 +读写位 即 0 1 1_ 0 0 0 0 +0( 读写位 ) 所以从机地址变成了0x30
如果你用i2c协议的话那么地址就要写成0x30,如果用gpio模拟sccb的话地址就要写成0x60,天嵌他们的代码是用后者。
在传输过程中它们需要检测厂商ID是否正确也就是从机地址,如果正确则进行数据传输。
在sccb.c中具体实现了sccb的初始化和操作。
ps:模拟的时候用gpe14 gpe15
这些是需要配置的pin脚:
我们看一下关于sccb的相关函数,在sccb.c中
sccb开始函数
static void __inline__ sccb_start(void) { CFG_WRITE(SIO_D);//设置“i2c”为输出模式 Low(SIO_D); //设置低电平 WAIT_STABLE(); //延迟10us }
sccb写一个字节函数
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++)<span style="line-height: 21px; background-color: rgb(242, 243, 245);"><span style="font-family:SimSun;font-size:10px;"> //并行数据转串行输出,串行数据输出的顺序为先高位再低位</span></span> { Low(SIO_C); WAIT_STABLE(); if (data & 0x80) //从高位开始写 { High(SIO_D); } else { Low(SIO_D); } data = data<<1; WAIT_CYCLE(); High(SIO_C); WAIT_CYCLE(); } /* write byte done, wait the Don't care bit now. */ //第九位don not care 位 Low(SIO_C); High(SIO_D); CFG_READ(SIO_D); WAIT_CYCLE(); High(SIO_C); WAIT_CYCLE(); }
<pre name="code" class="cpp">static u8 __inline__ sccb_read_byte(void) { int i; u8 data; CFG_READ(SIO_D); WAIT_STABLE(); Low(SIO_C); WAIT_CYCLE(); data = 0; for (i=0;i<8;i++) { High(SIO_C); WAIT_STABLE(); data = data<<1; data |= State(SIO_D)?1:0; WAIT_CYCLE(); Low(SIO_C); WAIT_CYCLE(); } /* read byte down, write the NA bit now.*/ CFG_WRITE(SIO_D); High(SIO_D); WAIT_CYCLE(); High(SIO_C); WAIT_CYCLE(); return data; }
static void __inline__ sccb_stop(void) { Low(SIO_C); WAIT_STABLE(); CFG_WRITE(SIO_D); Low(SIO_D); WAIT_CYCLE(); High(SIO_C); WAIT_STABLE(); High(SIO_D); WAIT_CYCLE(); CFG_READ(SIO_D); }
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);
</pre><pre name="code" class="cpp">
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);
</pre><pre name="code" class="cpp">
sccb_stop(); sccb_start(); sccb_write_byte(IdAddr|0x01); data = sccb_read_byte(); sccb_stop(); up(&bus_lock); return data; }
int sccb_init(void) { CFG_WRITE(SIO_C); CFG_WRITE(SIO_D); High(SIO_C); High(SIO_D); WAIT_STABLE(); return 0; }
对于驱动程序的追踪和理解可以参考这个博客:http://blog.csdn.net/mirkerson/article/details/8170227
RGB24,I420是目前最常用的两种图像格式。RGB24:表示R、G、B三种颜色各8bit,最多可表现色。
I420:YUV格式之一。
所谓分辨率就是指画面的解析度,由多少象素构成的数值越大,图像也就越清晰。分辨率不仅与显示尺寸有关,还会受到显像管点距、视频带宽等因素的影 响。我们通常所看到的分辨率都以乘法形式表现的,比如1024*768,其中的1024表示屏幕上水平方向显示的点数,768表示垂直方向的点数。
QXGA (2048 X 1536)又称300万像素
UXGA (1600X 1200)又称200万像素
SXGA(1280 x1024)又称130万像素
XGA(1024 x768)又称80万像素
SVGA(800 x600)又称50万像素
VGA(640x480)又称30万像素(35万是指648X488)
CIF(352x288) 又称10万像素
SIF/QVGA(320x240)
QCIF(176x144)
QSIF/QQVGA(160x120)
(joint photographic expert group)静态图像压缩方式。一种有损图像的压缩方式。压缩比越大,图像质量也就越差。当图像精度要求不高存储空间有限时,可以选择这种格式。目前大部分数码相机都使用JPEG格式。
反映对色彩的识别能力和成像的色彩表现能力,就是用多少位的二进制数字来记录三种原色。实际就是A/D转换器的量化精度,是指将信号分成多少个等级,常用 色彩位数(bit)表示。彩色深度越高,获得的影像色彩就越艳丽动人。非专业的SENSOR一般是24位;专业型SENSOR至少是36位。24位的 SENSOR,感光单元能记录的光亮度值最多有2^8=256级,每一种原色用一个8位的二进制数字来记录,最多记录的色彩是256×256×256约 16,77万种。
36位的SENSOR,感光单元能记录的光亮度值最多有2^12=4096级,每一种原色用一个12位的二进制数字来记录,最多记录的色彩是4096×4096×4096约68.7亿种。
PLL
S3C2440 CPU主频可达400MHz,开发板上的外接晶振为12M,通过时钟控制逻辑的PLL(phase locked loop,锁相环电路)来倍频这个系统时钟。2440有两个PLL(phase locked loop)一个是MPLL,一个是UPLL。UPLL专用于USB设备,常用频率为48MHz和96MHz。MPLL用于CPU及其他外围器件,用于产生FCLK, HCLK, PCLK三种频率,上电时,PLL并没有被启动,FCLK=Fin=12MHz,若要提高系统时钟,需要软件来启动PLL。
1,FCLK是CPU提供的时钟信号。
2,HCLK是为AHB总线提供的时钟信号, Advanced High-performance Bus,主要用于高速外设,比如内存控制器,中断控制器,LCD控制器, DMA 等。
3,PCLK是为APB总线提供的时钟信号,Advanced Peripherals Bus,主要用于低速外设,比如看门狗,UART控制器, IIS, I2C, SDI/MMC, GPIO,RTC and SPI等。
我们需要通过XVCLK1给摄像头提供时钟,RESET是复位线,PWDN在摄像头工作时应该始终为低。HREF是行参考信号,PCLK是像素时钟,VSYNC是场同步信号。一旦给摄像头提供了时钟,并且复位摄像头,摄像头就开始工作了,通过HREF,PCLK和VSYNC同步传输数字图像信号。数据是通过D0~D7这八根数据线并行送出的。
http://blog.csdn.net/mirkerson/article/details/8192190
网上说这2个的概念很模糊,我也百度了很久也不是很清楚,反正以我的理解就是,摄像头接口只是一个人为的说法,只是一个概念,相当于cpu和ov9650的中间人,ov9650将采集到的数据通过摄像头控制器处理后再传给cpu来进行处理。
CAMIF有2个DMA管道。P-path(预览管道)和C-path(编码管道)被分开接AHB总线上。从系统总线的角度看,这两条管道是独立的。P-path储存RGB格式的图片数据到内存中,以便生成预览。C-path保存YCbCr4:2:0或4:2:2图片数据到内存中,以便编码成MPEG-4,H.263等视频格式。
Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。
而摄像头所用的主要是capature了,视频的捕捉。
应用程序通过V4L2接口采集视频数据分为五个步骤:
首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
第五,停止视频采集。
具体的程序实现流程可以参考下面的流程图:
我的个人理解为v4l2是提供了一系列的camera功能的api,在驱动中加入它的ioctl然后可以在应用层进行调用,它已经封装好的api,这样对于我们获得视频的信息就方便很多,不用我们自己再去写函数去获得视频的信息
http://blog.csdn.net/rubyboss/article/details/17410677这个博客写到了v4l2用户空间和内核空间是如何交互的