目录
一、IIC总线概述
IIC总线两线制包括:串行数据SDA(Serial Data)、串行时钟SCL(Serial Clock)。总线必须由主机(通常为MCU)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件;
IIC总线特征:同步串行半双工(同一时刻只能是一种身份);
对于多个从机,每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问;
二、IIC总线时序
IIC一次完整通信
1、主机发送起始条件(主机占用总线,唤醒总线所有的从机);
2、主机发送器件地址(总线上所有的从机就会拿这个器件跟自身进行比较,匹配成功的那个从机就会回复一个应答信号给主机,并根据方向位来决定数据传输方向);
3、进行有效数据交流(每传输完一个直接数据都要给应答);
4、主机发送停止信号(释放总线,结束本次通信)。
起始信号:当 SCL 线是高电平时 ,SDA 线从高电平向低电平切换,表示通讯的起始;
停止信号:当 SCL 线是高电平时 ,SDA 线由低电平向高电平切换,表示通讯的停止。
起始和停止信号一般由主机产生,其示意图如下。
数据有效性
IIC 使用 SDA信号线来传输数据,使用 SCL信号线进行数据同步。SDA数据线在 SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。每次数据传输都以字节为单位,每次传输的字节数不受限制。
地址及数据方向
IIC 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址,也叫从机地址来查找从机。IIC 协议规定设备地址可以是 7位、8位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),可以是第 8位(设备地址是7位)或第 11 位(设备地址是10位)。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。如下图表示从机地址是7位,数据方向位为第8位。
响应
IIC 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 IIC 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。传输时主机产生时钟,在第 9个时钟时,数据发送端会释放 SDA的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号。
三、IIC三种通信方式
1、只读:主机只读取数据
2、只写:主机仅发送数据
3、有读有写
四、IIC总线模式
五、IIC上拉电阻阻值选择
六、IIC常见问题
1、IIC总线的地址错误,要注意区分7位地址、8位地址和10位地址,以及读写位的设置。
2、IIC总线的时序错误,要注意起始信号、停止信号、数据有效性和应答信号的生成和检测,以及时钟频率和上升时间的控制。
3、IIC总线的电压错误,要注意匹配主从设备的电压水平,以及使用合适的上拉电阻和串联电阻。
4、IIC总线的硬件错误,要注意检查供电电压、主输入时钟、GPIO配置、信号线走线等是否正确。
5、IIC总线的软件错误,要注意检查驱动代码、设备树配置、中断处理等是否正确。
七、IIC总线调试神器
逻辑分析仪
逻辑分析仪是一种电子测试工具,具有广泛的应用领域,其中包括分析IIC(Inter-Integrated Circuit,即串行双向通信总线)的输入信号。通过使用逻辑分析仪,工程师可以对IIC总线上的信号进行深入分析,从而更好地理解协议的解析过程。
协议解析:逻辑分析仪能够解析IIC协议的各个阶段,从而帮助工程师确认设备是否按照协议要求进行通信。
八、软件模拟IIC读写EEPROM
#include "STC8H.h"
#define I2C_SDA P24
#define I2C_SCL P25
#define AT24C02_ADDR 0xA0 //AT24C02的地址
unsigned char read_byte = 0;
void soft_delay(unsigned int ms)
{
unsigned int i,j;
for(i = 0;i < ms;i++)
for(j = 0;j < 200;j++);
}
/****************************************************************************************
* @名称 void i2c_start(void)
* @功能 I2C起始信号
* @输入参数 none
* @返回 none
****************************************************************************************/
void i2c_start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
/****************************************************************************************
* @名称 void i2c_stop(void)
* @功能 I2C停止信号
* @输入参数 none
* @返回 none
****************************************************************************************/
void i2c_stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
/****************************************************************************************
* @名称 void i2c_send_ack(bit ack)
* @功能 主机发送应答
* @输入参数 ack - 应答
* @返回 none
****************************************************************************************/
void i2c_send_ack(bit ack)
{
I2C_SDA=ack;
I2C_SCL=1;
I2C_SCL=0;
}
/****************************************************************************************
* @名称 void i2c_read_ack(void)
* @功能 主机接收应答
* @输入参数 none
* @返回 ack - 应答信号
****************************************************************************************/
bit i2c_read_ack(void)
{
bit ack=0;
I2C_SDA=1;//释放总线
I2C_SCL=1;
ack=I2C_SDA;
I2C_SCL=0;
return ack;
}
/****************************************************************************************
* @名称 void i2c_read_ack(void)
* @功能 I2C总线主机发送一个字节数据
* @输入参数 byte - 待发送的字节
* @返回 none
****************************************************************************************/
void i2c_write_byte(unsigned char byte)
{
unsigned char i=0;
for(i=0;i<8;i++)
{
I2C_SDA = byte&(0x80>>i);
I2C_SCL = 1;
soft_delay(1);
I2C_SCL = 0;
soft_delay(1);
}
}
/****************************************************************************************
* @名称 void i2c_read_byte(void)
* @功能 I2C总线主机接收一个字节数据
* @输入参数 none
* @返回 byte - 接收到的字节
****************************************************************************************/
unsigned char i2c_read_byte(void)
{
unsigned char i,byte=0;
I2C_SDA = 1;//释放总线
for(i=0;i<8;i++)
{
I2C_SCL = 1;
if(I2C_SDA == 1)
{
byte|=(0x80>>i);
}//I2C_SDA上的数据已经是从机发送的数据
soft_delay(1);
I2C_SCL = 0;
soft_delay(1);
}
return byte;
}
/****************************************************************************************
* @名称 void at24C02_write_byte(unsigned char word_address,unsigned char byte)
* @功能 向AT24C02中的某个地址写入一个字节数据
* @输入参数 word_address - 数据地址
* @输入参数 byte - 写入的字节数据
* @返回 none
****************************************************************************************/
void at24C02_write_byte(unsigned char word_address,unsigned char byte)
{
bit ack = 0;
i2c_start();
i2c_write_byte(AT24C02_ADDR);
ack = i2c_read_ack();
i2c_write_byte(word_address);
ack = i2c_read_ack();
i2c_write_byte(byte);
ack = i2c_read_ack();
i2c_stop();
}
/*
*函数名: AT24C02_Readbyte(unsigned char word_address)
*函数功能: 主机读取AT24C02的指定地址的数据
*输入: word_address:数据的地址
*输出: byte:被读取的字节数据
*/
unsigned char at24C02_read_byte(unsigned char word_address)
{
unsigned char byte = 0;
bit ack = 0;
i2c_start();
i2c_write_byte(AT24C02_ADDR);
ack = i2c_read_ack();
i2c_write_byte(word_address);
ack = i2c_read_ack();
i2c_start();
i2c_write_byte(AT24C02_ADDR|0x01);
ack = i2c_read_ack();
byte = i2c_read_byte();
i2c_send_ack(1);
i2c_stop();
return byte;
}
/*主函数*/
void main(void)
{
unsigned char i = 0;
P2M1 = 0X00; P2M0 = 0XCF; //设置P24/P25为准双向口
while(1)
{
at24C02_write_byte(0x01,i);
soft_delay(20);
read_byte = at24C02_read_byte(0x01);
soft_delay(20);
}
}