前段时间使用了INA226这款芯片,刚好复习了I2C协议的知识,故打算写一篇文章当做复习资料。
在前文我们已经大致的介绍了I2C协议。所以接下来我会根据I2C协议的顺序一步一步的进行解读。我们已经知道一个完整的I2C协议的部分分为以下几个步骤:1: I2C协议起始条件 2:寻址加读写位 3:需要读取或者写入的寄存器的地址 4:需要传输的数据 5:I2C的结束条件。当然我们每次发送一个字节的数据的时候都要跟上应答位。
代码部分:
I2C总线的初始化工作,因为本文是基于HAL库的,所以初始化工作我是在cubemx中完成的
可以看到在初始化IO口的时候我选择了PC4和PA7做作为I2C的引脚。其中需要关注的是这俩个引脚都被我设置为了开漏输出模式,同时引脚上拉。对于这部分的设置可以参考我上篇文章。
我们现在看keil代码部分:
首先是I2C协议的START部分
可以看到刚开始的时候我给SDA和SCK总线都赋予了高电平,这是因为I2C总线默认是高电平,虽然我们已经通过上拉电阻给他高电平了但是为了保险起见我们还是先给SDA SCK置高为好。
我们可以看到我是先给SDA高再给SCK高的,因为要是先给SCL高电平,万一SDA上一个时刻的状态是低电平,一旦我先给SCL高,之后将SDA拉高,那么是不是就是STOP的条件了。所以为了保险起见,先给SDA高再给SCK高电平。
之后再SCL高电平期间拉低SDA总线,这样我们的起始条件就完成了。我们最后让SCL总线拉低,方便将数据放到SDA总线上。大家可能看到了我在这个函数中加上了us延时函数,因为有时候IO口电平变换太快可能从设别无法正确的读取。所以加上延时可以保证我们和从设备的通讯正常。一般延时10ms即可。具体延时时间以手册为准。
I2C协议的STOP部分;
我们回顾一下I2C协议的结束条件,即SCL高电平期间SDA从低变高。可以看到该函数进来先给SDA低电平之后给SCL高电平,之所以先给SDA低电平除了为了满足stop时的上升沿之外,也是为了防止,若我们先给SCK高电平,一旦之后我将SDA置地,万一SDA前一时刻是高电平,那么此时我们这个函数就满足了start条件了,这是我们不想要的。
所以该函数的逻辑是,进入我先给SDA低电平,确保他满足stop条件,之后我们在SCK高电平期间将SDA拉高,此时结束信号完成。
I2C写一个字节
在这个函数中我们传入的参数是我们需要写的字节。利用一个for循环将传入的字节从高位到低位依次取出发送出去,具体逻辑是进到函数内部的时候我们先将Btye的最高位取出,因为start结束的时候SCL是低电平处于接收数据的状态,当我们把数据放到SDA总线上之后拉高SCL,此时数据发送了出去,我们在将SCL拉低,保持数据接收状态。重复这个过程8次,就可以将一个字节的数据发送出去了。
接收应答
当我们的地址和读写位发送完成后,我们紧跟着就要判断接收应答。
逻辑是主机将SDA置为高电平将SDA控制权交给从机,从机此时将应答位放到SDA上,要是从机给应答那么SDA就会被拉低,之后我们SCL拉高,此时应答的一个时序脉冲结束。我们判断一下此时SDA的电平状态,要是SDA是低电平说明从机给了应答,要是SDA为高电平说明没有应答。最后将SCL拉低,保持其数据接收状态。
I2C接受一个字节的数据
接收一个字节函数,返回值uint8类型,逻辑是首先主机将SDA拉高,交出总线控制权,此时从机将数据放到SDA上,拉高SCL接收从机的数据,注意数据也是高位先行,此时我们判断要是接收到的是1我们将data|=0x80,此时data的最高位就为1。依次类推从而接收7-0的数据。
接收完从机的数据之后依旧是我们的应答位,此时的应答是发送应答,因为当我们读取的时候我们的单片机实际上类似于接收设备。而INA226类似于发送设备,此时我们发送的应答信号是0的话表示我们接收到了一字节的数据,同时准备好接收下一个字节的数据,适合需要读取多字节数据的时候。
两字节读取代码代码如下
当我们不需要读取多个数据的时候,只需要将INA226_I2C_SendAck(1)置1即可。此时我们就相当于告诉INA226我已经读取完了,可以结束了。(补充说明,读取的这个代码实际上上半部分是写函数,执行完发送寄存器地址之后我们重启I2C,此时设置为读标志位,这是因为在我们发送完寄存器地址之后,指针就会停留在当前的地址,所以我们只需要读取就好了)
实际上上面的两字节读取函数不过是对于单字节读取函数和发送应答的操作罢了,核心点在于第一次接收一个字节数据之后,发送应答给的是0就是读取多字节,给1就是读取结束。
接收应答的代码
必须牢记接收应答之前主机必须释放SDA总线,将总线的控制权交给从机,同样SCL拉高读取SDA状态为低电平说明应答成功,高电平说明应答失败。
最后返回应答位。
发送应答代码如下
在接收数据的时用,要是我们只读取一个数据那么在读取函数结束后将发送应答置1即可。要是读取多个字节那么发送应答置0,只在最后一字节数据读取完之后将发送应答置1。
由此,关于I2C的几个部分包括开启,结束,地址+读写位,发送应答,接收应答,读取一个字节,发送一个字节,读取多个都给大家分析完成。
以上是本人对于I2C协议代码的理解,若有错误之处,欢迎大家多多指正。