之前参加学校的比赛曾经用过摄像头,不过当时时间不多,配置摄像头时直接用的是网上的sccb驱动,是大神crazybingo写的那个,自行百度ov7670摄像头彻底解读便能找到,而此次由于项目要求要把摄像头配置这一模块独立出来,需要把寄存器值写进去然后再一个一个读出来进行比对,那只好自己设计一个sccb驱动了。
Sccb和i2c差别不大,具体可百度(话说都可以百度出来的话我还写这个干嘛。。。),提供几个我认为有用的链接吧, 零基础sccb完全开发手册 http://wenku.baidu.com/link?url=2EF9rOeVFaFDnJmuu31qbDF61rVDq00f_I1jImoy5k1pm63X8lejWNvTSMPOqrmXLP9V0XTjyY1qmk_s3yf7JPpkoP-uYBzdCnXTNUeJny7 ,基于FPGA的OV摄像头初始化之SCCB协议的实现http://www.61ic.com/FPGA/Altera/201504/52884.html 。
话说代码不是最重要的,重要的是对协议的理解,所以我只是说一下我的设计思路,其实也是参考上面第二个链接设计的。
由于个人不喜欢用那种通过将state加1来进行状态转换的用法,就像链接里面的那种,我喜欢的是那种满足条件就跳转,否则就维持的用法,比如,一个sccb的写过程可以简化为 开始 ---> 写ID地址 ---> 写寄存器地址 ---> 写数据 ---> 结束。这样一个写过程的状态机就可以设计成 等待使能(WAIT_EN) -> START -> W_IDADDR -> W_SUBADDR -> W_DATA -> STOP。同样读过程状态转换为 等待使能(WAIT_EN) -> START -> W_IDADDR -> W_SUBADDR -> STOP -> START -> W_IDADDR -> R_DATA -> STOP 。可以看到上面两个过程的中间有几个状态是重合的,由于读写在这些过程中要写的数据都是一样的,所以读写的过程可以共用这些状态,两个过程中不一样的地方是读过程在第一次stop之后又要开始Start进入第二相得读操作,而写操作不用,那就可以在STOP状态的最后进行判断下一状态是应该跳到等待下一个使能,还是重新发start信号进行读取寄存器值的操作(实际最后由于时序上的要求,我在STOP之后都是跳到等待使能的状态重新把时钟线和数据线都拉高,然后在WAIT_EN状态进行判断的,《由于我的读/写使能只拉高时钟周期,而且控制模块要受到我发出的读/写完毕信号后才发下一个使能,所以不存在此时恰好又收到一个使能信号而打断读过程的问题》),这样,我就需要增加一个信号phase_flag来指示读过程的第二段操作,即在写寄存器地址W_SUBADDR状态的最后判断是否将此信号拉高在R_DATA状态后再把它拉低,这样在WAIT_EN状态如果检测到此信号为1则立刻跳到START进行读过程的第二段操作。好了,整个状态机的转换就是下面这样
代码的话就是这样:
里面一些信号的意义
sccb_en: 读/写使能,维持一个时钟周期,收到此信号执行一次读/写周期。
phase_flag: 上面已经说了。
w_flag: 一个sclk周期低电平的中间,也就是数据线sdat在此标志为高的时刻改变。如下,PERIOD与sclk频率有关,如要以100kHz读写的话,一个周期为10us,模块工作时钟为50MHz,周期为20ns,则要计数的PERIOD=10000/20=500
data_cnt: 读/写数据计数,从0到8共9个sclk周期,sclk就是时钟线。在w_flag标志为高时加1。这样要写到sdat线上的数据就可以根据data_cnt的值来决定。
注意,sdat是双向口,用三态门来决定何时释放总线。
下面来看看modelsim的仿真图
这是仿真的第一个START 和W_IDADDR状态,在sclk低电平的中间改变data_cnt加1,改变数据,sclk高低平期间保持稳定,仿真成功地发送了ID地址0x42。其中在data_cnt==8时,即第9个sclk周期释放总线,读取应答信号ack,所以sdat为高阻态。其实,这里的双向口也可以仿真,模拟回复一个低电平的ack信号让fpga读取,不过之前有段时间一直在纠结双向口该如何仿真的问题,其实如果在RTL图里看过综合出来的三态门,要仿真也是很简单的,打算另外详细说一下。
好了,不多说,上板验证,上signaltap
捕捉到的写ID地址时的波形,sccb_en拉高一个周期高电平后,进入START状态,sdat在sclk为高电平期间拉低,之后送ID地址0x42,注意data_cnt 为8时,sdat_en为低电平,说明此时fpga已释放sdat的控制权,此时从机摄像头把sdat拉低作为ack,可以看到在data_cnt为8期间sclk高电平之后sdat有一个时间很短的高电平,说明此时从机摄像头在sclk高电平之后也释放了对sdat的控制权,此时sdat由上拉电阻拉高为高电平。
来个读状态,注意在data_cnt为8期间,此时应该由主控制器fpga发送一个高电平作为NA信号,最后是一个结束信号,sdat在sclk为高电平期间拉高。
再来一张读过程中一个stop信号后接一个start信号开始读过程的第二段,注意sdat_en信号,此过程中主控制器FPGA不释放sdat的控制权,sdat一个拉高的结束标志之后紧接着一个拉低的开始标志,而且要注意这期间sclk要一直为高,否则接下来的读操作将会失败。
——————————————————————————————————————————————————————————————————————————————
好了,接下来就该说说调试过程中遇到的问题了。
第一个就是上电阶段由于系统时钟还不稳定,不应该立刻启动状态机,不知道是不是这个原因,也不是很确定,看图
这个不是sccb驱动模块里面的状态机,而是给我送使能信号sccb_en的另一个模块的状态机,可以看到(延时让我注释掉了),它一上电就立刻跳转,给我一个sccb_en的使能信号,结果就如上面捕捉到的波形,一下子来了一连串的sccb_en信号(正常情况下它在给我一个使能信号后要收到我发出的end信号后再发下一个使能),结果是在data_cnt为8时从机并没有给回正确的ack(低电平),写数据失败。
第二个还是要仔细看datasheet啊,
总线释放到下一个开始信号之间要间隔至少1.3us,fpga外部晶振50M,一个周期才20ns,之前就是在收到end信号后隔几个时钟就发下一个使能,才100多ns,结果老是写不成功。。。。
最后,说一个很郁闷的失误,本来我们是打算用ov2640来调试的,结果死活找不到,只能用ov7670了,本来这没什么的,但区别就在ov2640那个是内部自带工作时钟的,而ov7670则需要从xclk引脚输入工作时钟,结果我们没有给它一个xclk就愣是在那调了好久说怎么捕捉到的波形时序是对的,摄像头总是不应答呢,呃,不多说,最后还是潘哥提醒了才知道,估计潘哥特无语,惭愧,惭愧。。。。。
好了,就这么多吧,挺简单的一个协议,结果搞了好久,学艺不精啊。。。。。