无人问津也好,技不如人也罢,都应静下心来,去做该做的事。
最近在学STM32,所以也开贴记录一下主要内容,省的过目即忘。视频教程为江科大(改名江协科技),网站jiangxiekeji.com
本期开始学习 I2C通信,会分两大块来介绍,第一块, 就是介绍协议规则,然后用软件模拟的形式来实现协议;第二块, 就是介绍STM32的 I2C外设, 然后用硬件来实现协议。
I2C:高位先行,只有在SCL高电平时才会读取SDA的电平
本期的内容其实就是对MPU6050芯片内部的寄存器进行读写,写入到配置寄存器,就可以对外挂的这个模块进行配置;读出数据寄存器,就可以获取外挂模块的数据。最终,我们读出的数据会显示在这个OLED上。而STM32对MPU6050的读写是通过I2C来进行的。
因为I2C是同步时序,软件模拟协议也非常方便,所以这里会先介绍软件模拟 I2C。
本期会使用MPU6050陀螺仪、加速度传感器来学习 I2C。
I2C的完整时序,主要有指定地址写、当前地址读和指定地址读这3种。I2C指定地址写简单来说就是SCL高电平时、SDA下降沿开始传输,传输的第一个字节为从机的7位地址+1位读写位(置0表示要写入),然后是1位应答位(RA,置0表示应答),传输的第二个字节为从机的寄存器地址或者是指令控制字,然后也是1位应答位,传输的第三个字节为主机想写入从机的数据,然后也是1位应答位,这时若不想传输了,则SCL高电平时、SDA上升沿停止传输。
当前地址读就是SCL高电平时、SDA下降沿作为起始条件开始传输,传输的第一个字节为从机的7位地址+1位读写位(这里置1表示读取),然后是1位应答位(RA,置0表示应答),传输的第二个字节为从机的数据,然后是1位非应答位(SA,置1),这时SCL高电平、SDA上升沿作为停止条件停止传输。
指定地址读就是指定地址写去掉写数据的部分+当前地址读,SCL高电平时、SDA下降沿作为起始条件开始传输,传输的第一个字节为从机的7位地址+1位读写位(置0表示要写入),然后是1位应答位(RA,置0表示应答),传输的第二个字节为从机的寄存器地址或者是指令控制字,然后也是1位应答位(置0),然后接重复起始条件Sr(即再来一次起始条件),传输的第三个字节为从机的7位地址+1位读写位(置1表示要读取),然后再接收1位应答位(置0),传输的第四个字节为从机的数据,然后1位非应答位(SA,置1),然后SCL高电平、SDA上升沿作为停止条件停止传输。
I2C通信
下图左一是MPU6050,可以进行姿态测量,使用了I2C通信协议;左二是OLED模块,可以显标字符、图片等信息,也是I2C协议;左三是AT24C02,存储器模块;第4个图片,是DS3231,实时时钟模块,也是使用I2C协议。
本期只会用到一主多从,一主多从就是单片机作为主机,主导I2C总线的运行,挂载在I2C总线的所有外部模块都是从机,从机只有被主机点名之后才能控制I2C总线。
作为通信协议,它必须要在硬件和软件上,都作出规定。硬件规定就是你的电路该怎么连接、端口的输入输出模式都是啥样的。软件上的规定就是,你的时序是怎么定义的,字节如何传输、高位先行还是低位先行、一个完整的时序由哪些部分构成。
硬件电路
下图便是一主多从模型,任何时候,都是主机完全掌控SCL线。另外在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机。从机对于SCL时钟线,在任何时刻都只能被动的读取。
I2C的设计是,禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构。不采用推挽输出是为防止时序混乱导致SDA上出现电源短路 的情况。右边的图是内部的具体结构说明,SCLK信号进来,都可以通过一个数据缓沖器或者是施密特触发器,进行输入。输出只能是开漏输出。
软件部分
I2C时序基本单元
在I2C总线处于空闲状态时,SCL和SDA都处于高电平状态。当从机捕获到这个SCL高电平,SDA下降沿信号时,就会进行自身的复位,等待主机的召唤。
起始之后,就到发送部分。高位先行,SCL低电平时,主机如果想发送0,就拉低SDA到低电平;如果想发送,就放手,SDA回弹到高电平。在SCL低电平时,SDA可以变化,但在SCL高电平时,SDA不允许变化。
接收部分
应答
I2C完整时序
I2C的完整时序,主要有指定地址写、当前地址读和指定地址读这3种。首先把每个从设备都确定一个唯一的设备地址,主机在起始条件之后,要先发送一个字节叫一下从机名字,所有从机都会收到第一个字节,和自己的名字进行比较,如果一样,就说明,主机现在在叫我,那我就响应之后主机的读写操作。
指定地址写
从机设备地址,在I2C协议标准里分为7位地址和10位地址,我们目前只讲7位地址的模式。那在每个I2C设备出厂时,厂商都会为它分配一个7位的地址,这个地址具体是什么,可以在芯片手册里找到。一般不同型号的芯片地址都是不同的,相同型号的芯片地址都是一样的,那如果有相同的芯片挂载在同一条总线怎么办呢?这就需要用到地址中的可变部分了,一般器件地址的最后几位是可以在电路中改变的,比如MPU6050地址的最后一位,就可以由这个板子上的AD0脚确定。这个引脚接低电平,那它的地址就是1101 000;这个引脚接高电平,那它的地址就是1101 001。
下面以几个波形来详细解释I2C的完整时序。
空闲状态,SCL和SDA都是高电平。然后主机需要给从机写入数据的时候,首先,SCL高电平期间,拉低SDA,产生起始条件(Start,S)。在起始条件之后,跟着的时序,必须是发送一个字节的时序。字节的内容,必须是从机地址+读写位,正好从机地址是7位,读写位是1位,加起来是一个字节,8位。发送从机地址,就是确定通信的对象。比如这个波形下,主机寻找的从机地址就是1101 000,这个就是MPU6050的地址,然后最低位,表示读写位,0表示,之后的时序主机要进行写入操作。1表示,之后的时序主机要进行读出操作。紧跟着的单元,就得是接收从机的应答位(Receive Ack,RA),在这个时刻,主机要释放SDA。最终高电平期间,主机读取SDA,发现是0,就说明,我进行寻址,有人给我应答了。应答结束后,我们要继续发送一个字节。第二个字节,就可以送到指定设备的内部了,从机设备可以自己定义第二个字节和后续字节的用途,一般第二个字节可以是寄存器地址或者是指令控制字等。比如MPU6050定义的第二个字节就是寄存器地址;比如AD转换器,第二个字节可能就是指令控制字;比如存储器,第二个字节可能就是存储器地址。图中的第二个字节在MPU6050里,就表示我要操作0x19地址下的寄存器了。接着同样,是从机应答。同样的流程在来一遍,主机发送第三个字节。这个字节就是主机想要写入到0x19地址下寄存器的内容了,比如这里就表示,我要在0x19地址下,写入0xAA。最后是接收应答位,然后也是1位应答位,如果主机不需要继续传输了,就可以产生停止条件(Stop,P)。在停止条件之前,先拉低SDA,为后续SDA的上升沿作准备,然后释放SCL,再释放SDA,这样就产生了SCL高电平期间,SDA的上升沿。这样一个完整的数据帧就完成了。这个数据帧的目的就是,对于指定从机地址为1101 000的设备,在其内部0x19地址的寄存器中,写入0xAA这个数据。
当前地址读
这就是当前地址读的时序,如果主机想要读取从机的数据,就可以执行这个时序。
那最开始,还是SCL高电平期间, 拉低SDA,产生起始条件。然后,主机必须首先调用发送一个字节,来进行从机的寻址和指定读写标志位,比如图示的波形,表示本次寻址的日标是1101 000的设备,同时,最后一位读写标志为1,表示主机接下来想要读取数据。然后,接收一下从机应答位,从机应答0,代表从机收到了第一个字节。应答后,第二个字节数据的传输方向就要反过来,因为发了读的命令,所以主机要把SDA的控制权交给从机。主机调用接收一个字节的时序,进行接收操作,这段时间,从机可以在SCL低电平期间写入SDA,然后主机在SCL高电平期间读取SDA。那最终,主机在SCL高电平期间依次读取8位,就接收到了从机发送的一个字节数据。由于这里主机并没有指定读取的地址,所以就要用到当前地址指针。在从机中会有一个单独的指针变量,这个指针上电默认,一般指向0地址,并且,每写入一个字带和读出一个字节后,这个指针就会自动自增一次,那么在调用当前地址读的时序时,主机没有指定要读哪个地址,从机就会返回当前指针指向的寄存器的值,那假设,我刚调用了这个指定地址写的时序,在0x19的位置写入了0xAA,那么指针就会+1,移动到0x1A的位置,我再调用这个当前地址读的时序,返回的就是0x1A地址下的值。如果在调用一次,返回的就是0x1B地址下的值。由手当前地址读并不能指定读的地址,所以用的不多
指定地址读
从中间红线分开,左边是指定地址写,但是去掉了写数据的那部分;右边是当前地址读。
因为我们刚指定了地址,所以再调用当前地址读,就是指定地址读,也叫复合格式。所以这个时序会复杂点。
首先,最开始仍然是启动条件,然后发送一个字节,进行寻址。这里指定从机地址是1101 000,读写标志位是0,代表我要进行写的操作。经过从机应答后,再发送第二个字节,用来指定地址,这个数据就写入到了从机的地址指针里了。也就是说从机接收到第二个数据后,它的寄存器指针就指向了0x19这个位置。
这时再来一个起始条件,这个Sr(Start Repeat)的意思就是重复起始条件,这时还不能开始发送数据,因为指定读写标志位只能是跟着起始条件的第一个字节,所以如果想切换读写方向。只能再来个起始条件,然后起始条件后,重新寻址并且指定读写标志位,此时读写标志位是1,代耒我要开始读了,接着主机开始读,这个字节是不是就是0x19地址下的数据。在读完一个字节之后,一定要给从机发个非应答,(Send Ack,SA),非应答就是该主机应答的时候,主机不把SDA拉低。
两个程序现象
一是软件 I2C读写MPU6050
对MPU6050芯片内部的寄存器进行读写,写入到配置寄存器,就可以对外挂的这个模块进行配置;读出数据寄存器,就可以获取外挂模块的数据。最终,我们读出的数据会显示在这个OLED上。最上面的是ID号,图中这个MPU6050的ID号固定为0x68,可以读取这个ID号用来测试 I2C读取数据的功能是不是正常,不同批次的MPU6050,ID号会有所不同。然后下面,左边3个,是加速度传感器的输出数据,分别是X轴、Y轴和Z轴的加速度,右边3个,是陀螺仪传感器的输出数据,分别是X轴、Y轴和Z轴的角速度。