s3c2440供给了一个摄像接口,使开发人员很容易地完结摄像、照相等功用。摄像接口包含8位来自摄像头的输入数据信号,一个输出主时钟信号,三个来自摄像头的输入同步时钟信号和一个输出复位信号。摄像接口的主时钟信号由USB PLL产生,它的频率为96MHz,再经过分频处理后输出给摄像头,摄像头再依据该时钟信号产生三个同步时钟信号(像素时钟、帧同步时钟和行同步时钟),反过来再输入回s3c2440。
s3c2440只是供给了一个摄像接口,因而要完结其功用,还需求摄像头。在这儿,咱们运用OV9650。OV9650内部有很多的寄存器需求装备,这就需求别的的数据接口。OV9650的数据接口称为SCCB(串行摄像操控总线),它由两条数据线组成:一个是用于传输时钟信号的SIO_C,另一个是用于传输数据信号的SIO_D。SCCB的传输协议与IIC的极端类似,只不过IIC在每传输完一个字节后,接纳数据的一方要发送一位的结语数据,而SCCB一非有必要传输9位数据,前8位为有用数据,而第9位数据在写周期中是Don’t-Care位(即不用关怀位),在读周期中是NA位。SCCB界说数据传输的根本单元为相(phase),即一个相传输一个字节数据。SCCB只包含三种传输周期,即3相写传输周期(三个相顺次为设备从地址,内存地址,所写数据),2相写传输周期(两个相顺次为设备从地址,内存地址)和2相读传输周期(两个相顺次为设备从地址,所读数据)。当需求写操作时,使用3相写传输周期,当需求读操作时,顺次使用2相写传输周期和2相读传输周期。因而SCCB一次只能读或写一个字节。下面咱们就用s3c2440的IIC总线接口别离与OV9650的SIO_C和SIO_D相连接来完结SCCB的功用。详细的读、写函数为:
//装备IIC接口
rGPEUP = 0xc000;//上拉无效
rGPECON = 0xa0000000;//GPE15:IICSDA,GPE14:IICSCL
//IIC中止
void __irq IicISR(void)
{
rSRCPND |= 0x1<<27;
rINTPND |= 0x1<<27;
flag = 0;
}
//写操作
//输入参数别离为要写入的内存地址和数据
void Wr_SCCB(unsigned char wordAddr, unsigned char data)
{
//3相写传输周期
//写OV9650设备从地址字节
flag =1;
rIICDS =0x60;//OV9650设备从地址为0x60
rIICSTAT = 0xf0;
rIICCON &= ~0x10;
while(flag == 1)
delay(100);
//写OV9650内存地址字节
flag = 1;
rIICDS = wordAddr;
rIICCON &= ~0x10;
while(flag)
delay(100);
//写详细的数据字节
flag = 1;
rIICDS = data;
rIICCON &= ~0x10;
while(flag)
delay(100);
rIICSTAT = 0xd0;//中止位
rIICCON = 0xe3;//为下一次数据传输做准备
delay(100);
}
//读操作
//参数别离为要读取的内存地址和数据
void Rd_SCCB (unsigned char wordAddr,unsigned char *data)
{
unsigned char temp;
//2相写传输周期
//写入OV9650设备从地址字节
flag =1;
rIICDS = 0x60;
rIICSTAT = 0xf0;
rIICCON &= ~0x10;
while(flag)
delay(100);
//写入内存地址字节
flag = 1;
rIICDS = wordAddr;
rIICCON &= ~0x10;
while(flag)
delay(100);
rIICSTAT = 0xd0;//中止位
rIICCON = 0xe3;//为下一次数据传输做准备
delay(100);
//2相读传输周期
//写入OV9650设备从地址字节
flag = 1;
rIICDS = 0x60;
rIICSTAT = 0xb0;
rIICCON &= ~0x10;
while (flag)
delay(100);
//读取一个无用字节
flag = 1;
temp = rIICDS;
rIICCON &= ~((1<<7)|(1<<4));
while(flag)
delay(100);
//读取数据
flag = 1;
*data= rIICDS;
rIICCON &= ~((1<<7)|(1<<4));
while(flag)
delay(100);
rIICSTAT = 0x90;//中止位
rIICCON = 0xe3;//为下一次传输做准备
delay(100);
}
当然咱们也能够用两个通用IO口来模仿SCCB总线,下面咱们给出详细的程序,其间GPE15为SIO_D,GPE14为SIO_C。
#define CLOCK_LOW()(rGPEDAT&=(~(1<<14)))//时钟信号低
#define CLOCK_HIGH()(rGPEDAT|=(1<<14))//时钟信号高
#define DATA_LOW()(rGPEDAT&=(~(1<<15)))//数据信号低
#define DATA_HIGH()(rGPEDAT|=(1<<15))//数据信号高
//装备IO
rGPEUP = 0xc000;//上拉无效
rGPECON = 5<<28;//GPE15为SIO_D,GPE14为SIO_C,都为输出
void delay(int a)
{
int k;
for(k=0;k;
}
//发动SCCB
void __inline SCCB_start(void)
{
CLOCK_HIGH();
DATA_HIGH();
delay(10);
DATA_LOW();
delay(10);
CLOCK_LOW();
delay(10);
}
//完毕SCCB
void __inline SCCB_end(void)
{
DATA_LOW();
delay(10);
CLOCK_HIGH();
delay(10);
DATA_HIGH();
delay(10);
}
//SCCB发送一个字节
void __inline SCCB_sendbyte(unsigned char data)
{
int i=0;
//并行数据转串行输出,串行数据输出的次序为先高位再低位
for(i=0;i<8;i++)
{
if(data & 0x80)
DATA_HIGH();
else
DATA_LOW();
delay(10);
CLOCK_HIGH();
delay(10);
CLOCK_LOW();
delay(10);
DATA_LOW();
delay(10);
data <<= 1;
}
//第9位,Don’t Care
DATA_HIGH();
delay(10);
CLOCK_HIGH();
delay(10);
CLOCK_LOW();
delay(10);
}
// SCCB接纳一个字节
void __inline SCCB_receivebyte(unsigned char *data)
{
int i=0;
int svalue=0;
int pvalue = 0;
rGPECON = 1<<28;//把GPE15输出改变为输入
//串行数据转并行输入,高位在前
for(i=7;i>=0;i–)
{
CLOCK_HIGH();
delay(10);
svalue = rGPEDAT>>15;
CLOCK_LOW();
delay(10);
pvalue |= svalue <}
rGPECON =5<<28;//再把GPE15改回为输出
//第9位,N.A.
DATA_HIGH();
delay(10);
CLOCK_HIGH();
delay(10);
CLOCK_LOW();
delay(10);
*data = pvalue &0xff;
}
//写操作
void SCCB_senddata(unsigned char subaddr, unsigned char data)
{
//3相写传输周期
SCCB_start();//发动SCCB
SCCB_sendbyte(0x60);//OV9650设备从地址,写操作
SCCB_sendbyte(subaddr);//设备内存地址
SCCB_sendbyte(data);//写数据字节
SCCB_end();//完毕SCCB
delay(20);
}
//读操作
unsigned char SCCB_receivedata(unsigned char subaddr)
{
unsigned char temp;
//2相写传输周期
SCCB_start();//发动SCCB
SCCB_sendbyte(0x60);//OV9650设备从地址,写操作
SCCB_sendbyte(subaddr);//设备内存地址
SCCB_end();//完毕SCCB
//2相读传输周期
SCCB_start();//发动SCCB
SCCB_sendbyte(0x61);//OV9650设备从地址,读操作
SCCB_receivebyte(&temp);//读字节
SCCB_end();//完毕SCCB
return temp;
}
OV9650的寄存器较多,要想装备好这些寄存器是需求花费一些精力的。下面数组给出了一个VGA(640×480)形式下YUV五颜六色空间的装备比如,括号内第一个元素一共寄存器地址,第二个元素一共要写入的数据。
const unsigned char ov9650_register[ ][2] = {
{0x11,0x80},{0x6a,0x3e},{0x3b,0x09},{0x13,0xe0},{0x01,0x80},{0x02,0x80},{0x00,0x00},{0x10,0x00},
{0x13,0xe5},{0x39,0x43},{0x38,0x12},{0x37,0x00},{0x35,0x91},{0x0e,0xa0},{0x1e,0x04},{0xA8,0x80},
{0x12,0x40},{0x04,0x00},{0x0c,0x04},{0x0d,0x80},{0x18,0xc6},{0x17,0x26},{0x32,0xad},{0x03,0x00},
{0x1a,0x3d},{0x19,0x01},{0x3f,0xa6},{0x14,0x2e},{0x15,0x10},{0x41,0x02},{0x42,0x08},{0x1b,0x00},
{0x16,0x06},{0x33,0xe2},{0x34,0xbf},{0x96,0x04},{0x3a,0x00},{0x8e,0x00},{0x3c,0x77},{0x8B,0x06},
{0x94,0x88},{0x95,0x88},{0x40,0xc1},{0x29,0x3f},{0x0f,0x42},{0x3d,0x92},{0x69,0x40},{0x5C,0xb9},
{0x5D,0x96},{0x5E,0x10},{0x59,0xc0},{0x5A,0xaf},{0x5B,0x55},{0x43,0xf0},{0x44,0x10},{0x45,0x68},
{0x46,0x96},{0x47,0x60},{0x48,0x80},{0x5F,0xe0},{0x60,0x8c},{0x61,0x20},{0xa5,0xd9},{0xa4,0x74},
{0x8d,0x02},{0x13,0xe7},{0x4f,0x3a},{0x50,0x3d},{0x51,0x03},{0x52,0x12},{0x53,0x26},{0x54,0x38},
{0x55,0x40},{0x56,0x40},{0x57,0x40},{0x58,0x0d},{0x8C,0x23},{0x3E,0x02},{0xa9,0xb8},{0xaa,0x92},
{0xab,0x0a},{0x8f,0xdf},{0x90,0x00},{0x91,0x00},{0x9f,0x00},{0xa0,0x00},{0x3A,0x01},{0x24,0x70},
{0x25,0x64},{0x26,0xc3},{0x2a,0x00},{0x2b,0x00},{0x6c,0x40},{0x6d,0x30},{0x6e,0x4b},{0x6f,0x60},
{0x70,0x70},{0x71,0x70},{0x72,0x70},{0x73,0x70},{0x74,0x60},{0x75,0x60},{0x76,0x50},{0x77,0x48},
{0x78,0x3a},{0x79,0x2e},{0x7a,0x28},{0x7b,0x22},{0x7c,0x04},{0x7d,0x07},{0x7e,0x10},{0x7f,0x28},
{0x80,0x36},{0x81,0x44},{0x82,0x52},{0x83,0x60},{0x84,0x6c},{0x85,0x78},{0x86,0x8c},{0x87,0x9e},
{0x88,0xbb},{0x89,0xd2},{0x8a,0xe6},
};
别的OV9650有两个只读寄存器——0x1C和0x1D,用于寄存厂家ID,数据别离为0x7F和0xA2,咱们能够经过读取它们来判别s3c2440是否连接了OV9650。当结语连接了OV9650后,咱们就能够把上面的那个数组写入OV9650内,如下所示。在这儿咱们总是以为s3c2440连接了OV9650。
void config_ov9650(void)
{
unsigned char temp;
int i;
//读取OV9650厂商ID
i=1;
while(i)
{
temp = SCCB_receivedata(0x1C);//或Rd_SCCB (0x1C,&temp);
if(temp==0x7F)
i=0;
}
i=1;
while(i)
{
temp = SCCB_receivedata(0x1D);//或Rd_SCCB (0x1D,&temp);
if(temp==0xA2)
i=0;
}
//复位一切OV9650寄存器
SCCB_senddata(0x12,0x80);//或Wr_SCCB (0x12,0x80);
delay(10000);
//装备OV9650寄存器
for(i=0;i<((sizeof(ov9650_register))/2);i++)
{
SCCB_senddata(ov9650_register[i][0],ov9650_register[i][1]);
//或Wr_SCCB (ov9650_register[i][0],ov9650_register[i][1]);
}
}
上面程序中,咱们是用循环查办读取OV9650的寄存器0x1C和0x1D的,之所以这样,是为了避免只读取一次时,会有读取不正确的现象产生。而一旦正确读取了厂商ID信息,再读写OV9650寄存器,一般就不会产生读写的过错。
下面就介绍s3c2440摄像接口的相关装备。摄像接口有两个彼此独立的DMA通道——P通道(预览通道)和C通道(编解码通道)。P通道主要是存储用于视频显现的RGB图画数据,C通道主要是存储用于编解码的YCbCr图画数据。在这儿咱们主要是把OV9650并重到的视频信息实时显现在LCD上,因而只介绍P通道的用法。
设置s3c2440摄像接口一个很重要的过程便是设置视频尺度巨细。咱们把由OV9650并重到的视频尺度称为源,即源水平尺度和源笔直尺度,其间源水平尺度有必要是8的整数倍。这个尺度是经过装备OV9650的相关寄存器完结的。咱们把这两个值别离放入输入源格局寄存器CISRCFMT的第16位至第28位,和第0位至第12位内,例如经过OV9650,并重的到的视频尺度为640×480,则把640和480别离放入寄存器CISRCFMT中的相应方位即可。咱们把实践显现的视频尺度称为方针,即方针水平尺度和方针笔直尺度,这儿这个尺度便是LCD的尺度。咱们把这两个值别离放入预览DMA方针图画格局寄存器CIPRTRGFMT的第16位至第28位,和第0位至第12位内,例如LCD的尺度为320×240,则把320和240别离放入寄存器CIPRTRGFMT中的相应方位即可。别的还需求把这两个值的乘积放入预览缩放方针面积寄存器CIPRTAREA内。源尺度和方针尺度往往是不一样巨细的,那么或许还需求设置偏移量,即水平偏移量和笔直偏移量,应该把这两个值别离放入窗口偏移寄存器CIWDOFST的第16位至第26位,和第0位至第10位内,其间这个寄存器的第31位用于操控是否需求设置偏移量,当偏移量为0或不需求设置偏移量时,这一位应为0,否则为1。明显,经过源尺度、方针尺度和偏移量的设置,能够完结被摄像物体的缩放作用。当然,要完结这种缩放作用,还需求装备预览预缩放份额操控寄存器CIPRSCPRERATIO、预览预缩放间隔格局寄存器CIPRSCPREDST和预览主缩放操控寄存器CIPRSCCTRL,这些寄存器的相关参数是经过核算得到的,数据手册上有详细的阐明,而且还有规范的函数能够调用,因而在这儿就不过多介绍。
前面现已介绍过,摄像接口都是经过DMA完结数据交换的。s3c2440能够在内存中各拓荒四块乒乓存储区域,用于完结P通道和C通道的快速数据传递。在P通道中,寄存器CIPRCLRSA1、CIPRCLRSA2、CIPRCLRSA3和CIPRCLRSA4别离用于一共这四块内存的首地址。别的在DMA数据传递中,还要让DMA知道怎么进行传递,即一次传输多少个字节,这需求设置预览DMA操控相关寄存器CIPRCTRL的主突发长度和剩下突发长度,这两个值也能够经过调用规范函数来求得。别的在完结每一帧视频并重后,会触发一个视频中止。
下面就给出一段详细的程序,使用OV9650实时地在LCD上显现视频,并经过UART来操控视频,让视频图画扩大,缩小,以及完结照相的功用(让图画定格在LCD上)。
…………
int com;
…………
//核算主突发长度和剩下突发长度,用于CIPRCTRL寄存器
void CalculateBurstSize(U32 hSize,U32 *mainBurstSize,U32 *remainedBurstSize)
{
U32 tmp;
tmp=(hSize/4)%16;
switch(tmp) {
case 0:
*mainBurstSize=16;
*remainedBurstSize=16;
break;
case 4:
*mainBurstSize=16;
*remainedBurstSize=4;
break;
case 8:
*mainBurstSize=16;
*remainedBurstSize=8;
break;
default:
tmp=(hSize/4)%8;
switch(tmp) {
case 0:
*mainBurstSize=8;
*remainedBurstSize=8;
break;
case 4:
*mainBurstSize=8;
*remainedBurstSize=4;
default:
*mainBurstSize=4;
tmp=(hSize/4)%4;
*remainedBurstSize= (tmp) ? tmp: 4;
break;
}
break;
}
}
//核算预缩放比率及移位量,用于CICOSCPRERATIO寄存器
void CalculatePrescalerRatioShift(U32 SrcSize, U32 DstSize, U32 *ratio,U32 *shift)
{
if(SrcSize>=64*DstSize) {
//Uart_Printf(“ERROR: out of the prescaler range: SrcSize/DstSize = %d(< 64)/n",SrcSize/DstSize);
while(1);
}
else if(SrcSize>=32*DstSize) {
*ratio=32;
*shift=5;
}
else if(SrcSize>=16*DstSize) {
*ratio=16;
*shift=4;
}
else if(SrcSize>=8*DstSize) {
*ratio=8;
*shift=3;
}
else if(SrcSize>=4*DstSize) {
*ratio=4;
*shift=2;
}
else if(SrcSize>=2*DstSize) {
*ratio=2;
*shift=1;
}
else {
*ratio=1;
*shift=0;
}
}
//摄像接口初始化
//输入参数别离为预览方针宽和高(即LCD尺度),以及水平缓笔直偏移量
void CamInit(U32 PrDstWidth, U32 PrDstHeight, U32 WinHorOffset, U32 WinVerOffset)
{
U32 WinOfsEn;
U32 MainBurstSizeRGB, RemainedBurstSizeRGB;
U32 H_Shift, V_Shift, PreHorRatio, PreVerRatio, MainHorRatio, MainVerRatio;
U32 SrcWidth, SrcHeight;
U32 ScaleUp_H_Pr, ScaleUp_V_Pr;
//判别是否需求设置偏移量
if(WinHorOffset==0 && WinVerOffset==0)
WinOfsEn=0;
else
WinOfsEn=1;
SrcWidth=640/*源水平尺度*/-WinHorOffset*2;
SrcHeight=480/*源笔直尺度*/-WinVerOffset*2;
//判别尺度是扩大仍是缩小
if(SrcWidth>=PrDstWidth)
ScaleUp_H_Pr=0;//down
else
ScaleUp_H_Pr=1;//up
if(SrcHeight>=PrDstHeight)
ScaleUp_V_Pr=0;
else
ScaleUp_V_Pr=1;
rCIGCTRL |= (1<<26)|(0<<27);//PCLK极性回转,外部摄像处理器输入
rCIWDOFST = (1<<30)|(0xf<<12);//清FIFO溢出
rCIWDOFST = 0;//康复正常形式
rCIWDOFST=(WinOfsEn<<31)|(WinHorOffset<<16)|(WinVerOffset);//设置偏移量
rCISRCFMT=(1<<31)|(0<<30)|(0<<29)|(640/*源水平尺度*/<<16)|(0<<14)|(480/*源笔直尺度*/);
//设置内存首地址,由于是直接显现,所以设置为LCD缓存数组首地址
rCIPRCLRSA1 = (U32)LCD_BUFFER;
rCIPRCLRSA2 = (U32)LCD_BUFFER;
rCIPRCLRSA3 = (U32)LCD_BUFFER;
rCIPRCLRSA4 = (U32)LCD_BUFFER;
//设置方针尺度,而且不进行镜像和旋转处理
rCIPRTRGFMT=(PrDstWidth<<16)|(0<<14)|(PrDstHeight);
//核算并设置突发长度
CalculateBurstSize(PrDstWidth*2, &MainBurstSizeRGB, &RemainedBurstSizeRGB);
rCIPRCTRL=(MainBurstSizeRGB<<19)|(RemainedBurstSizeRGB<<14);
//核算水平缓笔直缩放比率和位移量,以及主水平、笔直比率
CalculatePrescalerRatioShift(SrcWidth, PrDstWidth, &PreHorRatio, &H_Shift);
CalculatePrescalerRatioShift(SrcHeight, PrDstHeight, &PreVerRatio, &V_Shift);
MainHorRatio=(SrcWidth<<8)/(PrDstWidth<
rCIPRSCPREDST=((SrcWidth/PreHorRatio)<<16)|(SrcHeight/PreVerRatio);
rCIPRSCCTRL=(1<<31)|(1 /*24位RGB格局*/ <<30)|(ScaleUp_H_Pr<<29)|(ScaleUp_V_Pr<<28)|(MainHorRatio<<16)|(MainVerRatio);
//设置面积
rCIPRTAREA= PrDstWidth*PrDstHeight;
}
//摄像中止,在这儿,除了清中止标志,没有其他操作
void __irq CamIsr(void)
{
rSUBSRCPND |= 1<<12;
rSRCPND |= 1<<6;
rINTPND |= 1<<6;
}
//UART中止
void __irq uartISR(void)
{
unsigned char ch;
rSUBSRCPND |= 0x3;
rSRCPND = 0x1<<28;
rINTPND = 0x1<<28;
ch = rURXH0; //接纳字节数据
switch(ch)
{
case 0x11://正常显现视频
com=1;
break;
case 0x22://定格图画
com=2;
break;
case 0x33://扩大尺度
com=3;
break;
case 0x44://缩小尺度
com =4;
break;
}
rUTXH0 = ch;
}
void Main(void)
{
int HOffset,VOffset;
//初始化UPLL,以得到OV9650的体系时钟
rUPLLCON = (56<<12) | (2<<4) | 1;//UPLL为96MHz
rCLKDIVN |= (1<<3);//UCLK = UPLL/2=48MHz
rCAMDIVN = (rCAMDIVN & ~(0xf))|(1<<4)|(2);//设置摄像接口时钟分频
…………
LCD_Init();//初始化LCD,其间LCD的显现格局为24位RGB格局
rLCDCON1|=1;//舱位LCD
//装备摄像接口引脚
rGPJCON = 0x2aaaaaa;
rGPJDAT = 0;
rGPJUP = 0;//上拉使能
//硬件复位摄像头
rGPJDAT |= 1<<12;
delay(100);
rGPJDAT &= ~(1<<12);
//软件复位摄像接口
rCIGCTRL |= (1<<31);
delay(100);
rCIGCTRL &= ~(1<<31);
delay(100);
//软件复位摄像头
rCIGCTRL |= (1<<30);
delay(300);
rCIGCTRL &= ~(1<<30);
delay(20000);
config_ov9650();//装备OV9650寄存器
HOffset=0;
VOffset=0;
//初始化摄像接口
CamInit(320,240,HOffset,VOffset);
//舱位摄像接口中止,
rSUBSRCPND |= 1<<12;
rSRCPND |= 1<<6;
rINTPND |= 1<<6;
rINTSUBMSK &= ~(1<<12);
rINTMSK &= ~(1<<6);
pISR_CAM = (U32)CamIsr;
rCIPRSCCTRL|=(1<<15);//预览缩放舱位
rCIIMGCPT =(1<<31)|(1<<29);//预览缩放捕捉使能
com=0;
while(1)
{
switch(com)
{
case 1://正常显现
com=0;
rCIPRSCCTRL|=(1<<15);
rCIIMGCPT =(1<<31)|(1<<29);
break;
case 2://定格图画
com=0;
rCIPRSCCTRL&=~(1<<15);
rCIIMGCPT &=~((1<<31)|(1<<29));
break;
case 3://扩大视频
com=0;
if(HOffset==160)
break;
HOffset += 8;
VOffset += 8;
rCIPRSCCTRL&=~(1<<15);
rCIIMGCPT &=~((1<<31)|(1<<29));
CamInit(320,240,HOffset,VOffset);
rCIPRSCCTRL|=(1<<15);
rCIIMGCPT =(1<<31)|(1<<29);
break;
case 4://缩小视频
com=0;
if(HOffset==0)
break;
HOffset -= 8;
VOffset -= 8;
rCIPRSCCTRL&=~(1<<15);
rCIIMGCPT &=~((1<<31)|(1<<29));
CamInit(320,240,HOffset,VOffset);
rCIPRSCCTRL|=(1<<15);
rCIIMGCPT =(1<<31)|(1<<29);
break;
}
}
}