基本的任务是:通过通信线,实现单片机读写外挂模块寄存器的功能。其中至少要实现在指定位置写寄存器和在指定的位置读寄存器这两个功能。
异步时序的优点:省一根时钟线,节约资源;缺点:对事件要求严格,对硬件电路依赖严重
同步时序反过来。
1 I2C通信
I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)
一主多从:一个单片机作为主机,挂载一个或者多个模块作为从机。
多主多从:多个主机,多个从机(但是同一时刻只能有一个主机控制)
1.1 硬件电路
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
左边的CPU就是单片机,作为总线的主机,主机的权利很大,包括对SCL线的完全控制,任何时候都是主机完全掌控SCL线。空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答时候,主机才会转交SDA的控制权给从机。下面都是被控制IC,也就是挂载在I2C上总线上的从机,这些从机可以是姿态传感器、OELD、存储器、时钟模块等。从机的权利比较小,对于SCL时钟线,在任何时刻都只能被动的读取,从机不允许控制SCL线。对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后或者从机应答的时候,从机才能短暂的取得SDA的控制权,这就是一主多从模型。
主机的SCL是输出,没问题,主机和从机的SDA在输入和输出之间变化,
左边是SCL的结构,右边是SDA的结构。
首先引脚的信号进来,都可以通过一个数据缓冲器或者施密特触发器,进行输入,因为输入对电路没有任何影响。在输出这部分使用的是开漏输出的配置(输出低电平,开关管导通,引脚直接接地,是强下拉;输出高电平,这个开关管断开,引脚什么都不接,处于浮空状态,所有的设备只能输出低电平而不能输出高电平,为了避免高电平造成的引脚浮空,这时需要在总线外面SCL和SDA各外置一个上拉电阻,弱上拉)
好处:
(1)完全杜绝了电源短路现象,保证了电路的安全;
(2)避免了引脚模式的频繁切换,开漏加弱上拉的模式,同时兼具了输入和输出的功能。开漏模式下,输出高电平就相当断开引脚,所以在输入之前,可以直接输出高电平,不用切换输入模式了;
(3)这个模式会有一个“线与”的现象,就是只要有任意一个或多个设备输出了低电平,总线就处于低电平。利用这个特性执行多主机模式下的时钟同步和总线仲裁。
1.2 I2C时序基本单元
(1)起始条件:SCL高电平期间,SDA从高电平切换到低电平
(2)终止条件:SCL高电平期间,SDA从低电平切换到高电平
在I2C处于空闲时,SCL和SDA都处于高电平;SCL高电平期间,SDA从高电平切换到低电平,之后主机要再把SCL拽下来,一方面是占用这个总线,另一方面也是为了这些基本单元的拼接。(低电平开始,低电平结束)
终止条件:SCL先回弹到高电平,之后,SDA再回弹至高电平,这个上升沿触发终止条件。
起始和终止都是由主机产生的。所以在总线空闲状态时,从机必须双手放开,不允许主动跳出来碰总线(允许的话是多主机模型了)
(3)发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL(SCL成高电平),从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
起始开始后,第一个字节也必须是主机发送的,最开始SCL是低电平,如果主机想发送0,就拉低SDA到低电平;如果想发送1,就放手,SDA回弹至高电平。在SCL低电平期间,允许改变SDA的电平。当这一位放好后,主机松手时钟线,SCL回弹到高电平,在高电平期间是从机读取SDA的时候,所以高电平期间,SDA不允许变化;SCL处于高电平之后,从机需要尽快的读取SDA,一般是在上升沿这个时刻,从机就已经读取完成了。主机在放手SCL一段时间后,就可以继续拉低SCL了,传输下一位,主机也需要在SCL下降沿之后尽快把数据放到SDA上;数据放完之后,主机再松手SCL,SCL到达高电平,从机读,以此类推。主机拉低SCL,把数据放在SDA上,主机松开SCL,从机读取SDA上的数据,在SCL的同步下依次进行主机的发送和从机的接收。高位先行,所以第一位是第一个字节的最高位B7。SCL和SDA全程由主机掌控,从机只能被动读取。
(4)接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
释放SDA就相当于切换成输入模式,所有设备包括主机都处于输入模式,当主机需要发送的时候,就可以主动拉低SDA,而主机在被动接收的时候,就必须先释放SDA(总线是线与的特征)。
接收一个字节和发送一个字节非常相似。
区别是:发送一个字节是低电平主机放数据,高电平从机读数据;
而接收一个字节是低电平从机放数据,高电平主机读数据,
主机在接收数据之前要先释放SDA线,然后这是从机取得SDA的控制权,从机需要发送0,就把SDA拉低;从机需要发送1,就放手SDA线,SDA回弹至高电平。然后同样的,低电平变换数据,高电平读取数据。实线表示主机控制的电平,虚线表示从机控制的电平。SCL全程由主机控制,SDA主机在接收前要释放,交由从机控制,从机的数据变化都是贴着SCL下降沿进行的,而主机可以在SCL任意高电平时刻读取数据,这就是接收一个字节的时序。
(5)发送应答(发送一位):主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
在接收一个字节后,需要给从机一个应答位,发送应答位的目的是告诉从机,是不会还要继续发,如果从机发送一个数据后,得到了主机的应答,那从机就会继续发送;如果从机没有得到主机的应答,那从机就会认为,自己发送了一个数据,主机不理,可能主机不想要了,这时从机就会释放SDA,交出SDA的控制权,防止干扰主机的操作。
(6)接收应答(接收一位):主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到了,那在应答位这里,主机释放SDA的时候,从机就应该立刻把SDA拉下来,然后在SCL高电平期间,主动读取应答;如果应答位为0,就说明从机确实收到了。这个场景就是主机刚发送一个字节,问有没有人收到,现在把SDA放手了,如果有人收到的话,就把SDA拽下来,然后主机高电平读取数据,发现确实有人拽下来了,说明有人收到了数据;如果主机发送松手后,SDA跟着回弹到高电平,说明没人回应,没人收到或者收到没回应。
1.3 I2C时序
从机有唯一的设备地址。(7位地址)MPU6050: 1101 000
(1)指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下(设备内部地址,寄存器),写入指定数据(Data)
SAD和SDL都处于高电平,开始的时候,拉低SDA,产生起始条件,在起始条件之后,紧跟着的时序必须是发送一个字节的时序,字节的内容必须是从机地址+读写位,从机地址7位,读写位1位, 加起来是一个字节8位,发送从机地址就是确定通信对象,发送读写位就是确定接下来是读出还是写入。具体:低电平期间,SDA变换数据,高电平期间,从机读取SDA。绿色的竖线表示从机读的数据。主机寻找的地址是1101 000(MPU6050的地址),后面的0表示之后的时序主机要进行写入操作,1表示之后的时序要进行读出操作。目前主机是发送一个字节,字节的内容转换成16进制,高位先行,就是0xD0,然后根据协议规定,紧跟着的就是接收从机的应答位(Receive ACK,RA),在这个时刻,主机要释放SDA。
如果单看主机的波形,释放SDA之后,引脚电平回弹到高电平(黄线),但是根据协议规定,从机要在这个时候拉低SDA(绿线),综合两者的波形,在主机释放SDA之后,由于SDA也被从机拽住了,所以主机松手后,SDA并没有回弹高电平,这个过程就表示从机产生了应答,最终高电平期间,主机读SDA,发现是0,就说明进行寻址时,有人给我应答了,传输没问题;如果主机读取SDA发现是1,就说明寻址在应答期间,我松手了,但是没人拽住它,没人给我应答,就直接产生停止条件。后面的上升沿就是应答结束后,从机释放SDA产生的,从机交出SDA的控制权,因为从机要在低电平期间尽快变换数据,所以这个上升沿和SCL的下降沿几乎是同时发生的。继续往后,读写位给了0,所以应答结束后,要继续发送一个字节,同样的时序,再来一遍,第二个字节就可以送到指定设备的内部了,从机设备可以自己定义第二个字节和后续字节的用途,一般第二个字节可以是寄存器地址或者指令控制字等。这里是0x19就表示要操作0x19地址下的寄存器了。
接着同样是从机应答,主机释放SDA,从机拽住SDA,SDA表现为低电平,主机接收到应答位0,表示收到了从机的应答。同样的流程再来一遍,主机再发送一个字节,这里表示要在0x19地址下写入0xAA,最后是接收应答位。如果主机不需要再继续传输了,就可以产生停止条件,在停止条件之前,先拉低SDA,为后续的SDA上升沿做准备,然后释放SCL,再释放SDA,这样就产生了SCL高电平期间,SDA的上升沿。
总结:这个数据帧的目的就是对于指定从机地址为1101 0000的设备,在其内部0x19的寄存器中写入0xAA这个数据。
(2)当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
如果主机想要读取从机的数据,就可以执行这个时序。最开始还是SCL高电平期间,拉低SDA,产生起始条件,起始条件开始后,主机必须首先调用发送一个字节,来进行从机的寻址和指定读写标志位,图中表示本次寻址的目标是1101 000的设备,最后一位读写标志位为1,表示主机读取数据,紧跟着,发送一个字节之后,接收一下从机的应答,从机应答0,表示从机接收到了第一个字节,在从机应答之后,在这里开始,数据的传输方向就要反过来了。
主机刚才发出了读的指令,所以在这之后,主机就不能继续发送了,要把SDA的控制权给从机,主机调用接收一个字节的时序,进行接收操作,之后,从机得到主机的允许,可以在SCL低电平期间写入SDA,然后主机在SCL高电平期间读取SDA,最终,主机在SCL高电平期间依次读取8位,就接收到从机发送的一个字节的数据,0000 1111,也就是0x0F,这里没有指定地址环节,这里需要用到当前地址指针了,在从机中,所有寄存器被分配到一个线性区域中,并且会有一个单独的指针变量,指示着其中一个寄存器,一般默认为0地址,每写入和读出一个字节后,这个指针就会自动自增一次,主机没有指定地址的话,从机就会返回当前指针指向的寄存器的值。
(3)指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
前面一部分是指定地址的时序,把后面写数据的部分去掉,然后把前面这一部分设置地址,还没有指定写什么数据的时序,追加到当前地址读的时序前,就得到了指定地址读的时序(复合格式)
前面部分是指定地址写,只指定了地址,还没来得及写;后面的部分是当前地址读,加起来就是指定地址读了。
前面依然是启动条件,然后发送一个字节进行寻址,指定从机地址是1101 000,读写标标志位是0,表示进行写操作,经过从机应答后,再发送一个字节,第二个字节用来指定地址,这个数据就写入到从机的地址指针里了,也就是说从机接收到这个数据之后,它的寄存器指针就指向0x19这个位置了,之后要写入的数据,不给它发,直接再来个起始条件(start repeat),然后重新寻址并且指定读的标志位,此时读写标志位是1,表示要开始读了,接着主机接收一个字节,这个字节就是0x19地址下的数据,
进阶版本就是读写多个字节。