【GD32F350 都市青年家庭安防卫士 】第七贴 I2C驱动OV5640
[复制链接]
本帖最后由 传媒学子 于 2018-10-8 22:29 编辑
【GD32F350 都市青年家庭安防卫士 】第七贴 I2C驱动OV5640
很多朋友认为I2C驱动摄像头很简单,确实不难,但对于一个新手,如何使用硬件I2C,或者软件I2C驱动一个寄存器是16位的摄像头就不是很容易的事情了。我们这款开发板上边的I2C接口貌似被按键等占用了,因此想使用硬件I2C,需对硬件电路进行小范围改进,去掉电阻,这里我就直接采用软件I2C。
很多人玩过0V7620,0V2640等摄像头,包括OV5640网上已有的例程,基本上都是用stm32自带的DCMI接口进行图像收集的,这个GD32是不存在的,是不是说GD32F350 low了点,NO, arm架构下的Timer + DMA+外部中断可以实现DCMI的功能。
今天呢,先讨论如何驱动OV5640。
我以新手的视角来讲解如何驱动这款16位寄存器的摄像头。
一、OV5640介绍
这款摄像头内部寄存器比较多,基本上你可以在网上找到一些资料。
但是,我建议大家学习一下SCCB协议,虽然与I2C差不多,但操作16为寄存器,写法可能需要注意小一些东西。
你想让摄像头工作,必须首先配置摄像头内部的寄存器。这款摄像头内部有光照传感阵列,ADC转换,ISP 自带DSP处理电路等,可以输出RGB、YUV等主流视频格式,支持JPEG压缩输出,像素可达500W。
首先,我们的目的是读取到摄像头的ID号:
- if((0x56 == SCCBReadByte(0x300A)) && (0x40 == SCCBReadByte(0x300B)))
- printf("\r\n***摄像头已启动***");
- else
- printf("\r\n,未检测到摄像头,或者摄像头异常!");
复制代码
也就是: 0x300A, 0x56; 0x40 0x300B; 读到这两个数,说明你的I2C协议是正确的,你可以继续下一步,否则,你要不断调试,直到读到这两个数。
二、软件I2C和SCCB协议
SCCB协议可以通过小幅度修改I2C协议来实现。
这部分不想多说了,直接上代码,需要指出的是,在GD32说明中,有一句话,值得注意:
这句话的意思是,你将GPIO设定为输出 OD 上拉时,可以通过读取输入寄存器得到此时端口的值。
这句话太方便了,我们可以直接设定I2C管脚为输出,当需要读取从机设备发来的数据,则直接读取输入寄存器,而不必将管脚设置为输入,就像你当初用51单片机来模拟I2C一样即可。
好了,首先我们需要定义两个GPIO,作为I2C的SCLK和SDA, 直接上代码,这个都是基于我前边发的帖子引用的官方固件库。
- #define SDA_H gpio_bit_set(GPIOB, GPIO_PIN_13)
- #define SDA_L gpio_bit_reset(GPIOB, GPIO_PIN_13)
- #define SCL_H gpio_bit_set(GPIOB, GPIO_PIN_14)
- #define SCL_L gpio_bit_reset(GPIOB, GPIO_PIN_14)
- #define RST_H gpio_bit_set(GPIOB, GPIO_PIN_15)
- #define RST_L gpio_bit_reset(GPIOB, GPIO_PIN_15)
- #define PWDN_H gpio_bit_set(GPIOB, GPIO_PIN_12)
- #define PWDN_L gpio_bit_reset(GPIOB, GPIO_PIN_12)
- //在开漏输出模式下,对端口输入状态寄存器的读访问将返回I/O的状态,因此 不需要为了读取数据,专门将I/0由输出设为输入
- #define SDA_read gpio_input_bit_get(GPIOB, GPIO_PIN_13)
复制代码
GPIO初始化:
- void I2C_GPIO_Configuration(void)
- {
- gpio_deinit(GPIOB);
- /* 模拟I2C PB13--SDA PB14-SCL */
- gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_13); //设定GPI0B上拉输出,上拉稳定
- gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_13);//GPIOBP OD,速度最大50MHZ
- gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_14);
- gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_14);
- //初始化摄像头RST PB4 PWDN PB5
- gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_15);
- gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_15);
- gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_12);
- gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12);
- /* enable GPIOB clock */
- rcu_periph_clock_enable(RCU_GPIOB);
- }
复制代码
然后,我们需要进行通信,delay很重要,你要根据你的系统时钟,来设置合适的delay。我的系统频率为108MHz.
- void I2C_delay(void) //Delay
- {
- uint32_t t=300*13.5; //300/8000=37.5us; 如果是108MHz,则乘以13.5
-
- while(t--);
-
- }
- void nops1ms(void) //1ms
- {
- uint32_t t=8000*13.5; //8000/8000=1ms; 如果是108MHz,则乘以13.5
- while(t--);
- }
- void delay_50ms(void) //50ms
- {
- uint t=50;
- for(t=0;t<50;t++)
- nops1ms();
- }
复制代码
然后就是I2C启动,停止:
I2C启动:
- int I2C_Start(void)
- {
-
- SDA_H;
- SCL_H;//高电平有效
- I2C_delay();//延时
- //查看此时SDA是否就绪(高电平)
- if(!SDA_read)
- {
- printf("\r\nSDA线为低电平,总线忙,退出\r\n");
- return DISABLE;//SDA总线忙,退出
- }
- //制造一个下降沿,下降沿是开始的标志
- SDA_L;
- I2C_delay();
- //查看此时SDA已经变为低电平
- if(SDA_read)
- {
- printf("\r\nSDA线为高电平,总线出错,退出\r\n");
- return DISABLE;//SDA总线忙,退出
- }
- SCL_L;
- return ENABLE;
- }
复制代码
I2C停止:
- void I2C_Stop(void)
- {
-
- SCL_L;
- //制造一个上升沿,上升沿是结束的标志
- SDA_L;
- SCL_H;//高电平有效
- I2C_delay();//延时
- SDA_H;
- I2C_delay();
- }
复制代码
其它代码我放在附件中了,感兴趣的可以去看,我的代码都是经过测试的,本着开源的精神,贡献出来,希望能对大家学习有些许作用,由于时间紧急,我没有对代码进行过多的注释和优化,比较乱,但是亲测没有问题。
三、用I2C模拟SCCB操作OV5640
首先,I2C启动首先会写从机地址+读写标志,5640的地址是 0x78,写的时候,我们要0x78, 读的时候我们要0x79;
具体读写请参考我写的代码,I2C是从网上找的,感谢这位哥们,很多部分经过我修改而成。
这是读操作,16位地址。
- uchar SCCBReadByte(uint Addr)
- {
- uchar data;
- //I2C_Initializes();
- I2C_Start();
- I2C_SendByte(0x78);
-
- I2C_GetAck();//不必在乎是否应答
- I2C_SendByte((uint8_t)((Addr>>8) & 0xFF) );
- I2C_GetAck();//不必在乎是否应答
- I2C_SendByte( (uint8_t)(Addr & 0xFF) );
- I2C_GetAck();//不必在乎是否应答
-
- I2C_Start();//不必在乎是否应答
- I2C_SendByte(0x79);
-
- I2C_GetAck();//不必在乎是否应答
- data = I2C_ReadByte(1);
- I2C_Stop();
-
- return data;
- }
复制代码
这是写操作:
- void OV5640_WriteReg(uint16_t Addr, uint8_t Data)//测试过没有问题
- {
- I2C_Start();
- I2C_SendByte(0x78);
-
- I2C_GetAck();//不必在乎是否应答
- I2C_SendByte((uint8_t)((Addr>>8) & 0xFF) );
- I2C_GetAck();//不必在乎是否应答
- I2C_SendByte( (uint8_t)(Addr & 0xFF) );
- I2C_GetAck();//不必在乎是否应答
-
- I2C_SendByte(Data);
- I2C_GetAck();//不必在乎是否应答
-
- I2C_Stop();
- }
复制代码
备注:代码仅供参考,附件中的SCCB不包含void OV5640_WriteReg(uint16_t Addr, uint8_t Data)这个函数,我把这个函数放在0v5640的读写中了,你可以自行添加到SCCB.h中,如果你想要下载我整个工程,请等待我最后的作品提交贴。
夜深了,我也要休息了,明天还得上班,打工。。。
|