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

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

  1. 总线协议之I2C总线时序

    路漫漫其修远兮,吾将上下而求索 I2C总线时序 http://hi.baidu.com/yangfengyuk/blog/item/16e586fa52fd211f6d22ebfe.html/cmti ...

  2. I2C总线时序以及ACK和NACK(NAK),SCL被从机拉低?

    1.I2C协议详解及裸机程序分析 - 简书(通俗易懂) 根据上图,我们首先设置IICCON(来设置时钟),时钟源是PCLK(是50MHZ)太快了我们需要设置这个分频系数,把时钟降低,降低到我们想要的S ...

  3. I2C总线时序图: 8位设备地址 16位寄存器地址 16位数据

    有关I2C总线时序,下面的文章写得很好,推荐阅读 https://www.cnblogs.com/BitArt/archive/2013/05/28/3103917.html#commentform ...

  4. I2C总线时序以及ACK和NACK(NAK)

    额外资料:https://www.jianshu.com/p/ded5a26bf3aa 关于i2c的响应问题:对于每一个接收设备(从设备,slaver),当它被寻址后,都要求在接收到每一个字节后产生一 ...

  5. 对I2C总线协议的一些理解

    1.无论读与写,都是在时钟线为低时把数据送到数据总线上,在高时采样数据,把数据锁存到内部,所以读之前先把时钟线拉低,做好准备(数据线为高表示释放数据线),为接下来读数据做好准备.也就是时钟信号为低时, ...

  6. I2C详解(4) I2C总线的规范以及用户手册(3) I2C电气规格和时序

    I2C详解(4) I2C总线的规范以及用户手册(3) I2C电气规格和时序 I2C详解(1) 一文快速了解I2C的工作原理 I2C详解(2) I2C总线的规范以及用户手册(1) I2C 总线协议 I2 ...

  7. 干货总结:I2C总线详细要点

    [导读] 前文总结了单片机串口个人认为值得注意的一些要点,本文来梳理一下 I2C 总线的一些要点.这个题目有点大,本文对于 I2C 其实很多地方也没整清楚,只为了与前文形成系列,如果大家有补充欢迎留言 ...

  8. [STM32学习]——一文搞懂I2C总线

    目录 I2C总线的概念 I2C最重要的功能包括: I2C的物理层 I2C主要特点: I2C的高阻态 I2C物理层总结: I2C的协议层 初始(空闲)状态 开始信号: 停止信号 数据有效性 应答信号 I ...

  9. 对PCIE设备访问及其配置空间的一点理解

    讲讲对PCIE总线协议的一点理解吧.感觉每一年又会多一点理解,但不懂得地方仍很多. PCI总线是拓扑结构,PCI总线从0开始,不超过256(但一般不会一层一层挂太多).Device不超过32,Func ...

最新文章

  1. C++builder XE 安装控件 及输出路径
  2. 【锁相环系列1】锁相环的基本原理
  3. c++模板类静态成员变量_一文讲透父子类中静态变量,成员变量初始化顺序原理...
  4. Limit与skip
  5. android 布局颜色设置颜色设置,怎么在Android中利用view设置布局颜色
  6. tp886n路由器是第几代?
  7. SVN missing 解决
  8. SVN下载以及中文包安装
  9. 弱监督学习总结(1)
  10. 【Tools】HP/惠普v285w 量产工具
  11. 基于JavaWeb的C2C网上购物平台系统设计
  12. Java中hash算法细述
  13. Git如何保留两地并行开发的提交
  14. Gartner:2018年前沿技术预测
  15. 在MySQL中实现交叉表查询2(动态交叉表)
  16. Set与List的前辈是Collection,Map自成一派
  17. 云里黑白第五回——联想Y430p系统迁移
  18. 蚂蚁电竞ANT27VQ电子竞技显示器重磅来袭
  19. 朴素贝叶斯的三个常用模型:高斯(GaussianNB)、多项式(multinomial model)、伯努利(Bernoulli model)
  20. 手机软件系统测试用例设计大全

热门文章

  1. 主流的深度学习优化方法(SGD,SGDM,Adagrad,RMSProp,Adam)
  2. linux vdi虚拟化,VDI桌面虚拟化解决方案
  3. C程序设计语言第二版·新版,C程序设计语言(第2版·新版) PDF扫描版[19MB]
  4. C++学习(一二零)数据中心 云计算 大数据的概念
  5. Javascript监听回车键和取消回车键提交
  6. nrm 切换不同的源工具
  7. Smartbanner: Intelligent banner design framework that strikes a balance between freedom and rules
  8. 像素风放置类游戏-突袭盛宴Raid Party
  9. 背景图片的精灵图的使用
  10. 计算机网络连接显示678是什么意思,网络连接错误678什么意思 网络连接错误678解决方法【图文】...