概述
IIC:两线式串行总线,它是由数据线SDA和时钟线SCL构成的串行总线,可以发送和接收数据。
时钟线SCL:在通信过程起到控制作用。
数据线SDA:用来一位一位的传送数据。
但是IIC是半双工通信方式。同一时间段,SDA的输入端和输出端只有一端有效。
通信协议
1、(在发送、接收数据的时候)当SCL为高电平的时候,SDA不允许变化,此时是某端在读取数据;当SCL为低电平的时候,SDA允许变化,此时是某端在放置数据
2、(在任意时刻)只有在SCL为高电平时,IIC电路才对SDA线上的电平(0/1)进行记录;当SCL为低电平时候,无论SDA是什么状态,IIC都不对SDA进行记录
空闲状态
又称初始状态,就是没有开始通信的状态,此时SCL和SDA两根线都处于高电平。此时各个器件的输出级场效管处于截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
开始信号与停止信号
开始信号:当SCL处于高电平状态的时候,SDA由高到低的跳变。
void I2C_Start(void)
{
//起始数据端和时钟端都是高电平
I2C_W_SCL(1);
I2C_W_SDA(1);
//
delay_us(5);
//在时钟端是高电平的时候拉低数据端 此时表明开始
I2C_W_SDA(0);
delay_us(5);
//然后时钟端拉低等待数据变化
I2C_W_SCL(0);
delay_us(5);
printf("I2C_Start Ok\n");
}
结束信号:当SCL为高电平状态时候,SDA由低到高的跳变。
void I2C_Stop()
{
//拉低数据位表明结束
I2C_W_SDA(0);
delay_us(5);
//然后通讯结束后两端又拉到高电平
I2C_W_SCL(1);
I2C_W_SDA(1);
printf("I2C_Stop\n");
}
发送过程
(一)先找相应寄存器
(二)再找寄存器当中存数据的地址
(三)发送数据
由上述通信协议可知:SCL处于低电平状态时,SDL是某端在放数据;SCL处于高电平状态时,SDL是IIC电路在读数据
SDA该现处于输出状态,将所需要发送的数据一位一位从最高位发给从端。
void I2C_Send_Byte(uint8_t data)
{
uint8_t i;
for(int i=0;i<8;i++)
{
I2C_W_SDA(data&(0x80>>i));//SDA每次发送1位数据,且从最高位开始
delay_us(5);
I2C_W_SCL(1);//时钟拉高,这样从机就能接收到
delay_us(5);
I2C_W_SCL(0);//时钟拉低,表明主机这会需要放置下一位数据,
delay_us(5);
}
printf("I2C_Send_Byte Ok\n");
}
(四)应答信号
当主端给从端每发一个字节,由接收器反馈一个应答信号,如果应答信号为1表明接收器并未作出接收动作,为0表示准备好接收。
SCL处于低电平状态时,将SDA线拉低,确保在SCL处于高电平时能给读到稳定的低电平。
综上一个完整的发送过程格式如下图所示:
void I2C_RA()
{
I2C_W_SDA(1);//主机放弃掌握权,从机准备拿取掌握权
delay_us(5);
I2C_W_SCL(1);//数据拉高,表明从机已经将回复的数据交到SDA线,准备读取
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==1)
{//如果从机给主机回的是高电平,那么就是从机拒绝与主机连接,处于忙碌
return;
}
//否则接收主机连接
delay_us(5);
I2C_W_SCL(0);//为下一次放置数据做准备
delay_us(5);
printf("I2C_RA Ok\n");
}
如果结束即回复1
void I2C_SA()
{
I2C_W_SDA(1);
delay_us(5);
I2C_W_SCL(1);
I2C_W_SCL(0);
printf("I2C_SA Ok\n");
}
接受过程
(一)随机接受
与发送过程相同,先找到对应寄存器,从中读取数据,注意这个数据是从机随机给主机发的,值不一定是想要的。
(二)指定位置接受
根据发送过程,我们不难发现发送的前两步是为了找到一个固定位置,此时这里有一个指针去指向该空间。那么如果我们想要读取指定位置的值,就可以先将想要的数据对应地址的指针找到,要这个指针先指向该空间,当我们二次发送寄存器位置的时候,从机直接从该指针位置处读取数据,这样就能得到我们想要的值。
uint8_t I2C_Read_Byte()
{
uint8_t data=0x00;
uint8_t i;
I2C_W_SDA(1);
for(int i=0;i<8;i++)
{
I2C_W_SCL(1);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==1)
{
data |=(0x80>>i);
}
I2C_W_SCL(0);
}
printf("I2C_Read_Byte Ok\n");
return data;
}
void I2C_Read()
{
uint8_t data;
I2C_Start();
I2C_Send_Byte(0xA0);
//接收应答
I2C_RA();
//发送写地址
I2C_Send_Byte(0x11);
//接收应答
I2C_RA();
I2C_Start();
I2C_Send_Byte(0xA1);
//接收应答
I2C_RA();
data = I2C_Read_Byte();
printf("read data:%x\n",data);
I2C_SA();
I2C_Stop();
}
注意点
1、为什么要有两个上拉电阻
如果有两个从机同时给主机发送1和0,这样就会形成短路,其中一个器件会受到损失。因此为了让从机不受到伤害,将从机的P-Mos管取掉,这样从机只能输出低电平,为了能够输出高电平,就在外面加两个上拉电阻。(也就形成了开漏模式+上拉模式)
就形成这样一个图,要想输出高电平,只需要让从机不输出低电平即可,上拉电阻提供高电平。
注意这里的电阻不能太小,如果过大输出的电流对从机有伤害。
此外:如果电阻过大,流向下端电流变小,从低电平变成高电平变化的这个爬坡阶段就会变得缓慢(有点像功能不足)
如果电阻过小,流向下端电流变大,则供能过大,这样就会出现爬坡巨快
以上两种情况都会让我们的数据失真。
2、I2C的仲裁机制
1.“线与”机制: 多主机时,总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。 2.SDA回读机制: 总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线。 3.低电平优先机制:由于线与的存在,当多主机发送时,谁先发送低电平谁就会掌握对总线的控制权。
3.仲裁结果: 仲裁结束后,获胜的主设备将继续发送其数据,而其他设备则将停止发送并等待下一个通信机会。 4.重试机制: 如果多个主设备的仲裁结果是平局,即它们的地址相同或优先级相同,通常会启用重试机制。在重试机制中,设备会等待一段随机时间后重新尝试发送数据,以避免再次发生冲突。
第一位大家都是1,那么都是高电平,这时候仲裁不发生,在第二位的时候,主机3为低电平,而其余为高电平,根据低电平优先,则主机三掌握主动权,主机2和主机1放弃掌握转而接受,此时1和2停止转发数据,主机3将剩余数据发送完后,主机1和主机2 继续,而在第四位时主机1和2 不一样,主机2 掌握主动权,主机2将剩余数据发完后主机1从第一位重新开始发送。