探索者 STM32F4 开发板的摄像头接口与 ALIENTEK OV2640 摄像头模块 的连接。在开发板的左下角的 2*9 的 P8 排座,是摄像头模块/OLED 模块共用接口,
特别注意:DCMI 摄像头接口和 I2S 接口、DAC、SDIO 以及 1WIRE_DQ 等有冲突,使用的时候,必须分时复用才可以,不可同时使用。
代码讲解
HARDWARE 文件夹下新建了 OV2640、DCMI 和 USART2 三个文件夹。在 OV2640 文件夹下新建了 ov2640.c、sccb.c、ov2640.h、sccb.h、ov2640cfg.h 等 5 个文件,并同时在工程中将这个文件夹加入头文件包含路径。在 DCMI 文件夹新建了 dcmi.c 和 dcmi.h 两个文件,也将这个文件夹加入头文件包含路径。在 USART2 文件夹下新建了:usart2.c 和 usart2.h 两个文件,同时将这个文件夹加入头文件包含路径。 DCMI 接口相关的库函数分布在 stm32f4xx_dcmi.c 文件以及对应的头文件 stm32f4xx_dcmi.h 中。
ov2640.c 里面的 OV2640_Init 函数:
u8 OV2640_Init(void)
{
u16 i=0;
u16 reg;
//设置IO
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
//GPIOG9,15初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//PG9,15推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
OV2640_PWDN=0; //POWER ON
delay_ms(10);
OV2640_RST=0; //复位OV2640
delay_ms(10);
OV2640_RST=1; //结束复位
SCCB_Init(); //初始化SCCB 的IO口
SCCB_WR_Reg(OV2640_DSP_RA_DLMT, 0x01); //操作sensor寄存器
SCCB_WR_Reg(OV2640_SENSOR_COM7, 0x80); //软复位OV2640
delay_ms(50);
reg=SCCB_RD_Reg(OV2640_SENSOR_MIDH); //读取厂家ID 高八位
reg<<=8;
reg|=SCCB_RD_Reg(OV2640_SENSOR_MIDL); //读取厂家ID 低八位
if(reg!=OV2640_MID)
{
printf("MID:%d\r\n",reg);
return 1;
}
reg=SCCB_RD_Reg(OV2640_SENSOR_PIDH); //读取厂家ID 高八位
reg<<=8;
reg|=SCCB_RD_Reg(OV2640_SENSOR_PIDL); //读取厂家ID 低八位
if(reg!=OV2640_PID)
{
printf("HID:%d\r\n",reg);
//return 2;
}
//初始化 OV2640,采用SXGA分辨率(1600*1200)
for(i=0;i<sizeof(ov2640_sxga_init_reg_tbl)/2;i++)
{
SCCB_WR_Reg(ov2640_sxga_init_reg_tbl[i][0],ov2640_sxga_init_reg_tbl[i][1]);
}
return 0x00; //ok
}
此部分代码先初始化 OV2640 相关的 IO 口(包括 SCCB_Init),然后最主要的是完成 OV2640 的寄存器序列初始化。OV2640 的寄存器特多(百几十个),配置特麻烦,我们用厂家有提供参考配置序列,本章我们用到的配置序列,存放在 ov2640_uxga_init_reg_tbl 这个数组里面,该数组是一个 2 维数组,存储初始化序列寄存器及 其对应的值,该数组存放在 ov2640cfg.h 里面。
另外,在 ov2640.c 里面,还有几个函数比较重要:
OV2640_Window_Set 函数,该函数用于设置传感器输出窗口;
OV2640_ImageSize_Set 函数,用于设置图像尺寸;
OV2640_ImageWin_Set 函数,用于设置图像窗口大小;
OV2640_OutSize_Set 函数,用于设置图像输出大小;
ov2640cfg.h 里面 ov2640_uxga_init_reg_tbl 的内容:
里面总共有 5 个数组。每个数组条目的第一个字节为寄存器号(也就是寄存器地址),第二个字节为要设置 的值,比如{0xff, 0x01},就表示在 0Xff 地址,写入 0X01 这个值。 五个数组里面 ov2640_uxga_init_reg_tbl 和 ov2640_svga_init_reg_tbl,分别用于配置 OV2640 输出 UXGA 和 SVGA 分辨率的图像,我们只用了 ov2640_uxga_init_reg_tbl 这个数组,完成对 OV2640 的初始化(设置为 UXGA)。最后 OV2640 要输出数据是 RGB565 还是 JPEG,就得通过 其他数组设置,输出 RGB565 时,通过一个数组:ov2640_rgb565_reg_tbl 设置即可;输出 JPEG 时,则要通过 ov2640_yuv422_reg_tbl 和 ov2640_jpeg_reg_tbl 两个数组设置。
dcmi.c:
DCMI_IRQHandler 函数,用于处理帧中断,可以实现帧率统计(需要定时器支持) 和 JPEG 数据处理等。DCMI_DMA_Init 函数,则用于配置 DCMI 的 DMA 传输,其外设地址固 定为:DCMI->DR,而存储器地址可变(LCD 或者 SRAM)。DMA 被配置为循环模式,一旦开 启,DMA 将不停的循环传输数据。My_DCMI_Init 函数用于初始化 STM32F4 的 DCMI 接口。最后,DCMI_Start 和 DCMI_Stop 两个函数, 用于开启或停止 DCMI 接口。
main.c 文件:
这里定义了一个非常大的数组 jpeg_buf(124KB),用来存储 JPEG 数据,因为 1600*1200 大小的 jpeg 图片,有可能大于 120K,所以必须将这个数组尽量设置大一点。
//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
{
if(jpeg_data_ok==0) //jpeg数据还未采集完?
{
DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输
while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置
jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);//得到此次数据传输的长度
jpeg_data_ok=1; //标记JPEG数据采集完按成,等待其他函数处理
}
if(jpeg_data_ok==2) //上一次的jpeg数据已经被处理了
{
DMA2_Stream1->NDTR=jpeg_buf_size;
DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节
DMA_Cmd(DMA2_Stream1, ENABLE); //重新传输
jpeg_data_ok=0; //标记数据未采集
}
}
else
{
LCD_SetCursor(0, 0);
LCD_WriteRAM_Prepare(); //开始写入GRAM
hsync_int = 1;
}
}
jpeg_data_process 函数用于处理 JPEG 数据的接收,在 DCMI_IRQHandler 函数里面被调用,通过一个 jpeg_data_ok 变量与 jpeg_test 函数共同控制 JPEG 的数据传送。
//JPEG测试
//JPEG数据,通过串口2发送给电脑.
void jpeg_test(void)
{
u32 i;
u8 *p;
u8 key;
u8 effect=0,saturation=2,contrast=2;
u8 size=3; //默认是QVGA 320*240尺寸
u8 msgbuf[15]; //消息缓存区
LCD_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
LCD_ShowString(30,70,200,16,16,"OV2640 JPEG Mode");
LCD_ShowString(30,100,200,16,16,"KEY0:Contrast"); //对比度
LCD_ShowString(30,120,200,16,16,"KEY1:Saturation"); //色彩饱和度
LCD_ShowString(30,140,200,16,16,"KEY2:Effects"); //特效
LCD_ShowString(30,160,200,16,16,"KEY_UP:Size"); //分辨率设置
sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
LCD_ShowString(30,180,200,16,16,msgbuf); //显示当前JPEG分辨率
OV2640_JPEG_Mode(); //JPEG模式
My_DCMI_Init(); //DCMI配置
DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置
OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸
DCMI_Start(); //启动传输
while(1)
{
if(jpeg_data_ok==1) //已经采集完一帧图像了
{
p=(u8*)jpeg_buf;
LCD_ShowString(30,210,210,16,16,"Sending JPEG data..."); //提示正在传输数据
for(i=0;i<jpeg_data_len*4;i++) //dma传输1次等于4字节,所以乘以4.
{
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(USART2,p[i]);
key=KEY_Scan(0);
if(key)break;
}
if(key) //有按键按下,需要处理
{
LCD_ShowString(30,210,210,16,16,"Quit Sending data ");//提示退出数据传输
switch(key)
{
case KEY0_PRES: //对比度设置
contrast++;
if(contrast>4)contrast=0;
OV2640_Contrast(contrast);
sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
break;
case KEY1_PRES: //饱和度Saturation
saturation++;
if(saturation>4)saturation=0;
OV2640_Color_Saturation(saturation);
sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
break;
case KEY2_PRES: //特效设置
effect++;
if(effect>6)effect=0;
OV2640_Special_Effects(effect);//设置特效
sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
break;
case WKUP_PRES: //JPEG输出尺寸设置
size++;
if(size>8)size=0;
OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸
sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
break;
}
LCD_Fill(30,180,239,190+16,WHITE);
LCD_ShowString(30,180,210,16,16,msgbuf);//显示提示内容
delay_ms(800);
}else LCD_ShowString(30,210,210,16,16,"Send data complete!!");//提示传输结束设置
jpeg_data_ok=2; //标记jpeg数据处理完了,可以让DMA去采集下一帧了.
}
}
}
jpeg_test 函数将 OV2640 设置为 JPEG 模式,该函数接收 OV2640 的 JPEG 数据,并通过串口 2 发送给上位机。
//RGB565测试
//RGB数据直接显示在LCD上面
void rgb565_test(void)
{
u8 key;
u8 effect=0,saturation=2,contrast=2;
u8 scale=1; //默认是全尺寸缩放
u8 msgbuf[15]; //消息缓存区
LCD_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
LCD_ShowString(30,70,200,16,16,"OV2640 RGB565 Mode");
LCD_ShowString(30,100,200,16,16,"KEY0:Contrast"); //对比度
LCD_ShowString(30,130,200,16,16,"KEY1:Saturation"); //色彩饱和度
LCD_ShowString(30,150,200,16,16,"KEY2:Effects"); //特效
LCD_ShowString(30,170,200,16,16,"KEY_UP:FullSize/Scale"); //1:1尺寸(显示真实尺寸)/全尺寸缩放
OV2640_RGB565_Mode(); //RGB565模式
My_DCMI_Init(); //DCMI配置
DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置
OV2640_OutSize_Set(lcddev.width,lcddev.height);
DCMI_Start(); //启动传输
while(1)
{
key=KEY_Scan(0);
if(key)
{
DCMI_Stop(); //停止显示
switch(key)
{
case KEY0_PRES: //对比度设置
contrast++;
if(contrast>4)contrast=0;
OV2640_Contrast(contrast);
sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
break;
case KEY1_PRES: //饱和度Saturation
saturation++;
if(saturation>4)saturation=0;
OV2640_Color_Saturation(saturation);
sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
break;
case KEY2_PRES: //特效设置
effect++;
if(effect>6)effect=0;
OV2640_Special_Effects(effect);//设置特效
sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
break;
case WKUP_PRES: //1:1尺寸(显示真实尺寸)/缩放
scale=!scale;
if(scale==0)
{
if(lcddev.id == 0X5510)
{
SCCB_WR_Reg(0xd3,0x02);
}
OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
OV2640_OutSize_Set(lcddev.width,lcddev.height);
sprintf((char*)msgbuf,"Full Size 1:1");
}else
{
OV2640_ImageWin_Set(0,0,1600,1200); //全尺寸缩放
OV2640_OutSize_Set(lcddev.width,lcddev.height);
sprintf((char*)msgbuf,"Scale");
}
break;
}
LCD_ShowString(30,50,210,16,16,msgbuf);//显示提示内容
delay_ms(800);
DCMI_Start();//重新开始传输
}
if (hsync_int) //刚刚产生帧中断,可以延时
{
delay_ms(10);
hsync_int = 0;
}
}
}
rgb565_test 函数将 OV2640 设置为 RGB565 模式,并将接收到的数据,直接传送给 LCD,处理 过程完全由硬件实现,CPU 完全不用理会。main 函数就相对简单了。
int main(void)
{
u8 key;
u8 t;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
usart2_init(42,115200); //初始化串口2波特率为115200
LED_Init(); //初始化LED
LCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次
usmart_dev.init(84); //初始化USMART
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"OV2640 TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/5/14");
while(OV2640_Init())//初始化OV2640
{
LCD_ShowString(30,130,240,16,16,"OV2640 ERR");
delay_ms(200);
LCD_Fill(30,130,239,170,WHITE);
delay_ms(200);
}
LCD_ShowString(30,130,200,16,16,"OV2640 OK");
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES) //RGB565模式
{
ov2640_mode=0;
break;
}else if(key==KEY1_PRES) //JPEG模式
{
ov2640_mode=1;
break;
}
t++;
if(t==100)LCD_ShowString(30,150,230,16,16,"KEY0:RGB565 KEY1:JPEG"); //闪烁显示提示信息
if(t==200)
{
LCD_Fill(30,150,210,150+16,WHITE);
t=0;
}
delay_ms(5);
}
if(ov2640_mode)jpeg_test();
else rgb565_test();
}