S3C2440 摄像接口CamInit()函数初始化之分析
转自:http://blog.csdn.net/xuxg2005/article/details/6139370
在调试2440 相机接口的时候碰到问题最多的就是这个CamInit()函数,下面贴上代码,分析之.
/* Description of Parameters
CoDstWidth: Destination Width of Codec Path
CoDstHeight: Destination Height of Codec Path
PrDstWidth: Destination Width of Preview Path
PrDstHeight: Destination Height of Preview Path
WinHorOffset: Size of Window Offset for Horizontal Direction
WinVerOffset: Size of Window Offset for Vertical Direction
CoFrameBuffer: Start Address for Codec DMA
PrFrameBuffer: Start Address for Previe DMA
*/
void CamInit(U32 CoDstWidth, U32 CoDstHeight, U32 PrDstWidth, U32 PrDstHeight,
U32 WinHorOffset, U32 WinVerOffset, U32 CoFrameBuffer, U32 PrFrameBuffer)
{
U32 WinOfsEn;
U32 divisor, multiplier;
U32 MainBurstSizeY, RemainedBurstSizeY, MainBurstSizeC, RemainedBurstSizeC, MainBurstSizeRGB, RemainedBurstSizeRGB;
U32 H_Shift, V_Shift, PreHorRatio, PreVerRatio, MainHorRatio, MainVerRatio;
U32 SrcWidth, SrcHeight;
U32 ScaleUp_H_Co, ScaleUp_V_Co, ScaleUp_H_Pr, ScaleUp_V_Pr;
if(CAM_CODEC_OUTPUT) // 注释01
divisor=2; //CCIR-422
else
divisor=4; //CCIR-420
//constant for calculating preview dma address
if(CAM_PVIEW_OUTPUT) // 注释02
multiplier=4; // RGB24 ,则每个像素用4字节
else
multiplier=2; // RGB16 ,则每个像素用2字节
if(WinHorOffset==0 && WinVerOffset==0) //注释03 检查是否需要缩放
WinOfsEn=0;
else
WinOfsEn=1;
SrcWidth=CAM_SRC_HSIZE-WinHorOffset*2;
SrcHeight=CAM_SRC_VSIZE-WinVerOffset*2;
// 注释 04 scale up ---按比例增加,相应提高
if(SrcWidth>=CoDstWidth) ScaleUp_H_Co=0; //down 偏移后的原始图像宽度还比目标图像宽度大,那么不需要放大,需要缩小.
else ScaleUp_H_Co=1; //up
if(SrcHeight>=CoDstHeight) ScaleUp_V_Co=0; // 偏移后的原始图像高度比目标图像高度大,那么不需要放大,需要缩小.
else ScaleUp_V_Co=1;
if(SrcWidth>=PrDstWidth) ScaleUp_H_Pr=0; //down
else ScaleUp_H_Pr=1; //up
if(SrcHeight>=PrDstHeight) ScaleUp_V_Pr=0; // edited 040225
else ScaleUp_V_Pr=1;
// 注释05
////////////////// common control setting
rCIGCTRL |= (1<<26)|(0<<27); // inverse PCLK, test pattern
//--- capbily
//rCIGCTRL |= (0<<26)|(0<<27); // don't inverse PCLK, test pattern
//---
rCIWDOFST = (1<<30)|(0xf<<12); // clear overflow 清除编码和预览的fifo
rCIWDOFST = 0;
rCIWDOFST=(WinOfsEn<<31)|(WinHorOffset<<16)|(WinVerOffset); // 设置窗口偏移并设置允许偏移
rCISRCFMT=(CAM_ITU601<<31)|(0<<30)|(0<<29)|(CAM_SRC_HSIZE<<16)|(CAM_ORDER_YCBYCR<<14)|(CAM_SRC_VSIZE);
// 设置源水平像素和垂直像素
//--- capbily
//rCISRCFMT=(CAM_ITU601<<31)|(1<<30)|(0<<29)|(CAM_SRC_HSIZE<<16)|(CAM_ORDER_YCBYCR<<14)|(CAM_SRC_VSIZE);
//---
// 注释06
////////////////// codec port setting
if (CAM_CODEC_4PP)
{
rCICOYSA1=CoFrameBuffer;
rCICOYSA2=rCICOYSA1+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOYSA3=rCICOYSA2+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOYSA4=rCICOYSA3+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOCBSA1=rCICOYSA1+CoDstWidth*CoDstHeight;
rCICOCBSA2=rCICOYSA2+CoDstWidth*CoDstHeight;
rCICOCBSA3=rCICOYSA3+CoDstWidth*CoDstHeight;
rCICOCBSA4=rCICOYSA4+CoDstWidth*CoDstHeight;
rCICOCRSA1=rCICOCBSA1+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA2=rCICOCBSA2+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA3=rCICOCBSA3+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA4=rCICOCBSA4+CoDstWidth*CoDstHeight/divisor;
}
else
{
rCICOYSA1=CoFrameBuffer;
rCICOYSA2=rCICOYSA1;
rCICOYSA3=rCICOYSA1;
rCICOYSA4=rCICOYSA1;
rCICOCBSA1=rCICOYSA1+CoDstWidth*CoDstHeight;
rCICOCBSA2=rCICOCBSA1;
rCICOCBSA3=rCICOCBSA1;
rCICOCBSA4=rCICOCBSA1;
rCICOCRSA1=rCICOCBSA1+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA2=rCICOCRSA1;
rCICOCRSA3=rCICOCRSA1;
rCICOCRSA4=rCICOCRSA1;
}
rCICOTRGFMT=(CAM_CODEC_IN_422<<31)|(CAM_CODEC_OUTPUT<<30)|(CoDstWidth<<16)|(CAM_FLIP_180<<14)|(CoDstHeight);
// 注释07
CalculateBurstSize(CoDstWidth, &MainBurstSizeY, &RemainedBurstSizeY);
CalculateBurstSize(CoDstWidth/2, &MainBurstSizeC, &RemainedBurstSizeC);
rCICOCTRL=(MainBurstSizeY<<19)|(RemainedBurstSizeY<<14)|(MainBurstSizeC<<9)|(RemainedBurstSizeC<<4);
// 注释08
CalculatePrescalerRatioShift(SrcWidth, CoDstWidth, &PreHorRatio, &H_Shift);
CalculatePrescalerRatioShift(SrcHeight, CoDstHeight, &PreVerRatio, &V_Shift);
MainHorRatio=(SrcWidth<<8)/(CoDstWidth<<H_Shift);
MainVerRatio=(SrcHeight<<8)/(CoDstHeight<<V_Shift);
// 注释09
rCICOSCPRERATIO=((10-H_Shift-V_Shift)<<28)|(PreHorRatio<<16)|(PreVerRatio);
rCICOSCPREDST=((SrcWidth/PreHorRatio)<<16)|(SrcHeight/PreVerRatio);
rCICOSCCTRL=(CAM_SCALER_BYPASS_OFF<<31)|(ScaleUp_H_Co<<30)|(ScaleUp_V_Co<<29)|(MainHorRatio<<16)|(MainVerRatio);
rCICOTAREA=CoDstWidth*CoDstHeight;
///////////////// preview port setting
if (CAM_PVIEW_4PP) // codec view mode 预览通道
{
rCIPRCLRSA1=PrFrameBuffer;
rCIPRCLRSA2=rCIPRCLRSA1+PrDstWidth*PrDstHeight*multiplier;
rCIPRCLRSA3=rCIPRCLRSA2+PrDstWidth*PrDstHeight*multiplier;
rCIPRCLRSA4=rCIPRCLRSA3+PrDstWidth*PrDstHeight*multiplier;
}
else // direct preview mode
{
rCIPRCLRSA1 = (U32)LCD_BUFFER_CAM;
rCIPRCLRSA2 = (U32)LCD_BUFFER_CAM;
rCIPRCLRSA3 = (U32)LCD_BUFFER_CAM;
rCIPRCLRSA4 = (U32)LCD_BUFFER_CAM;
}
rCIPRTRGFMT=(PrDstWidth<<16)|(CAM_FLIP_180<<14)|(PrDstHeight); // 设置预览水平像素和垂直像素,图像旋转180度
if (CAM_PVIEW_OUTPUT==CAM_RGB24B)
CalculateBurstSize(PrDstWidth*2, &MainBurstSizeRGB, &RemainedBurstSizeRGB);
else // RGB16B
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<<H_Shift);
MainVerRatio=(SrcHeight<<8)/(PrDstHeight<<V_Shift);
rCIPRSCPRERATIO=((10-H_Shift-V_Shift)<<28)|(PreHorRatio<<16)|(PreVerRatio);
rCIPRSCPREDST=((SrcWidth/PreHorRatio)<<16)|(SrcHeight/PreVerRatio);
rCIPRSCCTRL=(1<<31)|(CAM_PVIEW_OUTPUT<<30)|(ScaleUp_H_Pr<<29)|(ScaleUp_V_Pr<<28)|(MainHorRatio<<16)|(MainVerRatio);
rCIPRTAREA= PrDstWidth*PrDstHeight;
}
我们来分析这段函数代码
01) 根据宏CAM_CODEC_OUTPUT的定义来决定除数 divisor 的大小.
我们来看下CAM_CCIR422 在定义中是如何定义的:
#define CAM_CODEC_OUTPUT CAM_CCIR422
在这里定义宏 CAM_CCIR422定义为 CAM_CCIR422 表示视频编码通道输出为 4:2:2格式,此时divisor = 2;
如果宏CAM_CCIR422定义为 CAM_CCIR420 表示视频编码通道输出为 4:2:0格式.此时divisor = 4;
02) 根据宏CAM_PVIEW_OUTPUT 的定义来决定乘数 multiplier 的大小.
我们来看下CAM_PVIEW_OUTPUT 在定义中是如何定义的:
#define CAM_PVIEW_OUTPUT CAM_RGB16B
在这里定义宏 CAM_PVIEW_OUTPUT定义为 CAM_RGB16B 表示视频预览通道输出为每个像素占16位格式(占2个字节),此时multiplier= 2;
如果宏CAM_PVIEW_OUTPUT定义为 CAM_RGB24B 表示视频预览通道输出为真彩,此时每个像素占4个字节,此时multiplier= 4;
03)根据设置,检查是否需要进行图像缩放
2440相机接口的缩放其实很简单,
a)2440规定目标屏幕大小 TargetHsize_xx 必须小于等于原屏幕大小 SourceHsize.
b)在这样的情况下,相机接口图像的放大,只能是将原输入图像中需要放大的区域裁剪出来,"粘贴"到目标屏幕上,实现放大.
如果目标屏幕和原屏幕一样大,那么这种方式下图像是放大不了的.
SrcWidth=CAM_SRC_HSIZE-WinHorOffset*2;
SrcHeight=CAM_SRC_VSIZE-WinVerOffset*2;
这两行代码就是计算被裁剪后区域的水平大小和垂直大小.
04)scale up 是个英文单词,意思是按比例增加,相应提高.
if(SrcWidth>=CoDstWidth) ScaleUp_H_Co=0;
else ScaleUp_H_Co=1; //up
意思是水平方向偏移后的图像宽度还比目标图像宽度大,那么不需要放大,直接将裁剪后区域水平方向"粘贴"到目标屏幕就可.
下面几行针对视频编码通道和预览通道分别判断垂直方向是否需要放大.
05)
// 反转PCLK时钟
rCIGCTRL |= (1<<26)|(0<<27);
// 清除编码和预览的fifo
rCIWDOFST = (1<<30)|(0xf<<12);
rCIWDOFST = 0;
rCIWDOFST=(WinOfsEn<<31)|(WinHorOffset<<16)|(WinVerOffset); // 设置窗口偏移并设置允许偏移
// 设置源水平像素和垂直像素
rCISRCFMT=(CAM_ITU601<<31)|(0<<30)|(0<<29)|(CAM_SRC_HSIZE<<16)|(CAM_ORDER_YCBYCR<<14)|(CAM_SRC_VSIZE);
在上面这段代码中 涉及到寄存器 rCIWDOFST,这个单词r表示寄存器,CI表示camera interface,WD表示window,OFST表示offset.
rCIWDOFST 的第31位,表示是否允许放大,26到16位填写窗口的水平偏移量,10到0位填写窗口的垂直偏移量.
rCISRCFMT 是源帧格式寄存器,需要填写相机的水平像素和垂直像素数目.
06) 这段代码根据宏CAM_CODEC_4PP 的定义来设置帧内存地址
我们来看看在程序中的定义
#define CAM_CODEC_4PP (0) // 0:all equal, 1:4 pingpong
表示在帧存储中不使用乒乓方式
帧存储使用乒乓方式的好处是什么呢?
答:在内存开辟1块缓冲区Buf,当2440从图像传感器采集来数据后放入这个缓冲区Buf,同时DMA从这个缓冲区Buf将数据拷贝到LCD屏幕,
因为DMA操作是不经过CPU控制的,这样就产生了一个问题,可能从图像传感器来的数据放入缓冲区Buf的时候,恰好DMA也在操作Buf,这就会引发对数据访问的同步与互斥问题,为了解决这个问题,就设置了4块内存区域,这样就解决了问题.
根据 图23-6. 乒乓存储器体系我们看到
rCICOYSA1是编码通道Y1分量的内存地址
rCICOYSA2是编码通道Y2分量的内存地址
rCICOYSA3是编码通道Y3分量的内存地址
rCICOYSA4是编码通道Y4分量的内存地址
rCICOCBSA1 是编码通道CB1分量的内存地址
rCICOCBSA2 是编码通道CB2分量的内存地址
rCICOCBSA3 是编码通道CB3分量的内存地址
rCICOCBSA4 是编码通道CB4分量的内存地址
rCICOCRSA1 是编码通道CR1分量的内存地址
rCICOCRSA2 是编码通道CR2分量的内存地址
rCICOCRSA3 是编码通道CR3分量的内存地址
rCICOCRSA4 是编码通道CR4分量的内存地址
如果图像传感器按照 4:2 : 2方式采样,那么每4个像素中有4个Y分量,2个CB分量,2个CR分量,每个分量为1字节;
第1帧采样完成得到
CoDstWidth*CoDstHeight 个Y1, 放入rCICOYSA1[CoDstWidth*CoDstHeight]
CoDstWidth*CoDstHeight/2 个CB1,放入rCICOCBSA1[CoDstWidth*CoDstHeight/2]
CoDstWidth*CoDstHeight/2 个CR1,放入rCICOCRSA1[CoDstWidth*CoDstHeight/2]
第2帧采样完成得到
CoDstWidth*CoDstHeight 个Y2, 放入rCICOYSA2 [CoDstWidth*CoDstHeight]
CoDstWidth*CoDstHeight/2 个CB2,放入rCICOCBSA2[CoDstWidth*CoDstHeight/2]
CoDstWidth*CoDstHeight/2 个CR2,放入rCICOCRSA2[CoDstWidth*CoDstHeight/2]
第3帧采样完成得到
CoDstWidth*CoDstHeight 个Y3, 放入rCICOYSA3 [CoDstWidth*CoDstHeight]
CoDstWidth*CoDstHeight 个CB3, 放入rCICOCBSA3[CoDstWidth*CoDstHeight/2]
CoDstWidth*CoDstHeight 个CR3, 放入rCICOCRSA3[CoDstWidth*CoDstHeight/2]
第4帧采样完成得到
CoDstWidth*CoDstHeight 个Y4, 放入rCICOYSA4 [CoDstWidth*CoDstHeight]
CoDstWidth*CoDstHeight/2 个CB4,放入rCICOCBSA4[CoDstWidth*CoDstHeight/2]
CoDstWidth*CoDstHeight/2 个CR4,放入rCICOCRSA4[CoDstWidth*CoDstHeight/2]
后面第5次采样跟第一次采样一样,
第6次采样跟第2次一样.........................不断循环采样.
下面我们看这段代码是如何对这些寄存器的地址进行分配分配的,前面注释中说了如果是4:2:2采样方式,则divisor为2.
rCICOYSA1=CoFrameBuffer;
rCICOYSA2=rCICOYSA1+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOYSA3=rCICOYSA2+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOYSA4=rCICOYSA3+CoDstWidth*CoDstHeight+2*CoDstWidth*CoDstHeight/divisor;
rCICOCBSA1=rCICOYSA1+CoDstWidth*CoDstHeight;
rCICOCBSA2=rCICOYSA2+CoDstWidth*CoDstHeight;
rCICOCBSA3=rCICOYSA3+CoDstWidth*CoDstHeight;
rCICOCBSA4=rCICOYSA4+CoDstWidth*CoDstHeight;
rCICOCRSA1=rCICOCBSA1+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA2=rCICOCBSA2+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA3=rCICOCBSA3+CoDstWidth*CoDstHeight/divisor;
rCICOCRSA4=rCICOCBSA4+CoDstWidth*CoDstHeight/divisor;
如果rCICOYSA1的地址是CoFrameBuffer,那么rCICOYSA2就是在rCICOYSA1的基础上加上CoDstWidth*CoDstHeight个Y1的长度,再加上
CoDstWidth*CoDstHeight/2个CB1分量的长度,再加上CoDstWidth*CoDstHeight/2个CR1分量的长度.
rCICOCBSA1 就是在rCICOYSA1的基础上加上CoDstWidth*CoDstHeight个Y1的长度.
rCICOCRSA1就是在rCICOCBSA1的基础上加上CoDstWidth*CoDstHeight/2个CB分量的长度;
同理其它寄存器的分配就很清楚了.
07)计算突发长度,这块手册上说,所有突发长度必须为2、4、8 或16 中的一个,代码好理解,但是为何这样做暂时不清楚.
08) 计算预缩放比率
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;
}
}
根据SrcSize / DstSize 的不同值,设定不同的预缩放倍率,代码很简单.
09)注释09,似乎从手册上找不到解释,有能给解释下.
后面的代码都是针对视频预览通道的,与视频编码通道一样,不再详细讲述.