【GD32450I-EVAL】+ 07摄像头全屏显示测试

DDZZ669   2020-10-6 14:53 楼主

        板子上配了一个OV2640摄像头,其最大像素尺寸为1600*1200,板子上的液晶屏的尺寸为480*272,而光盘资料里的照相机例程,只使用了屏幕中间的240*272的一块区域显示摄像头的图像。

        本篇以资料中的照相机例子为基础,修改程序,测试摄像头在整个屏幕(480*272)上的显示效果,关于屏幕的使用基础,可先参考之前的文章:【GD32450I-EVAL】+ 04液晶屏层叠显示与透明度调整测试

 

1 例子程序分析

        先看一下例子的效果:

1.jpg

 

1.1 主程序分析

//----------------------主函数-----------------------------
int main(void)
{    
    ov2640_id_struct ov2640id;
    systick_config();
    nvic_configuration();
    
    /* init SDRAM*/  //SDRAM初始化,作为液晶屏的显存
    exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
    delay_1ms(1000);
	
    /* key configuration */   //按键初始化,用于拍照
    key_config();
  
    /* camera initialization */   //摄像头初始化,包括DMA传输配置
    dci_ov2640_init();
    dci_ov2640_id_read(&ov2640id);
    
    /* DMA interrupt and channel enable */    //使能DMA
    dma_interrupt_enable(DMA1, DMA_CH7,DMA_CHXCTL_FTFIE);
    dma_channel_enable(DMA1,DMA_CH7);
  
    /* DCI enable */    //使能摄像头获取图像
    dci_enable();  
    dci_capture_enable();
    delay_1ms(100);
  
    /* LCD configure and TLI enable */   //LCD配置
    lcd_config();
    tli_layer_enable(LAYER0);
    tli_layer_enable(LAYER1);
    tli_reload_config(TLI_REQUEST_RELOAD_EN);
    tli_enable();

    while (1)    //进入死循环,在DMA中断中显示图像
    {
    }
}

//------------------------DMA中断-----------------------
void DMA1_Channel7_IRQHandler(void)
{
    /* 320*240 size image convert to 240*272 size image */ 
    if(dma_interrupt_flag_get(DMA1,DMA_CH7,DMA_INTF_FTFIF))
    {         
        int i=0,x=0,y=0;  
        dma_channel_disable(DMA1, DMA_CH7);

        for(x=0;x<320;x++)//摄像头宽(320)
        {
            for(y=0;y<240;y++)//摄像头高(240)
            {
                if(x<272)
                {    
                    *(uint16_t *)(0XC0400000+2*i)=*(uint16_t *)(0XC0000000+2*((320*y) + x));//layer1地址<-摄像头地址 
                    i++;
                }
            }
        } 
        dma_interrupt_flag_clear(DMA1,DMA_CH7,DMA_INTC_FTFIFC);
        dma_channel_enable(DMA1, DMA_CH7);

    }       
}

        该程序需要用到外部SDRAM,因为这个程序需要用到的内存比较大,GD32F450自带的256K内存不够用,这个程序中:

  • 摄像头的图像尺寸为320*240,图像格式为RGB565,即一个像素需要2个字节的空间,所以一帧图像需要的内存为:

320*240*2/1024=150K字节

  • 摄像头采集的图像需要再裁剪为272*240并旋转方向,需要另一块内存,大小为:

272*240*2/1024=127K字节

  • 对于拍照功能,也需要150K的大小内存进行图片暂存

        所以,仅仅是前两项功能,所需内存超过了256K,因而必须使用SDRAM来扩展内存,关于SDRAM的基本使用,可以先查看我的上一篇文章:【GD32450I-EVAL】+ 06SDRAM介绍

 

1.2 几种地址的数据传递关系

        注意,这个程序中,摄像头采集的图像,DMA传输时,不是直接从摄像头传输到了液晶屏的显存(0XC0400000)!而时先传输到一个暂存地址(0XC0000000),这时因为摄像头与液晶屏的安装位置是90度的关系,为了竖屏显示摄像头图像,需要手动将图像旋转90度,就是DMA中断函数中指针操作那一句(见上面程序)。

        数据传递关系可用下图表示:

3.png

 

1.3 摄像头初始化程序

        再来看一下摄像头的初始化:

uint8_t dci_ov2640_init(void)
{
    uint8_t i;
	
    sccb_config();//SCCB通信初始化
	
    dci_config(); //摄像头配置,包括DMA的配置
	
    ckout0_init();
    delay_1ms(10);
	
    /* OV2640 reset */       //摄像头复位
    if(dci_byte_write(0xff, 0x01)!=0)
        return 0xff;
    if(dci_byte_write(0x12, 0x80)!=0)
        return 0xff;
    delay_1ms(10);
	
	//摄像头的参数配置,通过SCCB协议对寄存器写入各种参数
    for(i=0;i<sizeof(change_reg)/2;i++)
    {
        if(dci_byte_write(change_reg[0],change_reg[1])!=0)
        {
            return 0xff;
        }
    }
    delay_1ms(100);
        for(i=0;i<(sizeof(ov2640_rgb565_reg_tbl)/2);i++)
    {
        dci_byte_write(ov2640_rgb565_reg_tbl[0],ov2640_rgb565_reg_tbl[1]); 
    } 
    delay_1ms(100);
	
	//设置摄像头的输出大小
    ov2640_outsize_set(320,240);
	
    return 0;
}

        摄像头需要用到一种SCCB通信,进行参数的配置,SCCB的具体通信原理这里暂不分析,可以先把它理解为一种类似于IIC通信进行参数配置的方式即可。接着是摄像头接口(DCI)的初始化,包括GPIO初始化于DMA传输初始化等。然后是通过SCCB进行摄像头复位,以及进行各种参数配置。

        下面是摄像头的插座接口,可以看到,SCCB通信就是用的GD32的硬件IIC接口,数据传输为8根数据线。另外还有一些行同步、帧同步、像素时钟引脚。

4.png

 

2 修改为全屏显示

        先看一下修改后的效果:

2.jpg

 

2.1 直接修改显示尺寸

        修改为全屏显示,不能只是把摄像头和液晶屏的显示尺寸修改一下就完事了,因为尺寸变大以后,还要考虑DMA是否可以正常传输的问题。

例子程序中,DMA每次传说的大小为一帧图像,DMA的传输数据位宽为32位(4字节),则一帧的数据量为:320*240*2/4=38400。

        而DMA每次传输也有最大的限制,为:2的16次方,即65536。

        所以,如果是全屏图像,数据为480*272*2/4=65280,呃。。。好像刚刚够,小于65536。那就先直接修改显示尺寸看看效果。

        DMA中断可以改下为如下,注意这里我直接让摄像头输出为272*480,不需要在手动裁剪图像:

void DMA1_Channel7_IRQHandler(void)
{
	int i=0, x=0, y=0;

    if(dma_interrupt_flag_get(DMA1,DMA_CH7,DMA_INTF_FTFIF))
    {    		
		//将0xC0000000的内容,旋转一下方向,放到0xC0040000
		for(x=0;x<272;x++)
		{
			for(y=0;y<480;y++)
			{
				*(uint16_t *)(0XC0040000+2*i)=*(uint16_t *)(0xC0000000+2*(272*y+x));
				i++;
			}
		}
        dma_interrupt_flag_clear(DMA1,DMA_CH7,DMA_INTC_FTFIFC);
    }       
}
//480*272*2 = 0x3FC00
//LCD的地址可以是 0x C004 0000

        不过实际测试,图像静止时显示正常,但在图像快速变化的时候,显示的图像在某些区域会有些奇怪,如下图,在屏幕左侧有若干竖列显示的好像是之前的图像,在屏幕的右下角也有一块三角形区域显示的是之前的图,其刷新的速度没有正常区域刷新的快。只有当画面静止时,异常区域 的图像才能更新为正常图像。

5.jpg

 

2.2 修改DMA为每次传输一行

        上面出现局部异常的原因位置,可以是一次传输整帧图像,DMA异样?那就减少每次DMA的数据量。改为每次传输一行,则传输480次后形成一幅图像。另外,利用摄像头的帧中断,强制从第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);//每次传输一行
		
		//将0xC0000000的内容,旋转一下方向,放到0xC0040000
        //last_line = (0 == line_num)? 480 : (line_num-1);
		last_line = line_num;
		for(i=0;i<272;i++)
		{
			*(uint16_t *)(0XC0040000+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);
    }       
}
//480*272*2=0x3FC00
//LCD的地址可以是0xC0040000

//使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移
void DCI_IRQHandler(void)
{
	if( dci_interrupt_flag_get (DCI_INT_EF) == SET )    
	{
		/*传输完一帧,计数复位*/
		line_num=0;
		//printf("-----------line_num:%d \r\n",line_num);
		dci_interrupt_clear(DCI_INT_EF); 
		dci_interrupt_enable(DCI_INT_EF);	
	}
}

        实测效果,这种方式,图像晃动时,不会出现局部异常显示区域,但会有明显的图像刷新现象。有时间再研究其它更合理的显示方式。

1.gif

 

本帖最后由 DDZZ669 于 2020-10-6 14:54 编辑

回复评论 (4)

玩板看这里: https://bbs.eeworld.com.cn/elecplay.html EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!
点赞  2020-10-9 15:46

单纯采集图像显示的话,基本不需要多少ram,直接dma,外设到内存。

点赞  2020-10-12 21:56
up主,有把ov2640数据上传到上位机的代码吗
点赞  2023-7-16 11:23
引用: xiaoxuexue 发表于 2023-7-16 11:23 up主,有把ov2640数据上传到上位机的代码吗

还没有用上位机

点赞  2023-7-16 13:29
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复