I2C模块
参考博客链接
https://blog.csdn.net/weixin_42881419/article/details/104288391
相关介绍
I2C Bus——Inter-Integrated Circuit 即集成电路总线。
I2C总线端口为漏开结构,因此一般需要加上拉电阻Rp,通常选用5K~10KΩ
I2C总线以字节为单位收发数据,一个字节为一帧,数据传输的次序为从最高位到最低位
I2C由**数据线(SDA)**与 时钟线(SCL) 组成
SDA:Synchronous Data Adapter (同步数据衔接器)
SCL:Serial Communication Loop(串行通讯环路)
原理图
【主从结构:一主[Master]多从[Slave]】
数据格式
主机写数据:
主机读数据:
1.4信号传输时序图
SCL上拉电阻的作用:在第9个时钟之后,双方中若有一方处理还未完成,它可以一直把SCL拉低,当SCL一直为低电平时,双方都不应该操作SDA总线
读写流程
1.前8个clk(7bit地址 + 1bit读/写)
从设备不影响总线,从设备不去动三极管
主设备决定数据
2.第9个CLK,由从设备决定数据
主设备不驱动三极管
从设备决定数据
SDA高:无ACK
SDA低:有ACK
信号解析
起始信号[Start]:在SCL保持高电平时,SDA下降沿。
读写位:【0写1读】
数据位:SCL低电平时,SDA可变化;SCL高电平,SDA不变
终止信号:在SCL高电平时,SDA上升沿。
回应信号[ACK]:SDA低电平,低电平
如何在SDA上实现双线传输
1.主设备发送时,从设备不发送:可以通过SCL区分控制
2.主设备发送时,从设备的”发送“引脚不影响数据:使用开极电路。
开极电路
A B SDA
0 0 1(由上拉电阻决定)
0 1 0
1 0 0
1 1 1
SDA上拉电阻的作用:当A、B都低电平时,三极管都不导通时,SDA取决于外界电路,当什么都不接时,SDA悬空,无法获取电平,故接上拉电阻。
开极电路驱动思想
当某个设备不想影响SDA总线时,就不驱动三极管。
想输出高电平,都不驱动三极管。
想输出低电平,就驱动三极管。
主从关系
IIC控制器流程框图
IIC读写操作步骤
在发送模式下,当数据已经发送出去后,IIC会等待IICDS寄存器接收新的数据
1.IICDS = val;
2.发完数据,产生中断,拉低SCL
3.中断程序里,判断状态,IICDS = val2,IIC继续工作
在接收模式下,IIC接口会等待直到你把IICDS中的数据读走,在此期间,SCL拉低
1.发起传输,接收DATA
2.接收到数据之后,产生中断,SCL拉低
3.中断程序中,判断,设置中断,val = IICDS,IIC继续工作,继续接收收据
I2C协议标准代码
起始信号&停止信号
起始信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换。
停止信号:当 SCL 线是高电平时 SDA 线由低电平向高电平切换。
起始信号代码
void I2C_Start(void)
{
I2C_SDA_High(); //SDA=1
I2C_SCL_High(); //SCL=1
I2C_Delay();
I2C_SDA_Low();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
}
停止信号代码
void I2C_Stop(void)
{
I2C_SDA_Low();
I2C_SCL_High();
I2C_Delay();
I2C_SDA_High();
I2C_Delay();
}
下面展示一些 内联代码片
。
void I2C_Stop(void)
{
I2C_SDA_Low();
I2C_SCL_High();
I2C_Delay();
I2C_SDA_High();
I2C_Delay();
}
发送一个字节
CPU向I2C总线设备发送一个字节(8bit)数据
u8 I2C_SendByte(uint8_t Byte)
{
uint8_t i;
/* 先发送高位字节 */
for(i = 0 ; i < 8 ; i++)
{
if(Byte & 0x80)
{
I2C_SDA_High();
}
else
{
I2C_SDA_Low();
}
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
if(i == 7)
{
I2C_SDA_High(); /* 释放SDA总线 */
}
Byte <<= 1; /* 左移一位 */
I2C_Delay();
}
}
读取一个字节
CPU从I2C总线设备上读取一个字节(8bit数据)
u8 I2C_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 先读取最高位即bit7 */
value = 0;
for(i = 0 ; i < 8 ; i++)
{
value <<= 1;
I2C_SCL_High();
I2C_Delay();
if(I2C_SDA_READ())
{
value++;
}
I2C_SCL_Low();
I2C_Delay();
}
return value;
}
应答
CPU产生一个ACK信号
void I2C_Ack(void)
{
I2C_SDA_Low();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
I2C_SDA_High();
}
CPU产生一个非ACK信号
void I2C_NoAck(void)
{
I2C_SDA_High();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
}
CPU产生一个时钟,并读取器件的ACK应答信号
uint8_t I2C_WaitToAck(void)
{
uint8_t redata;
I2C_SDA_High();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
if(I2C_SDA_READ())
{
redata = 1;
}
else
{
redata = 0;
}
I2C_SCL_Low();
I2C_Delay();
return redata;
}
I2C通信时序图解析
写数据
结合I2C总线协议的知识,我们可以知道I2C写数据由一下10个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址,8bit数据。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,发送一个数据,8bit数据。
第七步,产生一个ACK应答信号,此应答信号为从机器件产生的应答信号。
第八步,发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
第九步,既可以发送一个应答信号,也可以发送一个无应答信号,均有从机器件产生。
第十步,发送一个停止信号。
接下来,按照以上是个步骤,可以写出OZ9350的i2c写数据的函数。代码如下:
u8 I2C_WriteBytes(void)
{
I2C_Start(); //1
I2C_SendByte(Slaver_Addr | 0); //2
I2C_WaitToAck(); //3
I2C_SendByte(Reg_Addr); //4
I2C_WaitToAck(); //5
I2C_SendByte(data); //6
I2C_WaitToAck(); //7
I2C_SendByte(crc); //8
I2C_WaitToAck(); //9
I2C_Stop(); //10
}
读数据
通过分解后的时序图,可以看到OZ9350的读数据由以下13个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,再次发送一个起始信号。
第七步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第八步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第九步,读取一个字节(8bit)的数据。
第十步,产生一个ACK应答信号,此应答信号为CPU产生。
第十一步,读取一个CRC校验码。
第十二步,产生一个NACK信号。此无应答信号由CPU产生。
第十三步,产生一个停止信号。
下面展示一些 内联代码片
。
u8 I2C_ReadBytes(void)
{
u8 data;
u8 crc;
I2C_Start(); //1
I2C_SendByte(Slaver_Addr | 0); //2
I2C_WaitToAck(); //3
I2C_SendByte(Reg_Addr); //4
I2C_WaitToAck(); //5
I2C_Start(); //6
I2C_SendByte(Slaver_Addr | 1); //7 1-读
I2C_WaitToAck(); //8
data=I2C_ReadByte(); //9
I2C_Ack(); //10
crc=I2C_ReadByte(); //11
I2C_NoAck(); //12
I2C_Stop(); //13
}