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);//关中断,不捕获
}
}
}
```