如何更加深入理解I2C总线、协议及应用

描述

开始和停止条件

SCL时钟电平为高:

SDA数据线由高 -> 低 为总线开始条件;

SDA数据线由低 -> 高 为总线结束条件;

(注意:开始之后将SCL变为低电平,防止误操作SDA使其通信停止,见源代码)

时序图:

协议

源代码程序:

/************************************************

函数名称 : I2C_Start

功    能 : I2C开始

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

voidI2C_Start(void)

{

I2C_SCL_HIGH;    //SCL高

I2C_Delay();

I2C_SDA_HIGH;    //SDA高 -> 低

I2C_Delay();

I2C_SDA_LOW;     //SDA低

I2C_Delay();

I2C_SCL_LOW;     //SCL低(待写地址/数据)

I2C_Delay();

}

/************************************************

函数名称 : I2C_Stop

功    能 : I2C停止

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void I2C_Stop(void)

{

I2C_SDA_LOW;     //SDA低 -> 高

I2C_Delay();

I2C_SCL_HIGH;    //SCL高

I2C_Delay();

I2C_SDA_HIGH;    //SDA高

I2C_Delay();

}

数据位传输

SCL时钟电平为低, 可以改换SDA数据线的电平,在SCL上升沿的过程将SDA数据发送出去。

(切记:请先将SCL变为低电平,再改变SDA电平状态。 主要用于I2C读写Byte函数,这两个函数网上很多人写的不规范,引用需注意,在下面我会举例说明)

时序图:

协议

发送一位“高”数据流程:

SCL_LOW时钟低 ->  SDA_HIGH数据 ->  SCL_HIGH时钟高

应答位信息

I2C是以字节(8位)的方式进行传输,总线上每传输完1字节之后会有一个应答信号,主器件(主机)需要产生对应的一个额外时钟。

应答位产生及接收:

1.在(主机)写数据的时候是从机应答(给主机),主机检测;

2.在(主机)读数据的时候是主机应答(给从机),从机检测;

(我们借助I2C读写函数一起理解)

1.主机写,从机应答,主机读取应答

时序图:

协议

源代码:

/************************************************

函数名称 : I2C_GetAck

功    能 : I2C主机读取应答(或非应答)位

参    数 : 无

返 回 值 : I2C_ACK ----- 应答

I2C_NOACK --- 非应答

作    者 : strongerHuang

*************************************************/

uint8_t I2C_GetAck(void)

{

uint8_t ack;

I2C_SCL_LOW;     //SCL低 -> 高

I2C_Delay();

I2C_SDA_HIGH;    //释放SDA(开漏模式有效)

I2C_Delay();

I2C_SCL_HIGH;    //SCL高(读取应答位)

I2C_Delay();

if(I2C_SDA_READ)

ack = I2C_NOACK;//非应答

else

ack = I2C_ACK; //应答

I2C_SCL_LOW;     //SCL低

I2C_Delay();

returnack;

}

2.主机读,主机产生应答

时序图:

协议

源代码:

/************************************************

函数名称 : I2C_PutAck

功    能 : I2C主机产生应答(或非应答)位

参    数 : I2C_ACK ----- 应答

I2C_NOACK --- 非应答

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

voidI2C_PutAck(uint8_t Ack)

{

I2C_SCL_LOW;     //SCL低

I2C_Delay();

if(I2C_ACK == Ack)

I2C_SDA_LOW;   //应答

else

I2C_SDA_HIGH;  //非应答

I2C_Delay();

I2C_SCL_HIGH;    //SCL高 -> 低

I2C_Delay();

I2C_SCL_LOW;     //SCL低

I2C_Delay();

}

I2C写一字节

这里说的I2C写,是主机往从机接入1Byte的数据;

“写”要求按照上面的“数据为传输”来操作:在SCL时钟为低电平时准备好,待SCL为高电平时发送出去。

写完一字节(8位)之后,读取从机的应答位:

若为0,表示从机应答,可以继续下一步操作; 

若为1,表示从机非应答,不能进行下一步操作。

注意:

I2C写一字节,不是EEPROM写一字节(需要区分开来)

写一字节时序(前面8位数据 + 最后1为应答):

协议

源代码:

/************************************************

函数名称 : I2C_WriteByte

功    能 : I2C写一字节

参    数 : Data --- 数据

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void I2C_WriteByte(uint8_t Data)

{

uint8_t cnt;

for(cnt=0; cnt<8; cnt++)

{

I2C_SCL_LOW;   //SCL低(SCL为低电平时变化SDA有效)

I2C_Delay();

if(Data & 0x80)

I2C_SDA_HIGH;//SDA高

else

I2C_SDA_LOW; //SDA低

Data <<= 1;

I2C_Delay();

I2C_SCL_HIGH;  //SCL高(发送数据)

I2C_Delay();

}

I2C_SCL_LOW;     //SCL低(等待应答信号)

I2C_Delay();

I2C_GetAck();    //读取应答位

}

提示:

网上常见几种关于“I2C写数据函数”的不规范写法, 或许整个I2C驱动能通信成功,但各个函数之间依赖关系很强,不便理解,也不是标准的函数。

1.首先将SCL置高:

voidI2C_WriteByte(uint8_t Data)

{

uint8_t cnt;

for(cnt=0; cnt<8; cnt++)

{

I2C_SCL_HIGH; 

if(Data & 0x80)

I2C_SDA_HIGH;

else

I2C_SDA_LOW; 

Data <<= 1;

I2C_SCL_LOW;

}

I2C_GetAck();

}

这种程序的写法有一个致命的地方(有可能停止,或重新开始I2C通信):

首先将SCL置高:

A.若之前SDA是低电平,第一位写入高电平,将停止I2C通信。

B.若之前SDA是高电平,第一位写入低电平,将重新开始I2C通信。

2.写完8位数据之后,未将SCL置低(也就是SCL保持高电平状态)

由于写完8位数据之后,将要读取应答信号,也就是要SDA将从输出状态变为输入状态。

这个时候SCL为高,如果SDA最后一位是低且SDA是开漏模式,需要将SDA释放,也就是要将SDA置位高,那么,这个时候就进行了一个停止操作。

3.时序混乱

void I2C_WriteByte(uint8_t Data)

{

uint8_t cnt;

I2C_SCL_HIGH; 

for(cnt=0; cnt<8; cnt++)

{

if(Data & 0x80)

I2C_SDA_HIGH;

else

I2C_SDA_LOW; 

Data <<= 1;

I2C_SCL_LOW;

I2C_SCL_HIGH; 

}

I2C_GetAck();

}

多种问题的例子,有可能产生以下问题:

A.有可能多写1位数据;

B.有可能停止I2C通信;

C.有可能重新开始I2C通信。

I2C读一字节

I2C的读一字节函数,其实和“写一字节”类似,只是数据传输方向相反,应答的方向也是相反。

读完一字节(8位)之后,由主机产生应答(或非应答)位:

若产生应答,表示可以继续读下一字节操作(从设备地址指向下一字节); 

若产生非应答,表示不可以继续读下一字节操作;

网上I2C读数据程序和“写数据”类似,存在很多不标准的版本,参考时请注意。

读一字节时序(主机读取前面8位数据 + 主机产生1为非应答<连续读,主机产生应答位>):

协议

源代码:

/************************************************

函数名称 : I2C_ReadByte

功    能 : I2C读一字节

参    数 : ack --------- 产生应答(或者非应答)位

返 回 值 : data -------- 读取的一字节数据

作    者 : strongerHuang

*************************************************/

uint8_t I2C_ReadByte(uint8_t ack)

{

uint8_t cnt;

uint8_t data;

I2C_SCL_LOW;     //SCL低

I2C_Delay();

I2C_SDA_HIGH;    //释放SDA(开漏模式有效)

for(cnt=0; cnt<8; cnt++)

{

I2C_SCL_HIGH;  //SCL高(读取数据)

I2C_Delay();

data <<= 1;

if(I2C_SDA_READ)

data |= 0x01; //SDA为高(数据有效)

I2C_SCL_LOW;   //SCL低

I2C_Delay();

}

I2C_PutAck(ack); //产生应答(或者非应答)位

return data;     //返回数据

}

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分