OV2640摄像头显示方式探究

时间:2022-07-28
本文章向大家介绍OV2640摄像头显示方式探究,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

国庆节快乐~点击上方文字关注我们哦

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的接收地址修改为下一行,并记录当前的行数,同时对该行数据进行旋转。