IIC概述
IIC总线是一种串行半双工两线总线。一根是双向的数据线SDA,另一根是时钟线SCL。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。硬件拓扑如下图所示,且每个器件都有唯一的设备地址来确定每个器件,地址位通常为7bit。
IIC总线是一种多主机总线,连接在IIC总线上的器件可分为主机和从机。其中每个器件都可以作为主机也可以作为从机。(但同一时刻只能有一个主机)。主机有权发起和结束一次通讯。而从机只能被主机呼叫。并且,IIC总线的时钟信号是由主控器件(主机)产生。在传输速率上,串行的8位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
IIC总线上的器件增加和删除不会影响其他器件正常工作,并且IIC总线在通讯时总线上发送数据的器件称为发送器,接收数据的器件称为接收器。
- 对于硬件IIC和软件IIC各有优劣,考虑实际情况具体使用。为更好理解IIC,文章下描述的是采用软件IIC。
IIC协议
IIC的通信过程由开始、结束、发送、响应、接收五个部分构成。并且在通讯过程中,IIC的SCL线和SDA线遵循以下原则:
- 当SCL高电平时采样SDA电平状态,此时SDA电平状态不允许改变。
- 当SCL低电平时不采样SDA电平状态,此时SDA电平状态允许改变。
- IIC在总线空闲状态时,这两根线通常是被外部上拉电阻拉高,保持着高电平。
IIC总线通讯过程
- 主机发送起始信号启用总线
- 主机发送一个字节数据,指明从机地址和后续字节的传输方向(7bit地址加1bit读写(0-主机写/1-主机读))
- 被寻址的从机发送应答信号回应主机
- 发送器发送一个字节数据
- 接收器发送应答信号回应发送机
- (…循环4.5)
- 通讯完成后,主机发送停止信号释放总线
典型的IIC时序
下面是拆分通讯过程的描述和代码实现。
开始信号与停止信号
- 开始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平。
- 停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
- 此时的起始信号和停止信号都是由主机发出的,起始信号产生后,总线处于占用,停止信号产生后,总线处于空闲状态(MSB)。
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //1.设置SDA输出
IIC_SDA=1; //2.先拉高SDA
IIC_SCL=1; //3.再拉高SCL,空闲状态
delay_us(4);
IIC_SDA=0; //4.拉低SDA START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0; //5.钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); //1.sda线输出
IIC_SCL=0; //2.先拉低SCL
IIC_SDA=0; //3.再拉低SDA STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1; //4.拉高SCL
IIC_SDA=1; //5.拉高SDA 发送I2C总线结束信号
delay_us(4);
}
应答信号
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
- 当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的。应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
//1.设置SDA为输入
//2.拉高SDA
//3.拉高SCL
//4.等待接收器返回应答信号,如果数据线SDA一直为高,就一直等待,并返回1(无效应答),如果数据线SDA为低,返回0(有效应答)
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
- 当从机向主机发送完一个字节的数据,从机也是需要等待主机给出一个应答信号。
//产生ACK应答
//这里就很清楚了,产生应答:SCL在SDA一直为低电平期间完成低高电平转换
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
//这里就很清楚了,不产生应答:SCL在SDA一直为高电平期间完成低高电平转换
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
发送数据
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
//IIC_SCL=0;
//在SCL上升沿时准备好数据,进行传送数据时,拉高拉低SDA,因为传输一个字节,一个SCL脉冲里传输一个位。
//数据传输过程中,数据传输保持稳定(在SCL高电平期间,SDA一直保持稳定,没有跳变)
//只有当SCL被拉低后,SDA才能被改变
//总结:在SCL为高电平期间,发送数据,发送8次数据,数据为1,SDA被拉高,数据为0,SDA被拉低。
//传输期间保持传输稳定,所以数据线仅可以在时钟SCL为低电平时改变。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
//获取数据的最高位,然后左移7位
//如果某位为1,则SDA为1,否则相反
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
接收数据
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
//先拉低SCL,延时后拉高
//读取数据
//是否发送应答
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}