我们解析一下 MINI2440 开发板的 CMOS 摄像头的驱动。该驱动总共
包括以下文件:sccb.c、sccb.h、s3c2440_ov9650.c、s3c2440camif.h、
s3c2440camif.c 五个文件!
s3c2440camif.c 用于从 cmos 接口获取图像数据和将数据传输到进程
空间(在有 app 读取时)。
s3c2440_ov9650.c 读取和配置 ov9650 寄存器。通过iic 接口传输数据。
设备地址是 60(#defineOV9650_SCCB_ADDR
0x60).比如进行初
始化和 productid 获取.
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 的功能。
接下来对一些重要的函数讲一讲。
staticvoid__inline__sccb_start(void)
{
CFG_WRITE(SIO_D);
Low(SIO_D);
WAIT_STABLE();
//延时一会,保持一定的低电平,以此代表 sccb
总线开始传输数据
这个函数原型是:
批注[A1]:
#defineCFG_WRITE(x)
do{s3c2410_gpio_cfgpin(x,S3
C2410_GPIO_OUTPUT);smp
_mb();}while(0)。通过这个宏
定义可以看出它是配置
GPE14 为输出管脚。
这个函数的原型
批注[A2]:
是:#defineLow(x)
do{s3c2410_gpio_setpin(x,0)
;smp_mb();}while(0)
可见是输出低电平的功能!
}
值得提出的是内核在对管脚的寄存器配置中用到了一个概念:内存屏
障!即 smp_mb()函数。
什么叫内存屏障?下面给出例子:
#defineset_mb(var,value)do {var=value;mb();}while(0)
#definemb()__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()带有一个 memorybarrier ,set_task_state()肯定是安
全的,但 __set_task_state()可能会快些。
关于 barrier()宏实际上也是优化屏障:
#definebarrier()__asm____volatile__("":::"memory")
CPU 越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句
实际上不生成任何代码,但可使 gcc 在 barrier()之后刷新寄存器对变
量的分配。
不论是 gcc 编译器的优化还是处理器本身采用的大量优化,如 Write
buffer, Lock-up free, Non- blocking reading, Register allocation,
Dynamicscheduling,Multipleissues 等,都可能使得实际执行可能违
反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序
顺序来执行。
使用内存屏障强加的严格的 CPU 内存事件次序,保证程序的执行看
上去象是遵循顺序一致性模型。
接下去的函数是写一个字节到芯片中:
staticvoid__inline__sccb_write_byte(u8data)
{
inti;
CFG_WRITE(SIO_D);
WAIT_STABLE();
/*write8-bitsoctet.*/
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]:
字节。很据时序,首先置时
然后开始传输一个
钟为低电平!然后等待一会!
并行数据转串行数
批注[A5]:
据,我们先传输最高位!将
我们要传输的字节与 0x80 位
与,没位与一次,该字节就
左移一次,以保证每位都与
0x80 都位与过,以得到整个
数据传输图!通过程序可以
看出字节是在时钟低电平时
变化的!这与 IIC 总线协议一
致!
批注[A6]:
然后将时钟信号置
高!这样循环下去直到 8 位
有效数据都传输完!
WAIT_CYCLE();
High(SIO_C);
WAIT_CYCLE();
}
/*writebytedone,waittheDon'tcarebitnow.*/
Low(SIO_C);
High(SIO_D);
CFG_READ(SIO_D);
WAIT_CYCLE();
High(SIO_C);
WAIT_CYCLE();
{
}
}
之后读一个字节函数与此类似!记住这里是串行数据转并行,同样高
位在前!这里的读写都是以 ov9650 芯片为参考物的!
Sccb 总线的 stop 函数与开始类似!
有了上面四个基本函数,我们就可以根据 sccb 总线的协议来定义读
写函数了。如下:(记住读写周期不一样的)
voidsccb_write(u8IdAddr,u8SubAddr,u8data)
{
}
down(&bus_lock);
sccb_start();
sccb_write_byte(IdAddr);
sccb_write_byte(SubAddr);
sccb_write_byte(data);
sccb_stop();
up(&bus_lock);
u8sccb_read(u8IdAddr,u8SubAddr)
{
u8data;
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);
returndata;
}
至于 sccb 总线的初始化与清除函数就不多讲了!
接着我们来分析 s3c2440_ov9650.c!
同样看其结构图:
首先有一个结构体:
staticstructov9650_reg