STM32 IO口直接采集无FIFO摄像头方法总结

1.概述
采用无数字口的单片机采集摄像头数据,通过模拟摄像头的采集时序进行图像采集,采集速度完全取决于单片机IO口的翻转速度和CPU处理速度;
硬件:stm32f401/411/405,M4内核,带DSP/FPU单精度浮点运算单元,当然F103也能用,但不建议用F103,因为RAM资源和内核,主频等等都跟不上,性能太差没有实际意义;
摄像头以OV7670无FIFO为例,最大像素640x480;
2.采集原理
(1)摄像头时序:
PCLK :像素时钟,一个像素时钟出一个8bit数据
XCLK:摄像头外部时钟,用于给摄像头提供时钟信号;
HSYNC:行同步信号,代表一行数据的起始,触发这个信号就开始一行图像的采集;
VSYNC:场同步信号,代表一场数据的起始,触发这个信号就开始一帧图像采集;
DATA 7-0:数据位bit7-bit0,一个RGB565的数据由两个data像素组成,从高到低依次传输
SCL&SDA:摄像头控制接口,寄存器配置,符合I2C/SCCB时序
RST :摄像头硬件复位信号,默认拉高
PWDN:上电时序相关,默认拉低
摄像头时钟24MHz 30帧/s,12MHz,15帧/s,可通过设置时钟分频和内部倍频机制合理控制时钟;
在这里插入图片描述
在这里插入图片描述

(2)IO配置
两种采集方式:
第一种采用全部普通GPIO模式,通过GPIO模拟摄像头时序,然后手动读取D0-D7的数据,尽量全部采集寄存器处理和指针处理方式会更快;
第二种采用定时器输入捕获+中断处理+DMA传输方式采集,控制行场信号,然后使用DMA搬运每行的数据;
(3)摄像头采集流程
第一种:GPIO直接采集

int liencnt=0;
int pixcnt=0;
unsigned char* pimage=NULL;
pimage=mymalloc(SRAMIN,cam_length);
mymemset(pimage,0,cam_length);//根据实际情况分配buffer大小,如果超过RAM,只能分配一行的大小,每采集一行就送显
while(1)
{
    
    
	unsigned char* pimg_temp=pimage;
	while(OV_VSYNC==1)//场信号为高时阻塞
	while(OV_VSYNC==0)//场信号为低开始采集
	{
    
    
		for (linecnt=0;linecnt<cam_height;linecnt++)
		{
    
    
			while(OV_HSYNC==0);  //行信号为高时采集有效数据,为低时是无效数据,阻塞处理
			// while(OV_HSYNC==1);
			for(pixcnt=0;pixcnt<cam_width*2;pixcnt++)
			{
    
    
				while(OV_PCLK==0);                     //在PCLK下降沿读取数据
				*pimg_temp=GPIOC->IDR&0XFF; //每次取8位数据
				while(OV_PCLK==1);
				pimg_temp++;
			}			
			/*
			for(pixcnt=0;pixcnt<cam_width*2;pixcnt++)
			{
				//UART_SEND(pimage,cam_length);  //一行一行送显
			}
			*/
		}	
	}
}

int sensor_snapshot(image_t *image)
{
    
    

	int liencnt=0;
	int pixcnt=0;
	unsigned char* pimage=NULL;
	 unsigned char* pimg_temp=image->data;
	 while(OV_VSYNC==1)//场信号为高时阻塞,这种方法采用丢帧操作
	 while(OV_VSYNC==0)//场信号为低开始采集
	 {
    
    
	  	for (linecnt=0;linecnt<cam_height;linecnt++)
	  	{
    
    
	   		while(OV_HSYNC==0);  //行信号为高时采集有效数据,为低时是无效数据,阻塞处理
	   		// while(OV_HSYNC==1);
	  		 for(pixcnt=0;pixcnt<cam_width*2;pixcnt++)
	  	 	{
    
    
	   		 	while(OV_PCLK==0);                     //在PCLK下降沿读取数据
	    			*pimg_temp=GPIOC->IDR&0XFF; //每次取8位数据
	    			while(OV_PCLK==1);
	   		 	pimg_temp++;
	   		}   
	 	}
	}
	//一帧图像采集完成,开始作批量拷贝操作
	
	return 0;
}

	```
	
	第二种:中断+DMA采集
	
	场中断:低电平有效
行中断:定时器输入捕获

```c
static void VSYNC_Exit_Init(void)
{
    
    

	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  			
        GPIO_Init(GPIOB, &GPIO_InitStructure);     
        
	EXTI_InitTypeDef EXTI_InitStructure;
	//初始化中断脚复用时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	//配置中断源
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource7);
	
	EXTI_ClearITPendingBit(EXTI_Line7);
	EXTI_InitStructure.EXTI_Line = EXTI_Line7;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rasing;  //上升沿代表一帧采集结束,低电平为有效像素
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_InitTypeDef NVIC_InitStructure ;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;		//通道设置为外部中断线
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; 	//中断抢占先等级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;           	//中断响应优先级
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           	//打开中断
        NVIC_Init(&NVIC_InitStructure);                             
}
void TIM4_Cap_Init(u16 arr,u16 psc)
{
    
    	 

	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;	
        NVIC_InitTypeDef NVIC_InitStructure;
   	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);	//使能TIM4时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //使能GPIOB时钟	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);		
	
	//初始化定时器4	 
	TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; 	//预分频器   
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  
	//初始化TIM4输入捕获参数
	TIM4_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 	选择输入端 IC1映射到TI1上
	TIM4_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//上升沿捕获
	TIM4_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
	TIM4_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 //配置输入分频,不分频 
	TIM4_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM4, &TIM4_ICInitStructure);

//中断分组初始化

	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 	
	TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断		
	TIM_Cmd(TIM4,ENABLE ); 	//使能定时器5
	
}
	
	void 	TIM4_DMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpaddr,u32 cmaddr,u32 cndtr)
{
    
    
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//打开DMA1时钟	

        DMA_DeInit(DMA_CHx);   
        DMA_InitStructure.DMA_Channel=DMA_Channel_2;
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpaddr;//外设地址 
	DMA_InitStructure.DMA_MemoryBaseAddr = cmaddr;  //内存地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //传输方向外设到内存
	DMA_InitStructure.DMA_BufferSize = cndtr;  //传输数量
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不自增  
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据大小为半字节
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据大小为半字节
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  
	DMA_Init(DMA_CHx, &DMA_InitStructure);  
  	
} 
void capture_start(void)
{
    
    
	EXIT_ITConfig(EXIT_Line7,ENABLE);//开中断
	TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
	TIM_DMACmd(TIM4, TIM_DMA_CC1, ENABLE);
}
void capture_stop(void)
{
    
    	
 	EXIT_ITConfig(EXIT_Line7,DISABLE);//关中断
 	TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC1,DISABLE);//关中断,不捕获
 	TIM_DMACmd(TIM4, TIM_DMA_CC1,DISABLE);
}



//PCLK---A4
//XCLK---A8
//VSYNC---B7
//HREF---B6 
//D0-D7---C0-C7
//SCL---B8
//SDA---B9
//PWND---B4
//RST---B5

int linecnt=0;
void EXIT9_5_IRQHandler(void)
{
    
    
	if(EXTI_GetITStatus(EXIT_Line7)==SET)  //一帧图像采集完
	{
    
    
		EXIT_ClearITPendingBit(EXIT_Line7);
		capture_stop();
		read_ok=1;//采集完成标志
		frame++;
		linecnt=0;		
	}
}
//TIM4_CH1  DMA请求DMA1_Stream_0  channel_2
//捕获上升沿
void TIM4_IRQHandler(void)
{
    
    
	if(TIM_GetITStatus(TIM4,TIM_IT_CC1)!=RESET)		//发生一次捕获事件,开始DAM传输
	{
    
    
		TIM_ClearITPendingBit(TIM4, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
		TIM_SetCounter(TIM4,0);//重新计数;
		if(linecnt <=cam_height)   
		{
    
    
			//这里没有采用DMA传输完成中断,如果采用中断处理,可以在回调函数中处理图像格式,F4支持双DMA缓存
			while(DMA_GetFlagStatus(DMA1_FLAG_TC2)==RESET);//等待DMA1传输完成
			DMA_ClearFlag(DMA1_FLAG_TC2);//清除标志
			//DMA采集每改变一次地址是需要将DMA失能后才能改变      
			DMA1_Stream0->CR &= (uint32_t)~DMA_SxCR_EN;//失能
			DMA1_Stream0->M0AR = (uint32_t)&(Image[linecnt++]);  //新的一行地址 
			DMA1_Stream0->CR |= DMA_SxCR_EN;  //使能
		}    
		if(linecnt >cam_height)    
		{
    
      
			DMA1_Stream0->CR &= (uint32_t)~DMA_SxCR_EN; 
			DMA_Cmd(DMA1_Stream0, DISABLE);
			TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC1,DISABLE);//关中断,不捕获
		}
	}
}
		```
		
	
	
	
	
	
	

猜你喜欢

转载自blog.csdn.net/weixin_40672861/article/details/122452486