通信协议——I2C总线
来源:互联网 发布:好的职业技术学校知乎 编辑:程序博客网 时间:2024/06/07 20:03
I2C总线是一种简单、双向二线制同步串行总线,由SCL和SDA两根线即可连接于总线上的器件之间传送信息。
其中SCL为时钟线,SDA为数据线,在时钟线SCL控制的时钟信号下,SDA进行数据的传送,而SDA上传送的每个字节必须为8位,每个字节后跟一个响应位,而传输是一位位进行的,其首先传输的是最高位。
一、I2C时序图及程序
1、起始信号和终止信号
起始信号从时序图可看出,先拉高SDA和SCL,然后拉低SDA,一般START信号到这里就结束了,但为了后面的发送字节、读字节等的方便,所以一般在拉低SDA并延时一小段时间后再拉低SCL。
终止信号,拉低SDA然后拉高SCL,最后再拉高SDA就完成了终止信号的发送。
具体程序可参考如下:
- /*******************************************************************************
- * 函 数 名 : I2C_Start()
- * 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
- * 输 入 : 无
- * 输 出 : 无
- * 备 注 : 起始之后I2C_SDA和I2C_SCL都为0
- *******************************************************************************/
- void I2C_Start()
- {
- I2C_SDA = 1;
- I2C_SCL = 1;
- I2C_Delay_us(5);//建立时间是I2C_SDA保持时间>4.7us
- I2C_SDA = 0;
- I2C_Delay_us(4);//保持时间是>4us
- I2C_SCL = 0;
- }
-
- /*******************************************************************************
- * 函 数 名 : I2C_Stop()
- * 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
- * 输 入 : 无
- * 输 出 : 无
- * 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
- *******************************************************************************/
- void I2C_Stop()
- {
- I2C_SDA = 0;
- I2C_Delay_us(4);
- I2C_SCL = 1;
- I2C_SDA = 1;
- }
2.I2C发送字节和读字节 如时序图所示,在SCL为低电平的时候,SDA处于数据转换的状态,此时数据状态是不稳定的也是不确定的,当SCL处于高电平后,此时在SDA上传输的数据稳定,而传输的可能是高电平,也可能是低电平。
对于发送字节,先拉低SCL,根据要发送的字节的当前正在处理的某一位的高低来转换给SDA,在转换完后,拉高SCL,进行数据传输。
对于读字节,因为读字节时读SDA线上是高电平还是低电平,因此应该要先拉高SCL,然后判断SDA上的数据是高还是低并赋值给相应的变量,然后再拉低SCL。
具体程序可参考如下:
- /*******************************************************************************
- * 函 数 名 : I2C_SendByte(unsigned char byt)
- * 函数功能 : 发送一个字节
- * 输 入 : byt
- * 输 出 : 无
- * 备 注 : 无
- *******************************************************************************/
- void I2C_SendByte(unsigned char byt)
- {
- unsigned char i;
- I2C_SCL = 0;
- for(i = 0;i < 8;i++)
- {
- if(byt&0x80) //从最高位开始判断发送的字节byt是0还是1来判断I2C_SDA是0还是1
- {
- I2C_SDA = 1;
- }
- else
- {
- I2C_SDA = 0;
- }
- byt <<= 1; //判断完一位后,那么要左移一位,让下一位继续判断,一个字节8位,所以一共是要判断8次
- delay_us(2);
- I2C_SCL = 1;
- delay_us(2);
- I2C_SCL = 0;
- }
- I2C_SCL = 0;
- }
-
- /*******************************************************************************
- * 函 数 名 : unsigned char IIC_Read_Byte(unsigned char ack)
- * 函数功能 : 读一个字节
- * 输 入 : ack(0或1)
- * 输 出 : 读到的一个字节dat
- * 备 注 : 无
- *******************************************************************************/
- unsigned char IIC_Read_Byte(unsigned char ack)
- {
- unsigned char i,dat = 0;
- I2C_SCL = 0;
- for(i = 0;i < 8;i++ )
- {
- IIC_SCL = 1;
- dat <<= 1;
- if(I2C_SDA) //根据I2C_SDA的高低来一位一位赋值给dat
- {
- dat |= 0x01;
- }
- delay_us(1);
- I2C_SCL = 0;
- }
- IIC_Ack(ack); //如果只接收1个字节的数据,则发送非应答信号1,
- //然后产生stop信号,告诉从机单片机停止接收数据,也就是不用再发了
- //如果要接收多个字节数据,则接收完一个字节数据后要发送应答信号0,
- //告诉从机要继续发给单片机
- return dat;
- }
3.I2C主机等待应答和产生应答
等待应答和产生应答类似于读字节和写字节,不同的是读和写字节是一个字节8位,因此函数内部进行了8次循环,而等待应答和产生应答只需处理一位数据,所以不需要循环,其本质是差不多的,在这里就不再讲述了,具体可参考下面的程序去理解:
- /*******************************************************************************
- * 函 数 名 : I2C_Wait_Ack()
- * 函数功能 : 等待应答:即等待从设备把I2C_SDA拉低
- * 输 入 : 无
- * 输 出 : 0或1
- * 备 注 : 0表示应答发送失败或非应答,1表示接收到应答
- *******************************************************************************/
- unsigned char I2C_Wait_Ack(void)
- {
- unsigned char acktime;
- I2C_SCL = 1;
- delay_us(1);
- while(I2C_SDA) //等待应答,即等待从设备把I2C_SDA拉低
- {
- acktime++;
- if(acktime>200) //如果超过200us没有应答,则发送失败,或者为非应答,表示接受结束
- {
- I2C_SCL = 0;
- delay_us(1);
- return 0;
- }
- }
- delay_us(1);
- I2C_SCL = 0;
- return 1;
- }
-
- /*******************************************************************************
- * 函 数 名 : IIC_Ack(unsigned char ackbit)
- * 函数功能 : 产生应答
- * 输 入 : 0或1
- * 输 出 : 无
- * 备 注 : 0表示产生应答,1表示不产生应答
- *******************************************************************************/
- void IIC_Ack(unsigned char ackbit)
- {
- I2C_SCL = 0;
- I2C_SDA = ackbit;
- delay_us(2);
- I2C_SCL = 1;
- }
二、主从机I2C通信过程:
1.主机发送过程
(1)主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始
(2)主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W组成(此时 R/W=0)
(3)相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0)
(4)主机收到从机的应答信号后开始发送第一个字节的数据
(5)从机收到数据后返回一个应答信号 ACK
(6)主机收到应答信号后再发送下一个数据字节
(7)当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信
2.主机接收过程
(1)主机发送启动信号后,接着发送命令字节(其中 R/W=1)
(2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据
(3)主机收到数据后向从机反馈一个应答信号
(4)从机收到应答信号后再向主机发送下一个数据
(5)当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ASK=1 的非应答信号后便停止发送
(6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信
三、以51单片机为主机,AT24C02为从机的I2C主从机通信程序
/******************************************************************************** 函 数 名 : write_eeprom(unsigned char add,unsigned char val)* 函数功能 : 向eeprom写一个字节* 输 入 : eeprom中某个地址和要写入的数据字节* 输 出 : 无* 备 注 : 无*******************************************************************************/void write_eeprom(unsigned char add,unsigned char val){ I2C_Start(); I2C_SendByte(0xa0); I2C_Wait_Ack(); I2C_SendByte(add); I2C_Wait_Ack(); I2C_SendByte(val); I2C_Wait_Ack(); I2C_Stop();}/******************************************************************************** 函 数 名 : unsigned char read_eeprom(unsigned char add)* 函数功能 : 从eeprom读一个字节* 输 入 : eeprom中的某个地址* 输 出 : 选中地址里面存储的数据* 备 注 : 无*******************************************************************************/unsigned char read_eeprom(unsigned char add){unsigned char da; I2C_Start();I2C_SendByte(0xa0);I2C_Wait_Ack();I2C_SendByte(add);I2C_Wait_Ack();I2C_Start();I2C_SendByte(0xa1);I2C_Wait_Ack();da = IIC_Read_Byte(); I2C_Stop();return da;}