国庆节快乐~点击上方文字关注我们哦
GD32F450I开发板上配了一个OV2640摄像头,其最大像素尺寸可设置为1600*1200,板子上的RGB-LCD液晶屏的尺寸为480*272,本篇来测试摄像头在整个屏幕上的显示效果。
1
显存大小分析
本次测试中:
摄像头的图像输出尺寸设置为480*272,图像格式为RGB565,即一个像素需要2个字节的空间(Red占5位,Green占6位,Blue占5位),所以一帧图像需要的内存为:
480*272*2/1024=255K字节
因为GD32F450自带的256K内存,虽然大于255K,但程序其它地方也需要内存,所以,摄像头全屏显示,需要用到外部SDRAM来扩展内存。
2
SDRAM搭配DMA进行图像显示
SDRAM扩展内存,就是将SDRAM作为RGB液晶屏的显存,关于RGB-LCD的使用方法,可以先查阅之前的文章:RGB-LCD液晶屏层叠显示测试
另外,为加快数据传输,使用DMA的方式,直接将摄像头采集的数据(地址为0x50050028)转移到显存(地址为0xc0000000,即SDRAM的首地址),减少CPU的干预。只要将图像数据传输至LCD显存,图像就自动显示在液晶屏上了。
图像数据传递基本原理如下图所示。
使用这种方式,显示结果如下,可以发现显示的图像为横向显示(注:这个屏幕的原点是在左下角,因为摄像头的参数设置中设置了镜像显示,所以不要误以为原点在右上角)
3
图像旋转
如果想要图像竖直显示,一种方式是:首先将摄像头的输出由480*272修改为272*480,然后,手动重新排列数据用于LCD的显示,就是将摄像头的行数据转换为LCD的列数据。
当然,这种方式就需要再使用一块内存,用于数据转换。这种方式,摄像头采集的图像,DMA传输时,从摄像头传输到的第一个地址(0XC0000000)不再作为LCD的显存地址,手动将图像旋转90度进行数据转换后,将转换后的数据传送到第二个地址(0XC0400000),用这个地址作为LCD的显存地址。
这种方式的数据传递关系可用下图表示:
图像旋转的代码实现:
void DMA1_Channel7_IRQHandler(void)
{
int i=0, x=0, y=0;
if(dma_interrupt_flag_get(DMA1,DMA_CH7,DMA_INTF_FTFIF))
{
//将0xC0000000的内容,旋转一下方向,放到0xC0400000
for(x=0;x<272;x++)
{
for(y=0;y<480;y++)
{
*(uint16_t *)(0XC0400000+2*i)=*(uint16_t *)(0xC0000000+2*(272*y+x));
i++;
}
}
dma_interrupt_flag_clear(DMA1,DMA_CH7,DMA_INTC_FTFIFC);
}
}
这种方式的显示效果如下,可以看到变为了竖直方向显示。
4
传输方式探究
修改为全屏显示,不能只是把摄像头和液晶屏的显示尺寸修改一下就完事了,因为尺寸变大以后,还要考虑DMA是否可以正常传输的问题。
DMA每次传输也有最大的限制,为:2的16次方,即65536。
上面的方式,DMA每次传输的大小为一帧图像,且DMA的传输数据位宽为32位(4字节),则一帧的数据量为:480*272*2/4=65280,小于65536,刚刚够,所以图像看起来显示正常(但实测,图像画面变化较大时,会出现局部图像滞后显示的现象)。
对于尺寸更大的屏幕,DMA就不能一次传输一帧图像了,可以考虑每次传输一行,每传输一行后,修改DMA的地址,传输下一行,直至一幅画面传输完。另外,利用摄像头的帧中断,强制从第1行重新开始传输,防止摄像头与DMA的速率不一致导致图像错位。
这种方式,中断函数可修改为如下:
static uint16_t line_num =0;//记录传输了多少行
void DMA1_Channel7_IRQHandler(void)
{
int i=0;
int last_line=0;
if(dma_interrupt_flag_get(DMA1,DMA_CH7,DMA_INTF_FTFIF))
{
/*行计数*/
line_num++;
if(line_num==480)
{
/*传输完一帧,计数复位*/
line_num=0;
}
dma_channel_disable(DMA1, DMA_CH7);
dci_dma_config(0xC0000000+(272*2*line_num), 272*2/4);//修改DMA的接收地址,每次传输一行
//将0xC0000000的内容,旋转一下方向,放到0xC0400000
//last_line = (0 == line_num)? 480 : (line_num-1);
last_line = line_num;
for(i=0;i<272;i++)
{
*(uint16_t *)(0XC0400000+2*(480*i+last_line))=*(uint16_t *)(0xC0000000+2*(272*last_line+i));
}
dma_interrupt_flag_clear(DMA1,DMA_CH7,DMA_INTC_FTFIFC);
dma_interrupt_enable(DMA1, DMA_CH7,DMA_CHXCTL_FTFIE);
dma_channel_enable(DMA1, DMA_CH7);
}
}
//使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移
void DCI_IRQHandler(void)
{
if( dci_interrupt_flag_get (DCI_INT_EF) == SET )
{
/*传输完一帧,计数复位*/
line_num=0;
dci_interrupt_clear(DCI_INT_EF);
dci_interrupt_enable(DCI_INT_EF);
}
}
上面的DMA中断函数,每传输完一行数据执行一次,将DMA的接收地址修改为下一行,并记录当前的行数,同时对该行数据进行旋转。