一种DMA的双缓冲区不间断传输结构
例程建立在stm32f446re平台,讲述gc0308使用硬件dcmi外设和dma采集传输加以解析的过程。可以达到一边采集传输一边解析图像的目的。
局限:虽然可以做到采集运输和图像解析,但是图像解析处理函数的用时要求比较严格,必须在采集和传输的总时长内完成解析对于单片机的运行频率比较高,因为硬件采用了180m的主频加以拥有硬件fpu所以图像处理的速度极快,实测在100us左右。gc0308的帧输出频率在100fps左右所以采集加传输的时间在10ms左右。
主函数的图像解析函数调用
/*DCMI->DMA*/
DCMI_DMA_Init((u32)&yuv0buff,YUVSIZE,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置
/*DCMI->DMA启动传输*/
DCMI_Start();
/* Infinite loop */
while (1)
{
/*图像转换解析标记*/
imgbuff_Get();
}
//DCMI DMA配置
//DMA_Memory0BaseAddr:存储器地址 将要存储摄像头数据的内存地址(也可以是外设地址)
//DMA_BufferSize:存储器长度 0~65535
//DMA_MemoryDataSize:存储器位宽
//DMA_MemoryDataSize:存储器位宽 @defgroup DMA_memory_data_size :DMA_MemoryDataSize_Byte/DMA_MemoryDataSize_HalfWord/DMA_MemoryDataSize_Word
//DMA_MemoryInc:存储器增长方式 @defgroup DMA_memory_incremented_mode /** @defgroup DMA_memory_incremented_mode : DMA_MemoryInc_Enable/DMA_MemoryInc_Disable
void DCMI_DMA_Init(u32 DMA_Memory0BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
DMA_DeInit(DMA2_Stream1);
while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_1; //通道1 DCMI通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DR
DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式
DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:32位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream
// DMA_DoubleBufferModeCmd(DMA2_Stream1,ENABLE);//双缓冲模式
// DMA_MemoryTargetConfig(DMA2_Stream1,DMA_Memory1BaseAddr,DMA_Memory_1);//配置目标地址1
DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);//开启传输完成中断
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
dma的中断处理函数
//void (*dcmi_rx_callback)(void);//DCMI DMA接收回调函数
DMA2_Stream1中断服务函数(仅双缓冲模式会用到)
void DMA2_Stream1_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_Stream1,DMA_FLAG_TCIF1)==SET)//DMA2_Steam1,传输完成标志
{
DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1);//清除传输完成中断
// dcmi_rx_callback(); //执行摄像头接收回调函数,读取数据等操作在这里面处理
yuv_data_process();
}
}
dma缓冲区分配
当然这里采用了直接定义数组的方式也可以使用动态内存分配的方式使得内存空间更加灵活和紧凑
#define YUVSIZE 3*160 //因为dma采用的是32位的传输方式 所以当像素为16位需要适当选择正确的缓冲区大小
uint32_t yuv0buff[YUVSIZE];
uint32_t yuv1buff[YUVSIZE];
uint8_t yuv_data_ok = 0;
u8 anaok = 0;
/***************************************************************
* 函数名:yuv_data_process
* 输入 :void
* 输出 :void
* 功能 :将此函数放置在dma中断中 每次dma传输完成时进入并操作传输和传出信号量告知图像处理函数可以处理
可以做到一帧在解析时硬件dcmi和dma可以实时工作 现将gc0308时钟设置为36m高于额定时钟频率24m但图像将偏暗
实测帧间隔为10ms 因图像的解析和打脚频率也为10ms。
gc0308图像配置为24*480再做1/4抽点缩小所以实际图像为6*160
****************************************************************/
void yuv_data_process(void)
{
// u16 i;
u16 rlen;//剩余数据长度
// u32 *pbuf;
if(anaok == 1)
{return;}
if(yuv_data_ok==0) //图像数据还未采集完?
{
DMA_Cmd(DMA2_Stream1,DISABLE); //停止当前传输
while(DMA_GetCmdStatus(DMA2_Stream1) != DISABLE); //等待DMA2_Stream1可配置
rlen=YUVSIZE-DMA_GetCurrDataCounter(DMA2_Stream1);//得到剩余数据长度
DCMI_DMA_Init((u32)&yuv1buff,YUVSIZE,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置
yuv_data_ok=1; //标记图像数据采集完按成,等待其他函数处理
anaok = 1;
DCMI_Start(); //启动传输
}
else if(yuv_data_ok==1) //上一次的图像数据已经被处理了
{
DMA_Cmd(DMA2_Stream1,DISABLE); //停止当前传输
while(DMA_GetCmdStatus(DMA2_Stream1) != DISABLE); //等待DMA2_Stream1可配置
rlen=YUVSIZE-DMA_GetCurrDataCounter(DMA2_Stream1);//得到剩余数据长度
DCMI_DMA_Init((u32)&yuv0buff,YUVSIZE,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置
yuv_data_ok=2; //标记数据未采集
anaok = 1;
DCMI_Start(); //启动传输
}
}
图像解析函数详述
uint8_t imgbuff[6][160];
/***************************************************************
* 函数名:imgbuff_Get
* 输入 :void
* 输出 :void
* 功能 :此函数将根据yuv_data_ok的值不同而选择不同的缓存区图像作为解析对象
将图像yuv格式转为灰度并取灰度的高字节(可选择二值化和发送图像)
****************************************************************/
void imgbuff_Get(void)
{
u16 i,j;
u16 colorh,colorl;
u16 color_r,color_g,color_b;
u8 *p;
p=(u8*)imgbuff;
/*采集和DMA传输完成*/
if(yuv_data_ok && anaok)
{
/*DMA传输目标缓冲区读取选择*/
if(yuv_data_ok == 1)pbuf = yuv0buff;
else if(yuv_data_ok == 2){pbuf = yuv1buff;yuv_data_ok = 0;}
/*停止传输*/
DCMI_Stop();
/*
图像自定解析位置
*/
/*LED闪烁*/
GPIOA->ODR ^= GPIO_Pin_10;
// yuv_data_ok = 0;
anaok = 0;
/*启动传输*/
DCMI_Start();
}
}