|
通讯协议是干什么用的?它是各设备包括单片机以及各种外设之间用于传输数据的人为规则。 前面的12864屏,我们采用的是一次传输一个字节8位二进制数的方式来传输数据的,这种传输方式的极其明显的缺点是占用的单片机的端口太多,本身引脚也太多而难以缩小体积,于是有聪明人想到了一次只传输一位数据的方法,这个区别于一次多位的方法,业界称之为串行通讯方式,而一次多位的方式称之为并行通讯方式。 串行的方式有很多种,从连接线的多少来分,可以分为一线、两线、三线等,I2C属于2线方式即它只用到了单片机的两个引脚。 这里补充一个名词:通讯通道用线称之为总线,一线称之为单总线模式,两线称之为双总线模式。 不同于同是两线方式的串口通讯协议,I2C方式的两根线一根用于传输数据,另一根由主器件(控制方,另一方则称之为从器件)来控制发出指挥信号。 器件之间的数据传输工作并非时时刻刻都在进行,它是只在需要时才会进行,所以,数据传输有几个必然的过程:发出确定开始的信号、确定参与传输的器件、确定输入与输出方、传输数据、完成后发出停止信号以终止本次传输工作。 对于开始信号,I2C总线的规则是在总线不工作时,总线上所有的器件都交出对这两根线的控制权即都输出高电位,同时监测SDA线上电位。当其中一个器件想要开始数据传输工作时,它的第一个动作就是拉低SDA的电位,这个信号就是开始信号,总线上的其它器件一监测到这个信号,就立即作出接收数据的准备。在这里,发出信号的器件被称之为主器件(主机),它随后会控制住SCL和SDA这两条线上电位的高低,直到它发出停止信号交出对这两条线的控制权。 接下来的事,就是开始传输数据了,这个时候,理所当然地,数据发送方为主器件,数据接收方为从器件(从机)。 在数据位发送的顺序上,I2C协议的规定是高位在前,低位顺序在后,所以,它最后发送的是一字节中的第0位。 在I2C通讯协议中,对主从双方有一个规则,就是数据发送方只在SCL为低电位时向SDA上放置数据,数据接收方只在SCL为高电位时读取SDA上的数据,所以,既然要开始数据传输了,那第一步就是主器件拉低SCL的电位;第二步就是主器件向SDA上放置一个电位高低数据;第三步告诉从器件这个SDA上的数据放置好了你可以读取这个数据了,这一步的实现就是主器件拉高SCL的电位,而从器件监测到SCL电位从低变成高了,就按协议的规定读取SDA上的电位值,这样第一位数据就被从器件读取了;第四步就该由主器件再次往SDA上放置一位数据了,这时先做的仍然是由主器件拉低SCL的电位,然后放置数据。其它的6位数据都按这个方法来进行。当第八位数据被从器件读取完成时,两根线的状态是SCL=1,SDA为主器件放置的最后一位数据的值,这个时候主器件将暂时不再向SDA上放置数据,而是要看看从器件是否成功收到了这8位一字节数据,从而决定下一步怎么做。 从器件在读取数据时,它内部是设计有一个计数器的,每读取一位就计一个数,当它读取完成8位数时,它会产生一个完成标志值,在规则中,这个标志值会由从器件决定是否返馈给主器件以告诉主器件这个从器件已收到完整的8位一字节数据,这个反馈的方式依然依靠SDA线,也就是从器件在SDA上放置这个信号数据,在放置之前,它必须要先获取对SDA的控制权,所以,主器件这时就要先交出对SDA的控制权,即主器件要向SDA输出高电位。根据I2C总线数据传输过程中只有当SCL为低电位时,SDA上的电位才可以改变的规则,在主器件向SDA输出高电位之前,主器件还得先把SCL的电位拉低。 综合说来,从器件向主器件反馈收到信号的流程是:主器件先将SCL拉低,SCL拉低之后,主器件的动作是交出SDA的控制权即向SDA输出高电位,从器件的动作则是将反馈信号放置在SDA上(这两个动作可能同时进行),接下来是主器件拉高SCL,再接着主器件按规则在高电位时读取SDA,主器件收到这个信号之后就可以开始判断它是否是从器件发出的反馈信号。 有关这个反馈信号的形式,我们知道要么是高电位,要么是低电位。要想区别是不是从器件发出的信号,那这个信号的形式就只能是低电位,所以,当主器件读取到的SDA为低电位时,就可以确定是从器件发出的信号,这样就可以确认从器件已收到一字节完整的数据。 以上这个过程,规范的称呼是“应答”两个字,而这个反馈信号,称之为应答信号。 应答如果成功了则SDA的电位为低,但这也意味着这时SDA的控制权还在从器件手中,所以它得交出控制权,这样主器件才能做接下来的事。方法依然是从器件向SDA输出高电位,这个输出的时间依然遵从只有当SCL只为低电位时才能改变SDA电位的规则,所以,在主器件完成应答信号接收之后,它要把SCL拉低,这样从器件就知道这时可以拉高SDA的电位以交出对SDA的控制权了,这样一个完成的应答过程才算完成。 应答完成之后,主器件可以继续数据传输,也可以发出停止信号以终止本次传输工作,发出停止信号的规则是当SCL处于高电位时,SDA改变电位而且是从低到高的改变,完成后主器件就交出了对这两条线的控制权。实现的流程是:对于有应答的传输,因为应答流程结束时,SCL处于低电位,所以就应该在这个时候将SDA的电位拉低,然后必须要让SCL成为高电位,然后再按照规则拉高SDA以发出停止信号。 以下划重点:SCL为高电位时,如果主器件从高拉低SDA,则从器件会判断为收到起始信号;如果主器件从低拉高SDA,则从器件会判断为收到停止信号。所以,在数据传输及应答期间,当SCL为高电位时,不要改变SDA的电位高低,这个原则,从器件在应答期间,对SDA的控制同样遵从。这也意味着,在任何时候,只要是在SCL处于高电位时发生SDA从高到低的变化,那么I2C总线就会重新启动;只要是在SCL处于高电位时发生SCL低到高的变化,就会终止本次传输。 I2C协议的数据发送时序图如下,怎么说呢,我所看到的所有教材上的时序图,反正没看到一个完整准确的,所以,我专门用心画了这个图: 这里要注意I2C的一个很重要的规则:它的数据位的传输顺序是高位在前,低位在后,如上图所示。 有关数据发送与应答的规则就说到这里,下面我们对这部分规则开展实验,实验的主要目的,是观察主器件能否收到正确的应答信号,以及在应答完成之后,从器件是否能正常地交出对SDA的控制权。实验的方法,我们来个骚操作,不用任何仪器,就用一个LED接到SDA上来观察,LED的正极接SDA,负极串一个电阻接地,这样当SDA为低电位时则LED不亮,高电位时亮。 I2C器件有很多,这里我们用教材中最常用的24C02存储器芯片来做实验,这个芯片没有其它用途,它只是用来存储数据,我们的实验,就是先向其中写入数据,然后将写入的数据读出并显示出来,看看是否与写入的数据一样。 任何通讯协议的功能,它只有传输数据这一项功能,你别想着它能直接实现什么功能。至于接收方收到数据后去干什么,如果是外设器件,那它是有固定规则的,不同种类的I2C器件有不同的规则;如果是单片机的话,那就看程序是怎么处理的,也就是说,由你来决定。 还有一点,同一条I2C总线上是可以存在多个器件包括单片机的,理论上任何器件都可以成为主器件也可以成为从器件,主器件可以同时向所有器件发送数据,也可以只与其中一个器件展开通讯。当只与一个器件展开通讯时,就得采取定位措施了。 固定功能的I2C器件是有器件分类编码的,24C02这类器件的分类编码为二进制数1010;它还有一个为三位二进制数的地址值,这个地址值由器件上1~3号引脚的电位高低来决定,使用时由用户自己来确定;器件还要确定其被操作时是写入还是被读取,这个方向的表示方法是二进制0表示写入、1表示读取。这前后八位数正好组成一个字节数,用于主器件定位从器件,这八位数的排列顺序,I2C协议规定,四位分类码在前,紧跟着的是其三位地址值,最后一位是读写方向位。单片机对任何I2C器件的操作,首先都是依据这八位数进行定位并确定读写方向。 对于单片机与24C02写入通讯来说,单片机向其输入的第一字节数据,是其地址加一个写入方向位数据,也就是10100000B即0xA0,第二个字节的数据则是24C02内部存储单元的地址,它内部最多可以存储256个字节数据,所以它的地址值是从0到255。 如果我们给它输入的第一个字节为10100001B,那就是要读取这个器件的内部的数据,因为向它给出的指令是读取,所以这个指令之后不能向其输入改变其内部任何内容的数据,包括其内部的地址指针和存储内容。这个系列的器件的读取位置取决于器件内部的地址指针的当前值,如果你在读取数据前不修改其内部的地址指针的话,则每次启动读取动作获得的其内部存储单元的地址,都是上一次读或写操作最后一个单元的地址+1,所以,如果你想读取某一指定地址的数据,你得先修改其内部的地址指针,修改的方式就是直接写入这个地址值,即启动总线、以写入模式定位该器件、写入这个地址、终止本次传输。 在读取数据的过程中,SDA的电位由从器件控制即从器件往SDA上放数据,这时,主器件就得交出对SDA的控制权即主器件向SDA输出高电位。从器件在放置数据时,同样遵从只有在SCL低电位时才能改变SDA电位的规则,也就是说,当从器件检测到SCL为低电位时,才能往SDA上面放数据,而当SCL为高电位时,从器件并不能改变SDA的电位,这也意味着,在读取数据时,你既可以在SCL低电位时也可以在其高电位时读取SDA上面的数据,但是,规范的做法,是在SCL高电位时进行读取,这个时候数据是稳定的。 读操作时,先输入器件内部地址指针数据——收到应答信号——直接重新启动总线的做法是通行做法,不过这种做法并不算规范,不规范的是最后一步直接重启,当然这种做法完全无影响并可稍微省点事,算是偷鸡,前面也解释过这样做能通过的原因。严谨的做法,应该是在收到应答信号后发出停止信号,然后再重新启动总线输入读指令进行读操作。之所以要提这点,是因为现有教材中都是这样讲的,我这算是一个解释。 实验的电路图如下: 搭建好电路的实物图如下: 前面说了,我们要用一个LED灯接在SDA上观察数据传输过程,但只有这一个灯是不够的,为了观察时序,我们在SCL上也接上一个灯。在数据写入实验中,为了观察到我们输入的数据是否是我们设定的数据,我们在P1端口接上八个LED灯,我们每向SDA上放置一位数据,都会立即将SDA的电位送入P1口对应引脚,第一位对应LED7(P1.7),第二位对应LED6(P1.6),依此类推。这八个灯在数据读取实验时则用于显示读出的数据值。 通过前面的介绍,我们看到,这个协议对于每一步操作之间的时间间隔并没有最长限制,所以,为了让用LED灯观察这个实验的方法具备可行性,在编程时我们可以将每一个步骤之间的时间设计得长一点,比如SCL电位的每一次跳变之后都间隔1秒钟才进行下一个步骤或下一个跳变,将应答的持续时长设计为2秒以区别于数据读写,等等。 应答观察是我们的重点观察内容,可以说,只要应答正确了,那我们的程序基本就没问题了,所以,我们在编写程序时,第一次将10100000只写到最末一位数据在SDAH 放上去之后+拉高SCL+等待2秒+拉低SCL+拉高SDA,然后编译、下载运行,观察SCL上的灯的闪烁,观察最后SDA灯是亮还是灭,通过前面的知识,我们知道,如果SDA上的灯不亮,那说明我们的应答信号是正确的,程序没问题了。 如果你对这个结果还有怀疑的话,那我们把上面的这个实验稍微修改一下,我们改成输入10100001这个“读”命令,为的是在应答期之前就让SDA为高电平而让SDA灯亮。我们再运行程序,然后我们会观察到最后一位数放置在SDA上之后,SDA灯为亮,然后我们再观察当SCL变化时,SDA灯会否熄灭,什么时候熄灭,如果熄灭了,那说明从器件的应答信号成功被放置在SDA上;然后我们再观察当SCL变化时,SDA灯是否会亮,什么时候亮,如果亮了,说明从器件成功交出了对SDA的控制权,主器件就重新获得了对SDA的控制权,然后主器件就可以继续下面的操作了。 给新手说一个小小的建议:在编程时,最好不要采用循环语句,而是一步一步老老实实地写程序,等你这个程序通过之后,再去使用循环语句等对程序进行优化和简化,免得因为对某种语句的使用不熟练出错而导致程序通不过。 加时的启动流程可以这样:拉高SCL、稍等、拉高SDA、等待2秒、拉低SDA、稍等、拉低SCL、等待2秒(启动观察拉长时间); 一位数据传输流程可以这样,比如第7位:拉低SCL、稍等、SDA放置第7位数据、将SDA状态送入LED7、等待1秒、拉高SCL、等待1秒,严谨一点的可以再加一个拉低SCL。至于最后一位数据的传输,为方便实验观察,则按前面说的来编写和运行。 应答流程可以这样:拉低SCL、稍等、拉高SDA、等2秒、拉高SCL、等2秒、拉低SCL。 停止流程可以这样:拉低SCL、等1秒、拉低SDA、等1秒、拉高SCL、等1秒、拉高SDA。 接收数据的实验我就不讲了,自己都觉得啰嗦。 通讯协议为什么叫做协议,因为数据传输是主从器件双方之间的事,它们得事先协商好规则也即每个动作的含意,这样才能顺利进行数据的发送与接收。 总结并补充一下,在I2C协议中,它们双方是协商好了以下动作的含意:总线空闲时各器件都向自己的两个端口输出高电位以示自己没有控制SDA和SCL,并必须保持对SDA电位变化的监测;总线空闲时若有器件想开始传输数据,则拉低SDA电位而成为主器件,此时总线上的所有其它器件一检测到这个情况,就知道总线上有器件想开始传输数据了,并立即作好接收第一字节数据的准备;主器件这时并不考虑从器件们是否已经作好了准备,而只是延时一下,然后就开始一位一位数据的传输,双方事前已协商好,在这个传输过程中,主器件是在SCL为低电位时才能往SDA上放置数据,而从器件则保持对SCL电位从低到高变化的检测,一旦检测到SCL电位从低到高进行了变化,则按约定读取SDA;从器件在一个8位数据接收完成后,会以拉低SDA的方式告诉主器件我已接收到了一个8位数据;从器件在接收到第一个8位数据后会将这个数据与自己本身的地址值进行对比,如果不符,则不作响应,但它会继续监测SDA及SCL,并只接收当SCL在高电位时,SDA电位的变化,也就是只接收总线上的启动信号和停止信号,接收到启动信号意味着它又要作接收数据的准备了,接收到停止信号意味着它有了成为主器件的条件。 PS:请纠错。
|