对I2C总线时序的一点理解以及ACK和NACK(NAK)

7307阅读 0评论2012-01-17 378333581
分类:

关键字: i2c ,IIC,bus, ACK,NACK,NAK,SDA,SCL,timing,master,slaver,时序,响应,总线


关于i2c的响应问题:对于每一个接收设备(从设备,slaver),当它被寻址后,都要求在接收到每一个字节后产生一个响应。因此,the master device 必须产生一个额外的时钟脉冲(第九个脉冲)用以和这个响应位相关联。

在这个脉冲期间,发出响应的从设备必须将SDA拉低并在时钟脉冲的高电平期间保持住。这表示该设备给出了一个ACK。如果它不拉低SDA线,就表示不响应(NACK)。

另外,在从机(发送方)发送完最后一个字节后主设备(接收方)必须产生一个不响应位,用以通知从机(发送方)不要再发送信息了,这样从机就知道该将SDA释放了,而后,主机发出一个停止位给slaver。

 

总结下,i2c通讯中,SDA 和 SCL 都是有主机控制的,从设备只是能够将SDA线拉低而已。对于SCL线,从机是没有任何能力去控制的。从机只能被动跟随SCL

再说的清楚些:

主机发送数据到从机的状态下:主机控制SCL信号线和SDA信号线,从机只是在SCL线为高的时候去被动读取SDA线。

主机读取从机的数据:主机来发出时钟信号,从机只是保证在时钟信号为高电平的时候的SDA的状态而已。

//----------------------------------------

补充@201108311142

SDA和SCL已经通过上拉电阻被上拉,master可以控制(拉低或者释放)这两条线,而slaver只能控制SDA线。当master发送数据时,master会适时地将SDA和SCL拉低或释放(拉高)。确切的时序应该是这样的:


当mater要发送一个start时,mater会将SDA拉低,这就可以了,因为此时的SCL一定是High。好了,一个start就这样发出去了。而slaver也会发现这个start信号的发生,slaver便会准备好接收接下来的数据了。紧接着,master要发送一个Byte的数据了,一位一位的发出这8个bits。这时master会先将SCL拉低,然后在SCL为低的状态下将一个bit准备好放到SDA上(比如要发送一个 0,master就会通过拉低SDA来放好这个0),然后master会把SCL拉高(释放),此时slaver会立刻检测到SCL的变化,由此聪明的slaver便知道master已经将要发送的那个bit准备好了,slaver便会在这个SCL的高电平期间尽快(maser不会等你很久的哦)去读取一下SDA,嗯读到了一个0,slaver就把这个0放到自己的移位寄存器中待后续处理。master会在一个设定好的时间后把SCL再次拉低,然后在SCL为低电平期间把下一个bit放到SDA上,然后再把SCL拉高,然后slaver在SCL的高电平期间再去读SDA。。。。。如此反复8次,一个Byte的传输便告结束。当这8个bit发完后,SCL是处于低电平的(被master拉低的),SDA是出于高电平的(master已经释放了SDA)。


当一个字节发送完毕后,master会释放SDA(拉高)并拉低SCL,此时slaver如果打算发出一个ACK的话,它必须在这个SCL被master拉低的短暂时间内去主动将SDA拉低并保持住 (此前我们说过,SDA此时已经被master释放,所以slaver才有机会去拉低这个SDA)。master会在一个确定的时间后再次将SCL拉高,并在拉高的期间去读取SDA线的状态,如果读到低电平,则认为收到了来自slaver的响应(ACK),否则认为slaver没有响应(NACK)刚才发送的那一个Byte。这个过程就是我们说的i2c通讯中的第9个时钟周期。当master读完这个ACK / NACK 后,会再次将SCL拉低,用以通知slaver:第9个时钟周期已经结束,你现在可以释放SDA了。而此时master也可以向SDA上准备下一个Byte的第一个bit。继而重复上述过程。。。。。或者,master也许想在接下来发送一个stop过去,那么master会在这个SCL为低的时间内将SDA拉低,而后再将SCL拉高,在SCL为高的期间再将SDA释放 (拉高) 。这样,一个STOP位就产生了。你会发现此后的SDA和SCL都是高,这就是是所谓的总线空闲了!


一句话:SCL是单向的,由master控制。而SDA是双向的,master可以控制,slaver也可以控制。


Yasin Lee@201109091400

昨天在调试mma8452q的过程中发现一个新的问题:

我一直以为用i2c读取slaver的某个寄存器的过程是:1.向slaver写一个寄存器地址过去。2.读取slaver。就是这样两步,可是这样的想法在读取mma8452q时却出现了问题,总是无法读到正确的数据。上述两步我是通过这样的方法实现的:1.调用i2c_master_send发送一个字节的数据(寄存器地址)过去。2.调用i2c_master_recv读取一个字节。完毕。之所以这么做是基于这样的想法:当向slaver写入(发送)一个寄存器地址过去后,slaver就会把当前的读写指针(假想的)指向这个寄存器,此后,读取的时候自然是读到这个寄存器的值了。

可是这样的想法不行,考虑到,这个假设可能不对,我便直接使用i2c_transfer来进行操作,因为这个函数可以实现在由写入状态切换到读取状体的过程中不发送stop,也就是直接再次发送一个start,即Restart。

问题解决,看来对mma8452q的读取操作必须经由restart来做中间的切换。而不能在切换过程中发送stop命令。而先前采用的分部操作在每一步完成后都有一个i2c stop命令发生,所以出了问题。

上一篇:gnuplot 使用技巧
下一篇:开漏(open drain)和开集(open collector) 概念