社区导航

 
查看: 844|回复: 3

[经验分享] 【GD32F350 都市青年家庭安防卫士 】第七贴 I2C驱动OV5640

[复制链接]

339

TA的帖子

1

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2018-10-8 22:26:07 | 显示全部楼层 |阅读模式
本帖最后由 传媒学子 于 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号:

  1. if((0x56 == SCCBReadByte(0x300A)) && (0x40 == SCCBReadByte(0x300B)))
  2.     printf("\r\n***摄像头已启动***");
  3.                 else
  4.           printf("\r\n,未检测到摄像头,或者摄像头异常!");
复制代码

也就是: 0x300A, 0x56;  0x40 0x300B;  读到这两个数,说明你的I2C协议是正确的,你可以继续下一步,否则,你要不断调试,直到读到这两个数。



二、软件I2C和SCCB协议

SCCB协议可以通过小幅度修改I2C协议来实现。
这部分不想多说了,直接上代码,需要指出的是,在GD32说明中,有一句话,值得注意:
GPIO.JPG
这句话的意思是,你将GPIO设定为输出 OD 上拉时,可以通过读取输入寄存器得到此时端口的值。
这句话太方便了,我们可以直接设定I2C管脚为输出,当需要读取从机设备发来的数据,则直接读取输入寄存器,而不必将管脚设置为输入,就像你当初用51单片机来模拟I2C一样即可。

好了,首先我们需要定义两个GPIO,作为I2C的SCLK和SDA, 直接上代码,这个都是基于我前边发的帖子引用的官方固件库。

  1. #define SDA_H  gpio_bit_set(GPIOB, GPIO_PIN_13)  
  2. #define SDA_L  gpio_bit_reset(GPIOB, GPIO_PIN_13)   

  3. #define SCL_H  gpio_bit_set(GPIOB, GPIO_PIN_14)  
  4. #define SCL_L  gpio_bit_reset(GPIOB, GPIO_PIN_14)  

  5. #define RST_H  gpio_bit_set(GPIOB, GPIO_PIN_15)  
  6. #define RST_L  gpio_bit_reset(GPIOB, GPIO_PIN_15)  

  7. #define PWDN_H  gpio_bit_set(GPIOB, GPIO_PIN_12)  
  8. #define PWDN_L  gpio_bit_reset(GPIOB, GPIO_PIN_12)

  9. //在开漏输出模式下,对端口输入状态寄存器的读访问将返回I/O的状态,因此 不需要为了读取数据,专门将I/0由输出设为输入
  10. #define  SDA_read           gpio_input_bit_get(GPIOB, GPIO_PIN_13)
复制代码

GPIO初始化:
  1. void I2C_GPIO_Configuration(void)
  2. {
  3.      gpio_deinit(GPIOB);
  4.   /* 模拟I2C PB13--SDA  PB14-SCL */
  5.     gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_13); //设定GPI0B上拉输出,上拉稳定
  6.     gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_13);//GPIOBP OD,速度最大50MHZ
  7.     gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_14);
  8.     gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_14);
  9.         //初始化摄像头RST PB4    PWDN PB5
  10.           gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_15);
  11.           gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_15);
  12.           gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,GPIO_PIN_12);
  13.           gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12);
  14.          /* enable GPIOB clock */
  15.     rcu_periph_clock_enable(RCU_GPIOB);
  16. }
复制代码



然后,我们需要进行通信,delay很重要,你要根据你的系统时钟,来设置合适的delay。我的系统频率为108MHz.
  1. void I2C_delay(void)                         //Delay
  2. {
  3.     uint32_t t=300*13.5;                        //300/8000=37.5us; 如果是108MHz,则乘以13.5
  4.    
  5.          while(t--);
  6.         
  7. }

  8. void nops1ms(void) //1ms
  9. {
  10.     uint32_t t=8000*13.5;     //8000/8000=1ms; 如果是108MHz,则乘以13.5
  11.           while(t--);
  12. }

  13. void delay_50ms(void) //50ms
  14. {
  15.   uint t=50;   
  16.         for(t=0;t<50;t++)
  17.         nops1ms();
  18. }
复制代码



然后就是I2C启动,停止:
I2C启动:
  1. int I2C_Start(void)
  2. {
  3.         
  4.         SDA_H;
  5.         SCL_H;//高电平有效
  6.         I2C_delay();//延时
  7.         //查看此时SDA是否就绪(高电平)
  8.         if(!SDA_read)
  9.         {
  10.                 printf("\r\nSDA线为低电平,总线忙,退出\r\n");
  11.                 return DISABLE;//SDA总线忙,退出
  12.         }
  13.         //制造一个下降沿,下降沿是开始的标志
  14.         SDA_L;
  15.         I2C_delay();
  16.         //查看此时SDA已经变为低电平
  17.         if(SDA_read)
  18.         {
  19.                 printf("\r\nSDA线为高电平,总线出错,退出\r\n");
  20.                 return DISABLE;//SDA总线忙,退出
  21.         }
  22.         SCL_L;
  23.         return ENABLE;
  24. }
复制代码




I2C停止:
  1. void I2C_Stop(void)
  2. {
  3.         
  4.         SCL_L;
  5.         //制造一个上升沿,上升沿是结束的标志
  6.         SDA_L;        
  7.         SCL_H;//高电平有效
  8.         I2C_delay();//延时
  9.         SDA_H;
  10.         I2C_delay();
  11. }
复制代码



其它代码我放在附件中了,感兴趣的可以去看,我的代码都是经过测试的,本着开源的精神,贡献出来,希望能对大家学习有些许作用,由于时间紧急,我没有对代码进行过多的注释和优化,比较乱,但是亲测没有问题。



三、用I2C模拟SCCB操作OV5640
首先,I2C启动首先会写从机地址+读写标志,5640的地址是 0x78,写的时候,我们要0x78, 读的时候我们要0x79;
具体读写请参考我写的代码,I2C是从网上找的,感谢这位哥们,很多部分经过我修改而成。
这是读操作,16位地址。
  1. uchar SCCBReadByte(uint Addr)
  2. {
  3.         uchar data;
  4.         //I2C_Initializes();
  5.         I2C_Start();
  6.         I2C_SendByte(0x78);
  7.         
  8.         I2C_GetAck();//不必在乎是否应答
  9.         I2C_SendByte((uint8_t)((Addr>>8) & 0xFF) );
  10.         I2C_GetAck();//不必在乎是否应答
  11.   I2C_SendByte( (uint8_t)(Addr & 0xFF) );
  12.         I2C_GetAck();//不必在乎是否应答
  13.         
  14.         I2C_Start();//不必在乎是否应答
  15.         I2C_SendByte(0x79);
  16.         
  17.         I2C_GetAck();//不必在乎是否应答
  18.         data = I2C_ReadByte(1);
  19.   I2C_Stop();
  20.         
  21.         return data;        
  22. }
复制代码



这是写操作:
  1. void OV5640_WriteReg(uint16_t Addr, uint8_t Data)//测试过没有问题
  2. {
  3.         I2C_Start();
  4.         I2C_SendByte(0x78);
  5.         
  6.         I2C_GetAck();//不必在乎是否应答
  7.         I2C_SendByte((uint8_t)((Addr>>8) & 0xFF) );
  8.         I2C_GetAck();//不必在乎是否应答
  9.   I2C_SendByte( (uint8_t)(Addr & 0xFF) );
  10.         I2C_GetAck();//不必在乎是否应答
  11.         

  12.         I2C_SendByte(Data);
  13.         I2C_GetAck();//不必在乎是否应答
  14.         
  15.   I2C_Stop();
  16. }
复制代码





备注:代码仅供参考,附件中的SCCB不包含void OV5640_WriteReg(uint16_t Addr, uint8_t Data)这个函数,我把这个函数放在0v5640的读写中了,你可以自行添加到SCCB.h中,如果你想要下载我整个工程,请等待我最后的作品提交贴。
夜深了,我也要休息了,明天还得上班,打工。。。




此帖出自GD32 MCU论坛

sccb.c

5.27 KB, 下载次数: 4

SCCB.h

575 Bytes, 下载次数: 0



回复

使用道具 举报

942

TA的帖子

2

TA的资源

版主

Rank: 6Rank: 6

发表于 2018-10-9 08:34:29 | 显示全部楼层
不错,谢谢分享,加油!
专注智能产品的研究与开发,专注于电子电路的生产与制造……QQ:2912615383,电子爱好者群: 422240210


回复

使用道具 举报

52

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2018-10-9 19:47:25 | 显示全部楼层
GD32F350的内存真的够放图片吗?

点评

16KB的SDRAM 可以放下大小为 4050个byte 的图像2幅左右吧。 不过128KB的flash,可以放很多张。 另外,摄像头本身可以输出JPEG压缩图像,也就不到10KB,完全没压力把。 鉴于一般的图像处理,甚至普通的工业应用  详情 回复 发表于 2018-10-9 21:56


回复

使用道具 举报

339

TA的帖子

1

TA的资源

一粒金砂(中级)

Rank: 2

 楼主| 发表于 2018-10-9 21:56:28 | 显示全部楼层
serialworld 发表于 2018-10-9 19:47
GD32F350的内存真的够放图片吗?

16KB的SDRAM 可以放下大小为 4050个byte 的图像2幅左右吧。 不过128KB的flash,可以放很多张。

另外,摄像头本身可以输出JPEG压缩图像,也就不到10KB,完全没压力把。

鉴于一般的图像处理,甚至普通的工业应用,完全可以。

如果你要存储带色彩的高清图片,或者处理这种,你可以外接SDRM,FLASH等。 DMA传输速度很快,108Mhz的系统主频,也是不错的。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

Archiver|手机版|小黑屋|电子工程世界 ( 京ICP证 060456 )

GMT+8, 2018-12-10 21:20 , Processed in 0.140567 second(s), 18 queries , Gzip On, MemCache On.

快速回复 返回顶部 返回列表