赤兔板CH32V307基于OV2640摄像头模块图片传输
前言
新的学习项目又来啦!此次带来的是CH32V307赤菟板的外接的OV2640摄像头模块JTEG模式相关学习笔记。这次的项目实际上是对之前的教程的补充,解决大家对于JPEG模式图片传输的疑问。老规矩,基础薄弱的同学可以移步论坛的赤菟教程目录:传送门1,教程本篇:传送门2。本篇会用到板子上的外接端口,OV2640,UartDisplay。
DVP接口相关
DVP即Digital Video Port,数字视频接口位于赤菟开发板右侧TF卡插槽的下方,能够兼容OV2640、OV5640等多种使用DVP接口的摄像头,同时芯片也内置了DVP数据采集模块,用于视频或图像数据的接收。本次仍然使用的OV2640,但内容同样适用于其他兼容型号。
这次项目的代码是基于DVP_UART这一例程修改而来,即沁恒在CH32V307EVT中所提供的关于DVP接口的使用例程。
DVP接口定义上的相关问题
具体介绍请参照本篇:传送门
在本篇下同学们学习DVP_UART例程时发现输出数据中并没有0xFFD9的开头和0xFFD8的结尾,这是因为开发板上只连接了低10位(D9-D0),他的连接方法参照下图。
开发板按照10bit连接的,而我们JPEG模式下取数据是取红框的8位数据,所以首先我们得到的数据要占2个字节,其次通过串口传输前必须要右移两位,这样才能得到正确的图像数据。
显示和保存图像
在JPEG模式下发送的数据在UartDisplay上可以正常显示。
该软件可以在各个网站上下到,参考链接:传送门
在该软件下直接选择串口,确定好波特率,打开串口即可接收图像,在右下方勾选图像保存到文件即可将你接收到的图像保存到你选择的路径下。具体演示之后体现。
必要准备
该部分将罗列同学们可能会碰到的问题和函数说明。
选择正确的UARTx
在CH32V307EVT中所提供的关于DVP接口的使用例程中,如果你直接提取来用会发现在debug.h中他选择的是UART1,而实际传输图像数据的是UART2,这里是可能导致数据出错的地方。
选择图片像素
由于传输速率的问题,在这里的像素选择上不建议沿用例程中的1024*768,为了保证速度和质量设置的小一点方便试验。
主程序
#include "debug.h"
#include "ov.h"
/*
*@Note
DVP操作OV2640摄像头JPEG模式例程:
通过UART2(PA2)输出图片数据,可通过串口图像软件显示图片,或从一帧数据中 取 0xFF,0xD8开头;
0xFF ,0xD9结尾的数据,修改文件格式,可显示图片。
注:使用UART2(PA2)串口输出,将debug.h中 #define DEBUG DEBUG_UART2
UART1(PA9)被DVP占用
*/
/* DVP Work Mode */
#define JPEG_MODE 1
/* DVP Work Mode Selection */
#define DVP_Work_Mode JPEG_MODE
UINT32 JPEG_DVPDMAaddr0 = 0x20005000;
UINT32 JPEG_DVPDMAaddr1 = 0x20005000 + OV2640_JPEG_WIDTH*2;//每一字节数据实际占两字节RAM
UINT32 RGB565_DVPDMAaddr0 = 0x20005000;
UINT32 RGB565_DVPDMAaddr1 = 0x20005000 + RGB565_COL_NUM;
volatile UINT32 frame_cnt = 0;
volatile UINT32 addr_cnt = 0;
volatile UINT32 href_cnt = 0;
void DVP_IRQHandler (void) __attribute__((interrupt("WCH-Interrupt-fast")));
/*******************************************************************************
* Function Name : UART2_Send_Byte
* Description : UART2 send one byte data.
* Input : t: UART send Data.
* Return : None
*******************************************************************************/
void UART2_Send_Byte(u8 t)
{
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);// 等待上次传输完成
USART_SendData(USART2, t);
}
/*******************************************************************************
* Function Name : DVP_Init
* Description : Init DVP
* Input : None
* Return : None
*******************************************************************************/
void DVP_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DVP, ENABLE);// 打开DVP模块的时钟信号
DVP->CR0 &= ~RB_DVP_MSK_DAT_MOD;// 清除DVP配置寄存器CR0
#if (DVP_Work_Mode == JPEG_MODE)
DVP->CR0 |= RB_DVP_D10_MOD | RB_DVP_V_POLAR | RB_DVP_JPEG;// 设置DVP的工作模式(10bit位宽),同步信号极性(低有效的VSYNC信号),打开DVP的JPEG模式
DVP->CR1 &= ~(RB_DVP_ALL_CLR| RB_DVP_RCV_CLR);// 配置寄存器CR0清除DVP缓存和标志位
DVP->COL_NUM = OV2640_JPEG_WIDTH;// 设置图片信号宽度
/* 配置DMA的目标地址 */
DVP->DMA_BUF0 = JPEG_DVPDMAaddr0; //DMA addr0
DVP->DMA_BUF1 = JPEG_DVPDMAaddr1; //DMA addr1
#endif
/* 设置DVP帧捕获率 */
DVP->CR1 &= ~RB_DVP_FCRC;
DVP->CR1 |= DVP_RATE_25P ;//DVP_RATE_25P; //25%
//Interupt Enable
DVP->IER |= RB_DVP_IE_STP_FRM;
DVP->IER |= RB_DVP_IE_FIFO_OV;
DVP->IER |= RB_DVP_IE_FRM_DONE;
DVP->IER |= RB_DVP_IE_ROW_DONE;
DVP->IER |= RB_DVP_IE_STR_FRM;
NVIC_InitStructure.NVIC_IRQChannel = DVP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DVP->CR1 |= RB_DVP_DMA_EN; //enable DMA
DVP->CR0 |= RB_DVP_ENABLE; //enable DVP
}
u32 DVP_ROW_cnt=0;
/*******************************************************************************
* Function Name : DVP_IRQHandler
* Description : This function handles DVP exception.
* Input : None
* Return : None
*******************************************************************************/
void DVP_IRQHandler(void)
{
if (DVP->IFR & RB_DVP_IF_ROW_DONE)
{
/* Write 0 clear 0 */
DVP->IFR &= ~RB_DVP_IF_ROW_DONE; //clear Interrupt
#if (DVP_Work_Mode == JPEG_MODE)
href_cnt++;
if (addr_cnt%2) //buf1 done
{
addr_cnt++;
DVP->DMA_BUF1 += OV2640_JPEG_WIDTH *4;
}
else //buf0 done
{
addr_cnt++;
DVP->DMA_BUF0 += OV2640_JPEG_WIDTH *4;
}
#endif
}
if (DVP->IFR & RB_DVP_IF_FRM_DONE)
{
DVP->IFR &= ~RB_DVP_IF_FRM_DONE; //clear Interrupt
#if (DVP_Work_Mode == JPEG_MODE)
DVP->CR0 &= ~RB_DVP_ENABLE; //disable DVP
//Use uart2 send JPEG data.
{
UINT32 i;
UINT16 val;
href_cnt = href_cnt*OV2640_JPEG_WIDTH;
for(i=0; i<href_cnt; i++){
val = *(UINT16*)(0x20005000+i*2);
UART2_Send_Byte((UINT8)(val>>2));// | 0xC0);//((val>>4)|(val));
}
}
DVP->CR0 |= RB_DVP_ENABLE; //enable DVP
DVP->DMA_BUF0 = JPEG_DVPDMAaddr0; //DMA addr0
DVP->DMA_BUF1 = JPEG_DVPDMAaddr1; //DMA addr1
href_cnt = 0;
addr_cnt =0;
#endif
}
if (DVP->IFR & RB_DVP_IF_STR_FRM)
{
DVP->IFR &= ~RB_DVP_IF_STR_FRM; //clear Interrupt
frame_cnt++;
}
if (DVP->IFR & RB_DVP_IF_STP_FRM)
{
DVP->IFR &= ~RB_DVP_IF_STP_FRM; //clear Interrupt
}
if (DVP->IFR & RB_DVP_IF_FIFO_OV)
{
DVP->IFR &= ~RB_DVP_IF_FIFO_OV; //clear Interrupt
printf("FIFO OV\r\n");
}
}
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None PA2 - UART2
* Return : None
*******************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
Delay_Init();
USART_Printf_Init(115200);// 设置UART2的波特率
printf("SystemClk:%d\r\n",SystemCoreClock);
while(OV2640_Init())
{
printf("Camera Model Err\r\n");
Delay_Ms(1000);
}
Delay_Ms(1000);
RGB565_Mode_Init();
Delay_Ms(1000);
#if (DVP_Work_Mode == JPEG_MODE)
printf("JPEG_MODE\r\n");
JPEG_Mode_Init();
Delay_Ms(1000);
#endif
DVP_Init();
while(1);
}
这就是这次项目所要用到的主程序,接下来对其进行说明。
头文件与参数定义
#include "debug.h"
#include "ov.h"
/* DVP Work Mode */
#define JPEG_MODE 1
/* DVP Work Mode Selection */
#define DVP_Work_Mode JPEG_MODE
UINT32 JPEG_DVPDMAaddr0 = 0x20005000;
UINT32 JPEG_DVPDMAaddr1 = 0x20005000 + OV2640_JPEG_WIDTH*2;//每一字节数据实际占两字节RAM
UINT32 RGB565_DVPDMAaddr0 = 0x20005000;
UINT32 RGB565_DVPDMAaddr1 = 0x20005000 + RGB565_COL_NUM;
volatile UINT32 frame_cnt = 0;
volatile UINT32 addr_cnt = 0;
volatile UINT32 href_cnt = 0;
这部分和教程本篇的区别就在于选定的工作模式是JPEG模式,同时正如之前所提及到的采集到的实际上是D9D2之上的数据,所以行像素占的地址要乘2,即将对应JPEG位宽乘2。
UART传输数据函数
void UART2_Send_Byte(u8 t)
{
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);// 等待上次传输完成
USART_SendData(USART2, t);
}
该部分为库中自带的函数构成的函数,作用就是把数据通过UART2传输进串口。
DVP相关函数
void DVP_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DVP, ENABLE);// 打开DVP模块的时钟信号
DVP->CR0 &= ~RB_DVP_MSK_DAT_MOD;// 清除DVP配置寄存器CR0
#if (DVP_Work_Mode == JPEG_MODE)
DVP->CR0 |= RB_DVP_D10_MOD | RB_DVP_V_POLAR | RB_DVP_JPEG;// 设置DVP的工作模式(10bit位宽),同步信号极性(低有效的VSYNC信号),打开DVP的JPEG模式
DVP->CR1 &= ~(RB_DVP_ALL_CLR| RB_DVP_RCV_CLR);// 配置寄存器CR0清除DVP缓存和标志位
DVP->COL_NUM = OV2640_JPEG_WIDTH;// 设置图片信号宽度
/* 配置DMA的目标地址 */
DVP->DMA_BUF0 = JPEG_DVPDMAaddr0; //DMA addr0
DVP->DMA_BUF1 = JPEG_DVPDMAaddr1; //DMA addr1
#endif
/* 设置DVP帧捕获率 */
DVP->CR1 &= ~RB_DVP_FCRC;
DVP->CR1 |= DVP_RATE_25P ;//DVP_RATE_25P; //25%
//Interupt Enable
DVP->IER |= RB_DVP_IE_STP_FRM;
DVP->IER |= RB_DVP_IE_FIFO_OV;
DVP->IER |= RB_DVP_IE_FRM_DONE;
DVP->IER |= RB_DVP_IE_ROW_DONE;
DVP->IER |= RB_DVP_IE_STR_FRM;
NVIC_InitStructure.NVIC_IRQChannel = DVP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DVP->CR1 |= RB_DVP_DMA_EN; //enable DMA
DVP->CR0 |= RB_DVP_ENABLE; //enable DVP
}
u32 DVP_ROW_cnt=0;
void DVP_IRQHandler(void)
{
if (DVP->IFR & RB_DVP_IF_ROW_DONE)
{
/* Write 0 clear 0 */
DVP->IFR &= ~RB_DVP_IF_ROW_DONE; //clear Interrupt
#if (DVP_Work_Mode == JPEG_MODE)
href_cnt++;
if (addr_cnt%2) //buf1 done
{
addr_cnt++;
DVP->DMA_BUF1 += OV2640_JPEG_WIDTH *4;
}
else //buf0 done
{
addr_cnt++;
DVP->DMA_BUF0 += OV2640_JPEG_WIDTH *4;
}
#endif
}
if (DVP->IFR & RB_DVP_IF_FRM_DONE)
{
DVP->IFR &= ~RB_DVP_IF_FRM_DONE; //clear Interrupt
#if (DVP_Work_Mode == JPEG_MODE)
DVP->CR0 &= ~RB_DVP_ENABLE; //disable DVP
//Use uart2 send JPEG data.
{
UINT32 i;
UINT16 val;
href_cnt = href_cnt*OV2640_JPEG_WIDTH;
for(i=0; i<href_cnt; i++){
val = *(UINT16*)(0x20005000+i*2);
UART2_Send_Byte((UINT8)(val>>2));// | 0xC0);//((val>>4)|(val));
}
}
DVP->CR0 |= RB_DVP_ENABLE; //enable DVP
DVP->DMA_BUF0 = JPEG_DVPDMAaddr0; //DMA addr0
DVP->DMA_BUF1 = JPEG_DVPDMAaddr1; //DMA addr1
href_cnt = 0;
addr_cnt =0;
#endif
}
if (DVP->IFR & RB_DVP_IF_STR_FRM)
{
DVP->IFR &= ~RB_DVP_IF_STR_FRM; //clear Interrupt
frame_cnt++;
}
if (DVP->IFR & RB_DVP_IF_STP_FRM)
{
DVP->IFR &= ~RB_DVP_IF_STP_FRM; //clear Interrupt
}
if (DVP->IFR & RB_DVP_IF_FIFO_OV)
{
DVP->IFR &= ~RB_DVP_IF_FIFO_OV; //clear Interrupt
printf("FIFO OV\r\n");
}
}
JPEG模式中仅将教程本篇中的RGB565改为JPEG即可,并且图像无需裁剪。其中:
{
UINT32 i;
UINT16 val;
href_cnt = href_cnt*OV2640_JPEG_WIDTH;
for(i=0; i<href_cnt; i++){
val = *(UINT16*)(0x20005000+i*2);
UART2_Send_Byte((UINT8)(val>>2));// | 0xC0);//((val>>4)|(val));
}
}
这里即为将图像数据右移两位传输到串口的代码。
main函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
Delay_Init();
USART_Printf_Init(115200);// 设置UART2的波特率
printf("SystemClk:%d\r\n",SystemCoreClock);
while(OV2640_Init())
{
printf("Camera Model Err\r\n");
Delay_Ms(1000);
}
Delay_Ms(1000);
RGB565_Mode_Init();
Delay_Ms(1000);
#if (DVP_Work_Mode == JPEG_MODE)
printf("JPEG_MODE\r\n");
JPEG_Mode_Init();
Delay_Ms(1000);
#endif
DVP_Init();
while(1);
}
main函数中需要注意的就是波特率,可以修改来改变传输速率,也会影响图像输出,这里我们选用的是115200,基本不会出错。
显示效果
烧录好程序之后,选择波特率115200,打开串口,即可从软件右边显示出来。
选择好图像保存到文件的目录之后,就可以在选定目录下找到保存的.jpg的文件了。
后话
到此对OV2640摄像头的JPEG模式的学习笔记就结束了,这次的学习笔记回答一些教程本篇遇到的问题,并且补充了JPEG模式的例程,希望这篇教程对您的赤菟板外接的OV2640摄像头的的学习有所帮助。
老规矩,这次项目所涉及到的程序和之前一样放到了gitee仓库上文件名为:
欢迎大家参考!