关于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站链接,哪里不懂多看几遍就懂了。