找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 19190|回复: 9
收起左侧

51单片机I2C详解与程序源码

  [复制链接]
ID:338578 发表于 2018-5-26 12:09 | 显示全部楼层 |阅读模式
I2C是由Philips公司发明的一种串行数据通信协议,仅使用两根信号线:SerialClock(简称SCL)和SerialData(简称SDA)。I2C是总线结构,1个Master,1个或多个Slave,各Slave设备以7位地址区分,地址后面再跟1位读写位,表示读(=1)或者写(=0),所以我们有时也可看到8位形式的设备地址,此时每个设备有读、写两个地址,高7位地址其实是相同的。
I2C数据格式如下:
无数据:SCL=1,SDA=1;
开始位(Start):当SCL=1时,SDA由1向0跳变;
停止位(Stop):当SCL=1时,SDA由0向1跳变;
数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数据,不可随意改变SDA;
当SCL保持为0时,SDA上的数据可随意改变;
地址位:定义同数据位,但只由Master发给Slave;
应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;
否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。

当数据为单字节传送时,格式为:
开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。
当数据为一串字节传送时,格式为:
开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。

需要注意的是:
1,SCL一直由Master控制,SDA依照数据传送的方向,读数据时由Slave控制SDA,写数据时由Master控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反。
2,开始位“Start”和停止位“Stop”,只能由Master来发出。
3,地址的8位传送完毕后,成功配置地址的Slave设备必须发送“ACK”。否则否则一定时间之后Master视为超时,将放弃数据传送,发送“Stop”。
4,当写数据的时候,Master每发送完8个数据位,Slave设备如果还有空间接受下一个字节应该回答“ACK”,Slave设备如果没有空间接受更多的字节应该回答“NACK”,Master当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时Master放弃数据传送,发送“Stop”。
5,当读数据的时候,Slave设备每发送完8个数据位,如果Master希望继续读下一个字节,Master应该回答“ACK”以提示Slave准备下一个数据,如果Master不希望读取更多字节,Master应该回答“NACK”以提示Slave设备准备接收Stop信号。
6,当Master速度过快Slave端来不及处理时,Slave设备可以拉低SCL不放(SCL=0将发生“线与”)以阻止Master发送更多的数据。此时Master将视情况减慢或结束数据传送。

在实际应用中,并没有强制规定数据接收方必须对于发送的8位数据做出回应,尤其是在Master和Slave端都是用GPIO软件模拟的方法来实现的情况下,编程者可以事先约定数据传送的长度,slave不检查NACK,有时可以起到减少系统开销的效果。但是如果slave方是硬件i2c要求一定要标准的NACK,master方是GPIO软件模拟i2c并没有正确的发送NACK,就会出现“slave收不到stop”导致i2c挂死。

在正常情况下,I2C总线协议能够保证总线正常的读写操作。但是,当I2C主设备异常复位时(看门狗动作,板上电源异常导致复位芯片动作,手动按钮复位等等)有可能导致I2C总线死锁产生。下面详细说明一下总线死锁产生的原因。

    在I2C主设备进行读写操作的过程中.主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从设备输出应答信号,将SDA信号拉为低电平。如果这个时候主设备异常复位,SCL就会被释放为高电平。此时,如果从设备没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。而对于I2C主设备来说.复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,I2C主设备等待从设备释放SDA信号,而同时I2C从设备又在等待主设备将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作,I2C从设备应答后输出数据,如果在这个时刻I2C主设备异常复位而此时I2C从设备输出的数据位正好为0,也会导致I2C总线进入死锁状态。

方法

    (1)尽量选用带复位输人的I2C从器件。

    (2)将所有的从I2C设备的电源连接在一起,通过MOS管连接到主电源,而MOS管的导通关断由I2C主设备来实现。
    (3)在I2C从设备设计看门狗的功能。

    (4)在I2C主设备中增加I2C总线恢复程序。

        每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的SCL时钟线产生9个时钟脉冲(针对8位数据的情况,“9个clk可以激活”的方法来自NXP的文档,NXP(Philips)作为I2C总线的鼻祖,这样的说法是可信的),这样I2C从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。

        这种方法有很大的局限性,因为大部分主设备的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲。

        或者,发送I2C_Stop条件也能让从设备释放总线。

        如果是GPIO模拟I2C总线实现,那么在I2C操作之前,加入I2C总线状态检测I2C_Probe,如果总线被占用,则可尝试恢复总线,待总线释放后,再进行操作。要保证I2C操作最小单元的完整性,不被其他事件(中断、高优先级线程,等)打断。

  (5)在I2C总线上增加一个额外的总线恢复设备。这个设备监视I2C总线。当设备检测到SDA信号被拉低超过指定时间时,就在SCL总线上产生9个时钟脉冲,使I2C从设备完成读操作,从死锁状态上恢复出来。总线恢复设备需要有具有编程功能,一般可以用单片机或CPLD实现这一功能。

  (6)在I2C上串人一个具有死锁恢复的I2C缓冲器,如Linear公司的LTC4307是一个双向的I2C总线缓冲器,并且具有I2C总线死锁恢复的功能。LTC4307总线输入侧连接主设备,总线输出侧连接所有从设备。当LTC4307检测到输出侧SDA或SCL信号被拉低30ms时,就自动断开I2C总线输入侧与输出侧的连接.并且在输出侧SCL信号上产生16个时钟脉冲来释放总线。当总线成功恢复后,LTC4307会再次连接输入输出侧,使总线能够正常工作。


  1. void I2Cstart()//开始标志  
  2. {     
  3.     SDA=1;  
  4.     SCL=1;  
  5.     SDA=0;  
  6.     delay1ms(4);  
  7.     SCL=0;  
  8.     delay1ms(4);  
  9. }  
  10. void I2Cstop()//结束标志  
  11. {  
  12.     SCL=0;  
  13.     delay1ms(4);  
  14.     SDA=0;  
  15.     delay1ms(4);  
  16.     SCL=1;  
  17.     delay1ms(4);  
  18.     SDA=1;  
  19.     delay1ms(4);  
  20. }  
  21. unsigned char I2Creadack()  
  22. {  
  23.     unsigned char i,byte;  
  24.     byte=0;  
  25.     for(i=0;i<8;i++)  
  26.     {  
  27.         SCL=0;  
  28.         SDA=1;  
  29.         delay1ms(4);  
  30.         byte<<=1;  
  31.         if(SDA==1)  
  32.         {  
  33.             byte|=0x01;  
  34.             delay1ms(4);  
  35.         }  
  36.     }  
  37.     SCL=0;  
  38.     delay1ms(4);  
  39.     SDA=0;  
  40.     delay1ms(4);  
  41.     SCL=1;  
  42.     delay1ms(4);  
  43.     SCL=0;  
  44.     return byte;  
  45. }  
  46. void I2Csend(unsigned char byte)//I2C写数据的过程  
  47. {  
  48.   
  49.     unsigned char mask,i;  
  50.     for(i=0;i<8;i++)  
  51.     {     
  52.         SCL=0;  
  53.         if((mask&byte)==0)  
  54.         {  
  55.             SDA=0;  
  56.         }  
  57.         else  
  58.         {  
  59.             SDA=1;  
  60.         }  
  61.         mask>>=1;  
  62.         delay1ms(4);  
  63.         SCL=1;//给足够时间让数据读取  
  64.         delay1ms(4);  
  65.   
  66.     }  
  67.     SCL=0;  
  68.     SDA=1;      //因为总线上有一个信号为低则低  
  69.     delay1ms(4);  
  70.     SCL=1;  
  71.     delay1ms(4);//等待应答位  
  72.     SCL=0;  
  73. }  
  74. unsigned char I2Cread(void)  
  75. {  
  76.     unsigned char i,byte;  
  77.     byte =0;  
  78.     for(i=0;i<8;i++)  
  79.     {  
  80.         SCL=0;  
  81.         SDA=1;//读数据必须拉高  
  82.         delay1ms(4);  
  83.         SCL=1;//数据稳定  
  84.         delay1ms(4);  
  85.         byte<<=1;  
  86.         if(SDA==1)  
  87.         {  
  88.             byte|=0x01;  
  89.         }  
  90.         delay1ms(4);  
  91.     }  
  92.     SCL=0;  
  93.     delay1ms(4);  
  94.     SDA=0;//发送的应答位  
  95.     delay1ms(4);  
  96.     SCL=1;  
  97.     delay1ms(4);  
  98.     SCL=0;  
  99.     return byte;  
  100. }  
  101. unsigned char I2Cread_eeprom(unsigned char addr)//I2C读取数据  
  102. {  
  103.     unsigned char datebyte,datebyte2;  
  104.     I2Cstart();  
  105.     I2Csend(0xa0);//写数据  
  106.     I2Csend(addr);  
  107.     I2Cstart();  
  108.     I2Csend(0xa1);//读数据  
  109.     datebyte2=I2Creadack();  
  110.     datebyte=I2Cread();  
  111.     I2Cstop();  
  112.     return datebyte;  
  113.   
  114. }  
  115.   
  116. void write_eeprom(unsigned char addr,unsigned char datebyte)  
  117. {  
  118.     I2Cstart();  
  119.     I2Csend(0xa0);  
  120.     I2Csend(addr);  
  121.     I2Csend(datebyte);  
  122.     I2Cstop();  
  123. }  
复制代码


i2c.zip

723 Bytes, 下载次数: 173, 下载积分: 黑币 -5

示例程序

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:184273 发表于 2018-8-22 15:39 | 显示全部楼层
多谢楼主分享!
回复

使用道具 举报

ID:585781 发表于 2019-7-22 14:41 | 显示全部楼层
这个i2c协议真厉害
回复

使用道具 举报

ID:476487 发表于 2019-9-24 10:45 | 显示全部楼层
讲解真详细,谢谢楼主分享
回复

使用道具 举报

ID:18797 发表于 2019-10-4 17:47 | 显示全部楼层
谢谢,进一步增强了对IIC的认识
回复

使用道具 举报

ID:558533 发表于 2019-10-15 08:54 | 显示全部楼层
谢谢楼主,
回复

使用道具 举报

ID:476274 发表于 2020-2-16 20:32 | 显示全部楼层
不错,学习下
回复

使用道具 举报

ID:891939 发表于 2021-4-1 00:23 | 显示全部楼层
请问  if((mask&byte)==0)   此语句怎么理解?mask没初始化,它和参数byte按位与结果不都是0x00吗?
回复

使用道具 举报

ID:24486 发表于 2024-3-13 20:56 | 显示全部楼层
xxx1949 发表于 2021-4-1 00:23
请问  if((mask&byte)==0)   此语句怎么理解?mask没初始化,它和参数byte按位与结果不都是0x00吗?

讲解真详细,谢谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表