moonf99

I2C总线

2023/07/20

关于I2C总线的一些理解与想法。

I2C总线概述

I2C总线有两根通信线,分别是SCL(串行时钟总线),SDA(串行数据总线)。I2C总线还具有同步,半双工,带数据应答的特性,并且有单独的时钟总线,所以可以在上面挂载多个设备。

I2C总线电路规范如下图:
这是图片

所有I2C设备的SCL连在一起,SDA连在一起,并且均要配置成开漏输出模式。
SCL和SDA各添加一个上拉电阻,阻值一般为4.7K左右。
开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题。


I2C时序结构

起始条件和终止条件

这是图片

起始条件:SCL高电平期间,SDA从高电平切换到低电平。
终止条件:SCL高电平期间,SDA从低电平切换到高电平。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void I2C_Start(void)//起始
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}

void I2C_Stop(void)//终止
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}

发送一个字节

这是图片

SCL线从低电平变为高电平,在SCL高电平期间不允许SDA线发生变化。
SDA为低电平则读0,高电平则读1。
连续循环8次,每次都将一个数据位放在SDA总线上,即可发送一个字节。

1
2
3
4
5
6
7
8
9
10
void I2C_SendByte(unsigned char Byte)//字节发送
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}

接收一个字节

这是图片

同理发送一个字节,但是主机在接收之前需要释放SDA!

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}

发送应答与接收应答

这是图片

发送应答:在接收完一个字节后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
接收应答:在发送完一个字节后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答。(主机在接收之前,需要释放SDA!)

注意:发送应答是主机给从机发送是否应答,主体只会是主机,接收应答是从机给主机发送受否应答,主体只会是从机!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void I2C_SendAck(unsigned char AckBit)//发送应答 主机向从机发送
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}

unsigned char I2C_ReCeiveAck(void)//接收应答 主机接收从机
{
unsigned char AckBit;

I2C_SDA=1;//此处的SDA是主机松手置高电平
I2C_SCL=1;
AckBit=I2C_SDA;//从机若应答则置0 不应答则置1
I2C_SCL=0;

return AckBit;
}

I2C数据帧

发送一帧数据

这是图片

流程:开始发送->从机地址->接收应答->数据位1->接收应答->数据位2->接收应答->…数据位n->接收应答->停止发送
其中从机地址由七位数据位+一位读写位组成。

接收一帧数据

这是图片

流程:同上

复合格式

这是图片

开始发送->从机地址->接收应答->数据位1->接收应答->数据位2->接收应答->…数据位n->接收应答->
开始发送->从机地址->接收应答->数据位1->发送应答->数据位2->发送应答->…数据位n->不发送应答->停止发送


AT24C02数据帧

字节写:在WORD ADDRESS处写入数据DATA

这是图片
注意:AT24C02的固定地址为1010,可配置地址本开发板上为000所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1

1
2
3
4
5
6
7
8
9
10
11
12
13
#define AT24C02_ADDRESS 0xA0
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
unsigned char Ack;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}

随机读:读出在WORD ADDRESS处的数据DATA

这是图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReCeiveByte();
I2C_SendAck(1);
I2C_Stop();

return Data;
}

食用例

AT24C02数据存储

按下键码1Num+1,按下键码2Num-1,按下键码3存储当前Num,按下键码4显示存储的Num。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
unsigned char KeyNum;
unsigned int Num;

void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);

while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2)
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3)
{
AT24C02_WriteByte(0,Num%256);//整型数据2个字节 字符型数据1个字节
Delay(5);
AT24C02_WriteByte(1,Num/256);
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4)
{
Num=AT24C02_ReadByte(0);//将0地址的8位数据放在Num的低8位
Num|=AT24C02_ReadByte(1)<<8;//将1地址的8位数据左移,放在Num的高8位
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}

最后放一点我的感想:江科大我的神!没有他的教程也就没有这篇博客,新人入坑嵌入式可以去看他的教程,这里放上他的B站链接,哪里不懂多看几遍就懂了。

CATALOG
  1. 1. I2C总线概述
  2. 2. I2C时序结构
    1. 2.1. 起始条件和终止条件
    2. 2.2. 发送一个字节
    3. 2.3. 接收一个字节
    4. 2.4. 发送应答与接收应答
  3. 3. I2C数据帧
    1. 3.1. 发送一帧数据
    2. 3.2. 接收一帧数据
    3. 3.3. 复合格式
  4. 4. AT24C02数据帧
    1. 4.1. 字节写:在WORD ADDRESS处写入数据DATA
    2. 4.2. 随机读:读出在WORD ADDRESS处的数据DATA
  5. 5. 食用例
    1. 5.1. AT24C02数据存储