【GD32F350 都市青年家庭安防卫士 】第八贴 GD32F350如何接收OV5640数据
[复制链接]
本帖最后由 传媒学子 于 2018-10-12 21:15 编辑
【GD32F350 都市青年家庭安防卫士 】第八贴 GD32F350如何接收OV5640数据
前言
说实话,我在如何驱动上边花费了太多时间,开始自己懒,想着用GPIO读写看能不能驱动,试了很多次,结论是no. 因为,CPU处理一条指令或者一条复杂的指令,至少需要好几个周期,那么这个时间延迟,是很难捕捉到数据的。 最后,不得不硬着头皮,采用中断+DMA的方法做。 一、如何降低OV5640的PCLK速率
实际上,降低OV5640的频率是可行的。首先,你要看懂OV5640的手册,见附件1。
这图片中的寄存器,囊括了ov5640内部所有的时钟信号。
PLL1中的第二级,是一个 A ?B :C的语句,如果A成立,那么B,否则C。
其它的难度不大,但要注意从每一级的最小频率。另外,左下角的标注也要注意,YUV传输,PCLK要大于两倍的SCLK。
看懂了上图,就可以自己配置寄存器。
建议,参照网上野火和战舰的资料,或者去网上搜索一些寄存器配置,也可以参考我的,见附件2.这部分不再详细叙述。
二、GD32如何接收OV5640输出数据
我设定OV5640输出YUV4:2:0格式,我的算法只用到Y信号,输出格式是YYYY / YUYV。
这里稍微解释一下输出格式,因为关系到你得到正确数据。以160X120分辨率为例。YYYY是一行,另一行是YUYV。 YYYY的那一行是160Bytes,那么YUYV的那一行则是320Bytes. 这就是所谓的YUV 4:2:0.
所以,手册上说,YUV输出的RGB格式可以在最后输出2分频,但是YUV就不行,原因就在于此。
说了,这么多,还是需要你自己去体会。
下面说如何利用GD32F350的DMA传输。
ETI很好写,长中断和行中断分别连接2个引脚,此时千万不要尝试用中断来接收PCLK,因为中断响应时间也有几个系统周期,那么你根本采集不到想要的数据。
PCLK的速率高,用GPIO读也是困难的。
我是直接用DMA传输, TIMERA 定时器捕捉PCLK来实现的。
我刚开始很犹豫,因为我看了兆易创新给的手册,DMA中断源根本没有GPIO引脚,后来才明白,需要通过TImerA捕捉。
GD32的定时器真的很强大,捕捉功能还带滤波功能。
上代码:
- void config_timer(void)
- {
- //PA1复用
- gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_1);//PCLK
- gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_1);
- //TIM1
- rcu_periph_clock_enable(RCU_TIMER1);
- TIM_TimeBaseStructure.prescaler = 0;
- TIM_TimeBaseStructure.alignedmode = TIMER_COUNTER_EDGE;
- TIM_TimeBaseStructure.counterdirection = TIMER_COUNTER_UP;
- TIM_TimeBaseStructure.period = 2;// 从0~200
- TIM_TimeBaseStructure.clockdivision = TIMER_CKDIV_DIV1;
- TIM_TimeBaseStructure.repetitioncounter = 0;
- timer_init(TIMER1, &TIM_TimeBaseStructure);
-
- TIM_ICInitStructure.icpolarity = TIMER_IC_POLARITY_RISING;
- TIM_ICInitStructure.icselection = TIMER_IC_SELECTION_DIRECTTI;
- TIM_ICInitStructure.icprescaler = TIMER_IC_PSC_DIV2;//摄像头PCLK2分频,一行取80个点
- TIM_ICInitStructure.icfilter = 0;
-
- timer_input_capture_config(TIMER1,TIMER_CH_1, &TIM_ICInitStructure);
- timer_dma_enable(TIMER1, TIMER_DMA_CH1D);
- timer_enable(TIMER1);
- }
复制代码
这里是DMA代码:
- void config_dma(void)
- {
- rcu_periph_clock_enable(RCU_DMA);
- DMA_InitStructure.periph_addr = (uint32_t) &GPIO_ISTAT(GPIOC);//需要加&
- DMA_InitStructure.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
- DMA_InitStructure.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
- DMA_InitStructure.memory_addr = (uint32_t) data_buf;//不需要加&
- DMA_InitStructure.memory_width = DMA_MEMORY_WIDTH_16BIT;
- DMA_InitStructure.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
- DMA_InitStructure.direction = DMA_PERIPHERAL_TO_MEMORY;
- DMA_InitStructure.number = 4050;
- DMA_InitStructure.priority = DMA_PRIORITY_ULTRA_HIGH;
-
- dma_init(DMA_CH2, DMA_InitStructure);
- //dma_circulation_disable(DMA_CH2);
- dma_circulation_enable(DMA_CH2);
- // dma_channel_enable(DMA_CH2);
- }
复制代码
DMA和TIMER 只需初始化就行了,CPU完全不参与,太实用了。
代码中:data _buf的定义如下:
- __IO uint16_t data_buf[4050]={0};
复制代码
我采集的图像不大, 就是81*50, 如何大得很,SDRM就放不下了,需要放到FLASH中,这个没尝试,大家有兴趣的可以尝试一下。
今天的分享就到这里了。谢谢~
最后指出一个坑,避免大家再次进入这个坑:- /****************************************************************************************************
- //开始尝试用CPU查询GPIO的方法进行图像采集,尝试了1周,最终失败,因为
- //由于while在处理的过程中,至少会占据CPU大量周期,因此,采用此方法无法得到正确的摄像头数据**********/
- void camera_receive(void)
- {
- //image_GPIO_init();//初始化摄像头输入GPIO
-
- while(v_switch)
- {
- while(!Camera_VS);//上降沿
- vs_i++;
- if(vs_i == 2)
- {
- v_switch = 0;
- h_switch = 1;
- //printf("vs_i:%d\n", vs_i);
- //此时进入第二帧,camera_vs=1;
- //printf("vs:%d\n", Camera_VS);
-
- }
- if(vs_i != 2)
- while(Camera_VS);
- }
-
- while(h_switch)
- {
- if(Camera_VS == 0)
- {
- h_switch = 0;
- //v_switch = 1;
- //printf("hs_i:%d\n", hs_i);
- for (p_i=0;p_i<1200;p_i++) //2值化处理
- {
- if(p_i%40==0) printf("\n");
- printf("%x ",(uint8_t)((data_buf[p_i]>>6)&0x00ff));
- }
- }
- if(hs_i != 120) //否则会陷入死循环 因为HREF一直是低电平,所以会一直陷入while()中
- {
- while(!Camera_HS);
- if(hs_i%4 == 0) //30行
- {
- while(p_i < (160*hs_i))
- {
- while(!Camera_PCLK);
- if(((p_i++)%4) == 2) //取出YUYU中的第一个Y 40个点
- {data_buf[p_j++] = (uint16_t)GPIO_ISTAT(GPIOC);}
- while(Camera_PCLK);
- }
- }
- hs_i++;
- while(Camera_HS);
- }
- }
- }
复制代码
|