对I2C总线时序的一点理解以及ACK和NACK(NAK)

来源:互联网 发布:windows api 下载文件 编辑:程序博客网 时间:2024/05/22 11:53

http://www.cnblogs.com/zym0805/archive/2011/07/31/2122890.html
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会再次连接输入输出侧,使总线能够正常工作。



http://www.openhw.org/sbogwxf230/blog/13-10/236383_4cff3.html

使用了STC12LE5206AD芯片

[cpp] view plain copy print?
  1. /* 
  2.  I2C.h 
  3.  标准80C51单片机模拟I2C总线的主机程序头文件 
  4. */  
  5.   
  6.   
  7. #ifndef _I2C_H_  
  8. #define _I2C_H_  
  9.   
  10.   
  11. #include "stc_new_8051.h"  
  12. #include "common.h"  
  13.   
  14.   
  15. //模拟I2C总线的引脚定义  
  16. //sbit I2C_SCL = SMBUS_SCL;  
  17. //sbit I2C_SDA = SMBUS_SDA;  
  18. #define I2C_SCL  SMBUS_SCL  
  19. #define I2C_SDA  SMBUS_SDA  
  20.   
  21.   
  22. //定义I2C总线时钟的延时值,要根据实际情况修改,取值1~255  
  23. //SCL信号周期约为(I2C_DELAY_VALUE*4+15)个机器周期  
  24. #define I2C_DELAY_VALUE  20  
  25.   
  26.   
  27. //定义I2C总线停止后在下一次开始之前的等待时间,取值1~65535  
  28. //等待时间约为(I2C_STOP_WAIT_VALUE*8)个机器周期  
  29. //对于多数器件取值为1即可;但对于某些器件来说,较长的延时是必须的  
  30. #define I2C_STOP_WAIT_VALUE 20  
  31.   
  32.   
  33. //I2C总线初始化,使总线处于空闲状态  
  34. void I2C_Init();  
  35.   
  36.   
  37. //主机通过I2C总线向从机发送多个字节的数据  
  38. bit I2C_Puts(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char Size, char *dat);  
  39.   
  40.   
  41. //主机通过I2C总线向从机发送1个字节的数据  
  42. bit I2C_Put(unsigned char SlaveAddr, unsigned char SubAddr, char dat);  
  43.   
  44.   
  45. //主机通过I2C总线从从机接收多个字节的数据  
  46. bit I2C_Gets(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char Size, unsigned char *dat);  
  47.   
  48.   
  49. //主机通过I2C总线从从机接收1个字节的数据  
  50. bit I2C_Get(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char *dat);  
  51.   
  52.   
  53. #endif //_I2C_H_  

[cpp] view plain copy print?
  1. /* 
  2.  I2C.c 
  3.  标准80C51单片机模拟I2C总线的主机程序 
  4.  
  5. */  
  6.   
  7.   
  8. #include "I2C.h"  
  9.   
  10.   
  11. //定义延时变量,用于宏I2C_Delay()  
  12. unsigned char data I2C_Delay_t;  
  13.   
  14. /* 
  15. 宏定义:I2C_Delay() 
  16. 功能:延时,模拟I2C总线专用 
  17. */  
  18. #define I2C_Delay()\  
  19. {\  
  20.  I2C_Delay_t = (I2C_DELAY_VALUE);\  
  21.  while ( --I2C_Delay_t != 0 );\  
  22. }  
  23.   
  24. //void uart_flag(void)  
  25. //{  
  26. // UART_TXD=1;  
  27. // UART_TXD=0;  
  28. // UART_TXD=1;  
  29. //}  
  30. /* 
  31. 函数:I2C_Init() 
  32. 功能:I2C总线初始化,使总线处于空闲状态 
  33. 说明:在main()函数的开始处,通常应当要执行一次本函数 
  34. */  
  35. void I2C_Init()  
  36. {  
  37.  I2C_SCL = 1;  
  38.  I2C_Delay();  
  39.  I2C_SDA = 1;  
  40.  I2C_Delay();  
  41. }  
  42.   
  43.   
  44. /* 
  45. 函数:I2C_Start() 
  46. 功能:产生I2C总线的起始状态 
  47. 说明: 
  48.  SCL处于高电平期间,当SDA出现下降沿时启动I2C总线 
  49.  不论SDA和SCL处于什么电平状态,本函数总能正确产生起始状态 
  50.  本函数也可以用来产生重复起始状态 
  51.  本函数执行后,I2C总线处于忙状态 
  52. */  
  53. void I2C_Start()  
  54. {  
  55.  EA=0;  
  56.   
  57.  I2C_SCL = 1;  
  58.  I2C_Delay();  
  59.  I2C_SDA = 1;  
  60.  I2C_Delay();  //起始条件建立时间大于4.7us延时  
  61.  I2C_SDA = 0;  //发送起始信号  
  62.  I2C_Delay();  
  63.  I2C_SCL = 0;  //钳住I2C总线,准备发送或接收数据  
  64.  I2C_Delay();  
  65.  I2C_Delay();  
  66.  I2C_Delay();  
  67. }  
  68.   
  69. /* 
  70. 函数:I2C_Stop() 
  71. 功能:产生I2C总线的停止状态 
  72. 说明: 
  73.  SCL处于高电平期间,当SDA出现上升沿时停止I2C总线 
  74.  不论SDA和SCL处于什么电平状态,本函数总能正确产生停止状态 
  75.  本函数执行后,I2C总线处于空闲状态 
  76. */  
  77. void I2C_Stop()  
  78. {  
  79.  unsigned int t = I2C_STOP_WAIT_VALUE;  
  80.   
  81.  I2C_SDA = 0;  //发送结束条件的数据信号  
  82.  I2C_Delay();  
  83.   
  84.  I2C_SCL = 1;  //发送结束条件的时钟信号  
  85.  I2C_Delay();  
  86.  I2C_SDA = 1;  //发送I2C总线结束信号  
  87.  I2C_Delay();  
  88.  EA=1;  
  89.  while ( --t != 0 );  //在下一次产生Start之前,要加一定的延时  
  90. }  
  91.   
  92.   
  93. /* 
  94. 函数:I2C_Write() 
  95. 功能:向I2C总线写1个字节的数据 
  96. 参数: 
  97.  dat:要写到总线上的数据 
  98. */  
  99. void I2C_Write(unsigned char dat)  
  100. {  
  101.   /*发送1,在SCL为高电平时使SDA信号为高*/  
  102.   /*发送0,在SCL为高电平时使SDA信号为低*/  
  103.  unsigned char t ;  
  104.  for(t=0;t<8;t++)  
  105.  {  
  106.   
  107.   I2C_SDA = (bit)(dat & 0x80);  
  108.   I2C_Delay();  
  109.   I2C_SCL = 1;  //置时钟线为高,通知被控器开始接收数据位  
  110.   I2C_Delay();  
  111.   I2C_SCL = 0;    
  112.   I2C_Delay();  
  113.   dat <<= 1;  
  114.  }  
  115.   
  116.   
  117. }  
  118.   
  119.   
  120. /* 
  121. 函数:I2C_Read() 
  122. 功能:从从机读取1个字节的数据 
  123. 返回:读取的一个字节数据 
  124. */  
  125. unsigned char I2C_Read()  
  126. {  
  127.  unsigned char dat=0;  
  128.  unsigned char t ;  
  129.  bit temp;  
  130.  I2C_Delay();  
  131.  I2C_Delay();  
  132.  I2C_SDA = 1; //在读取数据之前,要把SDA拉高  
  133.   
  134.  I2C_Delay();  
  135.    
  136.  for(t=0;t<8;t++)  
  137.  {  
  138.   I2C_SCL = 0; /*接受数据*/  
  139.   I2C_Delay();  
  140.   I2C_SCL = 1;//置时钟线为高使数据线上升沿数据有效  
  141.   I2C_Delay();  
  142.   temp = I2C_SDA;  
  143.   dat <<=1;  
  144.   if (temp==1) dat |= 0x01;     
  145.  }  
  146.    
  147.   I2C_SCL = 0;  
  148.   I2C_Delay();  
  149.   
  150.  return dat;  
  151. }  
  152.   
  153.   
  154. /* 
  155. 函数:I2C_GetAck() 
  156. 功能:读取从机应答位 
  157. 返回: 
  158.  0:从机应答 
  159.  1:从机非应答 
  160. 说明: 
  161.  从机在收到每个字节的数据后,要产生应答位 
  162.  从机在收到最后1个字节的数据后,一般要产生非应答位 
  163. */  
  164. bit I2C_GetAck()  
  165. {  
  166.  bit ack;  
  167.  unsigned char Error_time=255;  
  168.   
  169.     I2C_Delay();    
  170.  I2C_SDA = 1; /*8位发送完后释放数据线,准备接收应答位 释放总线*/  
  171.  I2C_Delay();  
  172.  I2C_SCL = 1; /*接受数据*/  
  173.  I2C_Delay();  
  174.  do  
  175.  {    
  176.   ack = I2C_SDA;  
  177.     Error_time--;  
  178.    if(Error_time==0)  
  179.    {  
  180.      I2C_SCL = 0;  
  181.    I2C_Delay();  
  182.    return 1;  
  183.    }  
  184.  }while(ack);   //判断是否接收到应答信号  
  185.   
  186.   
  187.  I2C_SCL = 0;  //清时钟线,钳住I2C总线以便继续接收  
  188.  I2C_Delay();  
  189.  I2C_Delay();  
  190.  I2C_Delay();  
  191.  return 0;  
  192. }  
  193.   
  194.   
  195. /* 
  196. 函数:I2C_PutAck() 
  197. 功能:主机产生应答位或非应答位 
  198. 参数: 
  199.  ack=0:主机产生应答位 
  200.  ack=1:主机产生非应答位 
  201. 说明: 
  202.  主机在接收完每一个字节的数据后,都应当产生应答位 
  203.  主机在接收完最后一个字节的数据后,应当产生非应答位 
  204. */  
  205. void I2C_PutAck(bit ack)  
  206. {  
  207.   
  208.  I2C_SDA = ack;  //在此发出应答或非应答信号  
  209.  I2C_Delay();  
  210.  I2C_SCL = 1;   //应答  
  211.  I2C_Delay();   
  212.   
  213.  I2C_SCL = 0;  //清时钟线,钳住I2C总线以便继续接收  ,继续占用  
  214.  I2C_Delay();  //等待时钟线的释放  
  215.  I2C_Delay();  
  216.  I2C_Delay();  
  217.  I2C_Delay();  
  218.   
  219. }  
  220.   
  221.    
  222.   
  223.   
  224. /* 
  225. 函数:I2C_Puts() 
  226. 功能:主机通过I2C总线向从机发送多个字节的数据 
  227. 参数: 
  228.  SlaveAddr:从机地址(高7位是从机地址,最低位是写标志0) 
  229.  SubAddr:从机的子地址 
  230.  Size:数据的字节数 
  231.  *dat:要发送的数据 
  232. 返回: 
  233.  0:发送成功 
  234.  1:在发送过程中出现异常 
  235. */  
  236. bit I2C_Puts(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char Size, char *dat)  
  237. {  
  238. //检查长度  
  239.  if ( Size == 0 ) return 0;  
  240. //确保从机地址最低位是0  
  241.  SlaveAddr &= 0xFE;  
  242. //启动I2C总线  
  243.  I2C_Start();  
  244. //发送从机地址  
  245.  I2C_Write(SlaveAddr);  
  246.  if ( I2C_GetAck() )  
  247.  {  
  248.   I2C_Stop();  
  249.   return 1;  
  250.  }  
  251. //发送子地址  
  252.  I2C_Write(SubAddr);  
  253.  if ( I2C_GetAck() )  
  254.  {  
  255.   I2C_Stop();  
  256.   return 1;  
  257.  }  
  258. //发送数据  
  259.  do  
  260.  {  
  261.   I2C_Write(*dat++);  
  262.   if ( I2C_GetAck() )  
  263.   {  
  264.    I2C_Stop();  
  265.    return 1;  
  266.   }  
  267.  } while ( --Size != 0 );  
  268. //发送完毕,停止I2C总线,并返回结果  
  269.  I2C_Stop();  
  270.  return 0;  
  271. }  
  272.   
  273.   
  274. /* 
  275. 函数:I2C_Put() 
  276. 功能:主机通过I2C总线向从机发送1个字节的数据 
  277. 参数: 
  278.  SlaveAddr:从机地址(高7位是从机地址,最低位是写标志0) 
  279.  SubAddr:从机的子地址 
  280.  dat:要发送的数据 
  281. 返回: 
  282.  0:发送成功 
  283.  1:在发送过程中出现异常 
  284. */  
  285. bit I2C_Put(unsigned char SlaveAddr, unsigned char SubAddr, char dat)  
  286. {  
  287.  return I2C_Puts(SlaveAddr,SubAddr,1,&dat);  
  288. }  
  289.   
  290.   
  291. /* 
  292. 函数:I2C_Gets() 
  293. 功能:主机通过I2C总线从从机接收多个字节的数据 
  294. 参数: 
  295.  SlaveAddr:从机地址(高7位是从机地址,最低位是读标志1) 
  296.  SubAddr:从机的子地址 
  297.  Size:数据的字节数 
  298.  *dat:保存接收到的数据 
  299. 返回: 
  300.  0:接收成功 
  301.  1:在接收过程中出现异常 
  302. */  
  303. bit I2C_Gets(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char Size, unsigned char *dat)  
  304. {  
  305.   
  306. //检查长度  
  307.  if ( Size == 0 ) return 0;  
  308. //确保从机地址最低位是0  
  309.  SlaveAddr &= 0xFE; //确保最低位是0  
  310. //启动I2C总线  
  311.  I2C_Start();  
  312. //发送从机地址  
  313.  I2C_Write(SlaveAddr);  
  314.  if ( I2C_GetAck() )  
  315.  {  
  316.   I2C_Stop();  
  317.   return 1;  
  318.  }  
  319. //发送子地址  
  320.  I2C_Write(SubAddr);  
  321.  if ( I2C_GetAck() )  
  322.  {  
  323.   I2C_Stop();  
  324.   return 1;  
  325.  }  
  326. //发送重复起始条件  
  327.  I2C_Start();  
  328. //发送从机地址  
  329.  SlaveAddr |= 0x01;  
  330.  I2C_Write(SlaveAddr);  
  331.  if ( I2C_GetAck() )  
  332.  {  
  333.   I2C_Stop();  
  334.   return 1;  
  335.  }  
  336. //接收数据  
  337.  for (;;)  
  338.  {  
  339.   *dat++ = I2C_Read();  
  340.   if ( --Size == 0 )  
  341.   {  
  342.    I2C_PutAck(1);  
  343.    break;  
  344.   }  
  345.   I2C_PutAck(0);  
  346.  }  
  347.   
  348. //接收完毕,停止I2C总线,并返回结果  
  349.  I2C_Stop();  
  350.  return 0;  
  351. }  
  352.   
  353.   
  354. /* 
  355. 函数:I2C_Get() 
  356. 功能:主机通过I2C总线从从机接收1个字节的数据 
  357. 参数: 
  358.  SlaveAddr:从机地址(高7位是从机地址,最低位是读标志1) 
  359.  SubAddr:从机的子地址 
  360.  *dat:保存接收到的数据 
  361. 返回: 
  362.  0:接收成功 
  363.  1:在接收过程中出现异常 
  364. */  
  365. bit I2C_Get(unsigned char SlaveAddr, unsigned char SubAddr, unsigned char *dat)  
  366. {  
  367.  return I2C_Gets(SlaveAddr,SubAddr,1,dat);  
  368. }  
0 0