板子上配了一个OV2640摄像头,其最大像素尺寸为1600*1200,板子上的液晶屏的尺寸为480*272,而光盘资料里的照相机例程,只使用了屏幕中间的240*272的一块区域显示摄像头的图像。
本篇以资料中的照相机例子为基础,修改程序,测试摄像头在整个屏幕(480*272)上的显示效果,关于屏幕的使用基础,可先参考之前的文章:【GD32450I-EVAL】+ 04液晶屏层叠显示与透明度调整测试
先看一下例子的效果:
//----------------------主函数-----------------------------
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*2/1024=150K字节
272*240*2/1024=127K字节
所以,仅仅是前两项功能,所需内存超过了256K,因而必须使用SDRAM来扩展内存,关于SDRAM的基本使用,可以先查看我的上一篇文章:【GD32450I-EVAL】+ 06SDRAM介绍
注意,这个程序中,摄像头采集的图像,DMA传输时,不是直接从摄像头传输到了液晶屏的显存(0XC0400000)!而时先传输到一个暂存地址(0XC0000000),这时因为摄像头与液晶屏的安装位置是90度的关系,为了竖屏显示摄像头图像,需要手动将图像旋转90度,就是DMA中断函数中指针操作那一句(见上面程序)。
数据传递关系可用下图表示:
再来看一下摄像头的初始化:
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根数据线。另外还有一些行同步、帧同步、像素时钟引脚。
先看一下修改后的效果:
修改为全屏显示,不能只是把摄像头和液晶屏的显示尺寸修改一下就完事了,因为尺寸变大以后,还要考虑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
不过实际测试,图像静止时显示正常,但在图像快速变化的时候,显示的图像在某些区域会有些奇怪,如下图,在屏幕左侧有若干竖列显示的好像是之前的图像,在屏幕的右下角也有一块三角形区域显示的是之前的图,其刷新的速度没有正常区域刷新的快。只有当画面静止时,异常区域 的图像才能更新为正常图像。
上面出现局部异常的原因位置,可以是一次传输整帧图像,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);
}
}
实测效果,这种方式,图像晃动时,不会出现局部异常显示区域,但会有明显的图像刷新现象。有时间再研究其它更合理的显示方式。
本帖最后由 DDZZ669 于 2020-10-6 14:54 编辑
引用: xiaoxuexue 发表于 2023-7-16 11:23 up主,有把ov2640数据上传到上位机的代码吗
还没有用上位机