1、I2C总线简介

  I2C总线是一种由PHILIPS公司开发的两线串行通讯总线,用于连接为控制器及其外围设备。

  I2C串行通讯总线由两条线组成:

  时钟线SCL。

  数据线SDA。

  时钟线SCL用来同步数据的传输,数据线SDA用来传输或读取数据。

  I2C总线通信设备之间常用连接方式如下:

  总线一般是指多个设备共用的信号线,比如上图中的SCL总线和SDA总线,这两个总线组成了I2C通讯总线,可以在这两个总线上挂载多个设备。每一个连接到I2C总线上的设备都有一个独立设备地址,主机通过设备地址识别响应的设备并与之通信。

  IC2总线一般需要使用上拉电阻接到电源端,这是因为I2C的设备的内部结构一般是开漏的,在平常状态时为高组态,状态时不确定的,通过上拉电阻将其上拉到高电平,使用这种方式也可以提高I2C总线的驱动能力。

  I2C总线有三种传输模式:

  标准模式:通讯速率为100Kbit/s。

  快速模式:通讯速率为400Kbit/s。

  高速模式:通讯速率为3.4Mbit/s。

  一般使用标准或快速这两个模式进行通讯,这是应该目前大多数I2C设备并不支持高速模式,具体使用哪种模式可以根据I2C的设备的规格资料进行查询。

2、I2C通讯协议

  I2C通讯协议包含起始信号、设备地址和读写信号、读或写数据信息、响应或非响应信号、结束信号。

  起始信号:

  起始信号由主机产生,主机通过在SCL为高电平的时候将SDA从输出高电平变为输出低电平来触发一个I2C的起始信号,如下图:

  主机在产生完起始信号之后,通过拉低SCL时钟线使总线处于空闲状态。主机产生起始信号的程序如下:

  1. void I2C_START(void)
  2. {
  3. I2C_SDA();
  4. I2C_SCL();
  5. I2C_DELAY();
  6. I2C_SDA();
  7. I2C_DELAY();
  8. I2C_SCL();
  9. }

  程序首先将SDA和SCL输出高电平,然后通过给SDA输出低电平触发起始信号,最后拉低SCL使总线处于空闲状态(也有一种说法是钳住总线)。

  停止信号:

  当不需要再进行I2C通讯时,主机产生一个停止信号来结束I2C通讯。主机通过在SCL为高电平时将SDA从输出低电平变为输出高电平来触发一个I2C结束信号,如下图:

  主机产生停止信号的程序如下:

  1. void I2C_STOP(void)
  2. {
  3. I2C_SCL();
  4. I2C_SDA();
  5. I2C_DELAY();
  6. I2C_SCL();
  7. I2C_DELAY();
  8. I2C_SDA();
  9. }

  程序首先将SCL和SDA输出低电平使总线处于空闲状态,然后使SCL输出高电平,最后再将SDA从输出低电平变为输出高电平,这样不仅产生了一个停止信号,并且还释放了总线(SCL和SDA都为高电平)。

数据采样

  主机发送启动信号之后,就可以向从机设备传输数据了。在传输数据时,每经过一个SCL时钟周期传输一个bit的数据,如果是主机写数据到从机,那么主机通过SDA在一个SCL时钟周期内传输一个bit给从机;如果是主机从从机中读取数据,那么主机通过SDA在一个SCL时钟周期内读取一个bit的数据。

  从I2C的起始信号和结束信号的介绍中可以看到,在SCL为高电平时,SDA电平的变换会触发起始信号或停止信号,所以在数据的传输过程中,不能在SCL为高电平时改变SDA的电平状态,也就是说SDA线上的数据应该在SCL为高电平的时候保持稳定。如下图:

  I2C总线通讯时数据的采样发生在SCL时钟周期的高电平时间段,数据的改变发生在SCL时钟周期的低电平时间段。也就是说数据是在SCL为高电平的时候传输或读取,在SCL为低电平的时候根据传输的数据改变SDA的电平状态。

  I2C总线在传输数据时,一般是先发送高位字节的数据,最后发送低位字节的数据。

  主机向从机发送一个字节的程序如下:

  1. uint8_t I2C_WRITE_BYTE(uint8_t dat)
  2. {
  3. uint8_t i;
  4.  
  5. I2C_SCL();
  6. I2C_SDA();//释放总线
  7. I2C_DELAY();
  8.  
  9. for(i = ;i < ;i ++)
  10. {
  11. if(dat & 0x80)
  12. {
  13. I2C_SDA();
  14. }
  15. else
  16. {
  17. I2C_SDA();
  18. }
  19. dat <<= ;
  20. I2C_DELAY();
  21. I2C_SCL();
  22. I2C_DELAY();
  23. I2C_SCL();
  24. }
  25.  
  26. i = I2C_WAIT_ACK();
  27.  
  28. return i;
  29. }

  发送程序第11~18行是根据要传输的数据dat来决定SDA的电平状态,这是在SCL为低电平的时候处理的,在SCL为高电平的时候保持SDA的输出状态不变。第26行是等待从机反馈应答信号,在后面会讲到应答信号的作用。第28行是返回的参数用来判断是否收到应答信号,如果没有收到应答信号则退出。

  主机从从机读取一个字节的程序如下:

  1. uint8_t I2C_READ_BYTE(uint8_t ack)
  2. {
  3. uint8_t i;
  4. uint8_t dat;
  5.  
  6. I2C_SCL();
  7. I2C_SDA_IN();
  8. I2C_SDA();
  9. I2C_DELAY();
  10.  
  11. for(i = ;i < ;i ++)
  12. {
  13. I2C_SCL();
  14. I2C_DELAY();
  15. dat <<= ;
  16. if(RD_I2C_SDA != )
  17. {
  18. dat |= 0x01;
  19. }
  20. I2C_SCL();
  21. I2C_DELAY();
  22. }
  23.  
  24. I2C_SDA_OUT();
  25.  
  26. if(ack != )
  27. {
  28. I2C_ACK();
  29. }
  30. else
  31. {
  32. I2C_NOT_ACK();
  33. }
  34.  
  35. return dat;
  36. }

  接收数据程序的第6~8行是将SDA数据线转换为输入状态。第15~19行是在SCL为高电平的时候读取SDA的电平状态,即接收到一个bit的数据。在接收完8个字节的数据后,第24行将SDA数据线转换为输出状态。第26~33行是根据函数输入的参数来决定接收到一个字节的数据之后反馈给从机的是应答信号还是非应答信号。最后一行是返回接收到的数据。

  IC2设备地址和数据方向:

  I2C总线上的每个从机设备都有一个属于自己的独立的设备地址,主机在发送起始信号之后,需要发送从机的设备地址来查找需要通讯的从机设备,因为总线上的I2C从机设备的地址是独立的,所以不会造成多个总线上的设备在传输数据时相互干扰。

  主机在发送设备地址的同时,还需要发送一个数据读写方向位,用来决定接下来的操作是向从机传输数据还是从从机读取数据。数据读写方向位一般包含在设备地址中发送给从机设备,如下图。

  bit7~bit1位是从机的设备地址,最低位bit0是数据读写方向位。

  当bit0位为0时,表示后面的操作是主机向从机发送数据。

  当bit0位为1时,表示后面的操作是主机从从机读取数据。

  关于I2C设备的地址需要注意的是,一般I2C设备在规格资料说明里给出的设备地址都是不包含数据读写方向位的,需要自己根据设备地址进行转换。比如说SHTC3温湿度传感器,在它的数据手册中给出的设备地址是0x70,在发送设备地址和读写方向位时,并不是发送0x70(最低位为0表示写)或0x71(最低位为1表示读),而是要经过以下变换得出的,首先将0x70左移一个bit,得到0xE0,再用0xE0的最低位来表示数据的读写方向位,即在给SHTC3发送写操作时应该发送0xE0(最低位为0表示写);给SHTC3发送读操作时应该发送0xE1(最低位为1表示读)。

  响应信号:

  在I2C总线通讯中,在传输完一个字节之后,接收数据的设备(主机和从机都有可能接收数据)会在第9个SCL时钟周期反馈一个应答信号或非应答信号给发送数据的设备,也就是说在I2C通讯中,数据的传输由8bit的数据和1bit的响应信号组成,即每一次传输会产生9个SCL时钟周期信号。如下图:

  响应信号包括应答信号(ACK)、非应答信号(NACK):

  应答信号(ACK):在第9个SCL时钟周期,数据发送端将SDA的控制权释放转换为输入状态,数据接收端控制SDA的输出,通过SDA数据线输出一个低电平来产生一个应答信号,数据发送端根据SDA数据线的电平状态来识别响应信号。

  非应答信号(NACK):在第9个SCL时钟周期,数据发送端将SDA的控制权释放转换为输入状态,数据接收端控制SDA的输出,通过SDA数据线输出一个高电平来产生一个非应答信号,数据发送端根据SDA数据的电平状态来识别响应信号。

  所以说应答信号和非应答信号是根据SDA数据在第9个SCL时钟周期时的电平状态来决定的,如果在第9个SCL时钟周期时SDA为低电平,那么就是应答信号;如果SDA是高电平,那么就是非应答信号。

  应答信号和非应答信号的作用如下:

    当主机向从机发送数据时:

  在正常的I2C通讯中,主机每发送一个字节的数据给从机后,从机都会反馈一个应答信号给主机,主机可以通过从机反馈的响应信号来判断与从机的通讯是否正常。如果发送完一个数据之后,从机反馈的是一个应答信号则说明数据传输成功,通讯正常;如果发送完一个数据之后,从机反馈的是一个非应道信号说明数据没有传输成功,通讯异常,可能是从机正在进行其它操作或异常,这时候主机应当发送一个停止信号来停止发送数据。

  一般主机在传输完一个数据给从机后,都会等待从机反馈应答信号,如果超时没有识别到应答信号,则说明收到的是非应答信号,程序如下:

  1. uint8_t I2C_WAIT_ACK(void)
  2. {
  3. uint8_t Wait_cnt;
  4.  
  5. I2C_SCL();
  6. I2C_SDA_IN();
  7. I2C_SDA();
  8. I2C_DELAY();
  9. I2C_SCL();
  10.  
  11. Wait_cnt = ;
  12.  
  13. while(RD_I2C_SDA != )
  14. {
  15. Wait_cnt--;
  16. if(Wait_cnt == )
  17. {
  18. break;
  19. }
  20. }
  21.  
  22. I2C_SCL();
  23. I2C_SDA_OUT();
  24.  
  25. if(Wait_cnt == )
  26. {
  27. I2C_STOP();
  28. }
  29.  
  30. return Wait_cnt;
  31. }

  以上是主机等待从机应答信号的程序,第5~9行首先在SCL为低电平的时候将SDA数据线切换为输入状态,然后将SCL输出高电平产生第9个SCL时钟周期。第13~20行是等待应答信号,如果SDA位低电平,说明识别到了应答信号,直接通过break退出循环;如果一直识别不到应答信号,当Wait_cnt的值被自减到0后退出循环。第22~23行是将SDA数据线切换为输出状态。第25~28行是根据Wait_cnt来决定是否要产生停止信号,如果Wait_cnt等于0说明收到的是非应答信号,这时候通讯已经异常,所以产生一个停止信号来结束通讯;如果Wait_cnt不等于0说明收到的应答信号,通讯正常。函数通过返回Wait_cnt的值来指示通讯是否正常。

  当主机从从机读取数据时:

  当主机读取从机的数据时,每读取完一个字节的数据时,主机通过反馈一个应答信号或非应答信号给从机,用来通知从机是继续传输数据还是停止传输数据。

  如果主机在读取完一个从机发送的数据之后,产生一个应答信号给从机,那么从机会继续发送下一个数据给主机。

  如果主机在读取完一个从机发送的数据之后,产生一个非应答信号给从机,那么从机则会停止发送数据。

  也就是说,在主机读取从机的过程中,从机时根据主机反馈的响应信号来决定是否要传输下一个数据的。

  在主机读取从机数据时,主机产生的应答信号程序如下:

  1. void I2C_ACK(void)
  2. {
  3. I2C_SCL();
  4. I2C_SDA();//发送低电平,响应从机
  5. I2C_DELAY();
  6. I2C_SCL();
  7. I2C_DELAY();
  8. I2C_SCL();
  9. } 

  在SCL为高电平的时候,SDA输出一个低电平来表示应答信号。

  在主机读取从机数据时,主机产生的非应答信号程序如下:

  1. void I2C_NOT_ACK(void)
  2. {
  3. I2C_SCL();
  4. I2C_SDA();//发送低电平,响应从机
  5. I2C_DELAY();
  6. I2C_SCL();
  7. I2C_DELAY();
  8. I2C_SCL();
  9. }

  在SCL为高电平的时候,SDA输出一个高电平来表示非应答信号。

  等待应答信号的程序在主机发送一个字节的函数I2C_WRITE_BYTE中用到;主机产生应答信号或非应答信号的程序在主机读取从机数据函数I2C_READ_BYTE中用到。

3、I2C通讯的读写过程

  主机写数据到从机的通讯过程如下图:

  图中灰色部分是主机产生的,白色部分是从机产生的。

  主机产生一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和写数据方向位R/W=0给从机,从机收到设备地址和R/W数据之后产生一个应答信号给主机,然后主机再发送一个字节的数据给从机,从机每收到一个数据都会产生一个应答信号给主机,当主机不需要再发送数据给从机或收到从机的非应答信号的时候产生一个停止信号P结束通讯。

  主机由从机中读取数据的通讯过程如下图:

  图中灰色部分是主机产生的,白色部分是从机产生的。

  主机产生一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和读数据方向位R/W=1给从机,从机收到设备地址和R/W数据之后产生一个应答信号给主机,然后主机开始读取从机发送过来的数据,主机每收到一个数据都会产生一个应答信号或非应答信号给从机,如果希望从机继续发送数据则反馈应答信号给从机;如果希望从机停止发送数据则反馈一个非应答信号给从机。当主机不需要再读取从机的数据时产生一个停止信号P结束通讯。

  I2C通讯的复合格式如下图:

  在I2C通讯过程中,可能需要先发送一个地址或命令给从机后再读取从机的数据,比如读取AT24C02的数据时,需要先发送需要读取的地址,然后再读取数据。主机需要先发送一个地址信息给从机,然后再读取数据,由于发送地址信息是一个写的过程,而读取数据是一个读的过程,这样就组成了复合通讯格式。

  主机首先发送一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和写数据方向位R/W=0给从机,等待从机反馈应答信号,再发送一个地址数据给从机后等待应答,然后再发送一个重复起始信号Sr,Sr信号之后发送一个字节的从机设备地址SLAVE ADDRESS和读数据方向位R/W=1给从机,以读取从机的数据,当读取完数据之后,主机产生一个结束信号P来停止通讯。 

IIC通讯程序的更多相关文章

  1. 用Verilog实现IIC通讯

    注意,此代码是错误代码,并不能实现想要的结果. 之所以留着,因为里面的enable 是独立开来的思想值得借鉴.就是控制单元和运算单元分开(我也是借鉴别人的实现思想).具体用verilogHDL实现II ...

  2. LPC1768的iic通讯

    LPC1768有三路IIC,其中IIC0支持高速模式和plus模式,另外两路是普通IIC,使用IIC的过程如下 首先依然是打开IIC时钟,同时打开GPIO时钟 然后配置引脚为IIC功能 另外,因为ii ...

  3. STM32作为主设备,Arduino作为从设备进行IIC通讯的注意要点

    近日公司的项目重心要往米思齐的Arduino图形化编程上转移了,需要我将STM32和Arduino的IIC通讯调通.之前Arduino并没怎么使用过,仅仅是将超声波的代码移植成TOF激光测距而已.网上 ...

  4. 基于esp32的IIC通讯

    本文源码地址在:http://download.csdn.net/download/noticeable/9962029 IIC 通讯应该是当代比较常用的几种通讯方式之一,其无需特殊的IO接口,连线方 ...

  5. HALCON串口通讯程序

    串口通讯程序   * Note: This example is meant to demonstrate the use of the serial interface * of HALCON.   ...

  6. Verilog实现IIC通讯第二版

    HMC5883三轴磁力传感器IIC通讯模块的VerilogHDL的实现 上一版并没有实现我想要的功能 0.0.1版   正在修订中   2013/9/2 //date :2013/7/7 //desi ...

  7. iic通讯 FPGA实现 mpu6050为例

    IIC最常用的通讯协议,但普遍用于单片机.arm这些,用FPGA实现大材小用,但对于菜鸡水平练练手很不错,考验串并转换和时序的控制.今天我就以mpu6050陀螺仪为例,实现FPGA的iic通信. 1. ...

  8. 「STM32 」IIC通讯原理及其实验

    I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工 ...

  9. 填坑-关于IIC通讯

    ​01.概述 在之前的文章中<STM32IIC详解>中详细讲解了IIC协议,并且使用是NXP的官方手册,demo示例使用IIC读取RTC芯片,运行正常,没有任何问题.并且更新了<II ...

随机推荐

  1. javascript学习2

    上次我们了解到 JavaScript提供了一组以window为核心的对象,实现了对浏览器窗口的访问控制.JavaScript中定义了6种重要的对象: window对象       表示浏览器中打开的窗 ...

  2. 利用n 升级工具升级Node.js版本及在mac环境下的坑

    一.利用n 升级Node.js 最近在用NPM安装一个nodejs工具时发现,我的nodejs的版本有些旧了.这不是大问题,只要升级就可以了,当然,重新从nodejs.org最新版本是一种方法,但我想 ...

  3. C#中Array类

    Array类是C#中所有数组的基类,它是在System命名空间中定义的,Array类提供了各种用于数组的属性和方法

  4. UGA,PGA

    tom认为UGA不包含 sort工作区,所以下面的图都是错误的 The UGA is, in effect, your session’s state. It is memory that your ...

  5. nginx详解反向代理、负载均衡、LNMP架构上线动态网站

    简介 Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器.N ...

  6. Django DTL 加减乘除求余

    django模板只提供了加法的filter,没有提供专门的乘法和除法运算: django提供了widthratio的tag用来计算比率,可以变相用于乘法和除法的计算. 加法 {{value|add:1 ...

  7. 浏览器内多个标签页之间的通信之storage

    在一个标签页里面使用 localStorage.setItem(key,value)添加(修改.删除)内容: 在另一个标签页里面监听 storage 事件. 即可得到 localstorge 存储的值 ...

  8. mongdb的聚合管道

    我们先介绍一下 MongoDB 的聚合功能,聚合操作主要用于对数据的批量处理,往往将记录按条件分组以后,然后再进行一系列操作,例如,求最大值.最小值.平均值,求和等操作.聚合操作还能够对记录进行复杂的 ...

  9. c# 在WebBrowser中用SendMessage模拟鼠标点击

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  10. T6跨账套辅助工具[v1.04]

    [v1.03] 增加自定义报表,用户可以自行设置报表所打开的数据表,然后设置查询条件 [v1.04]更改单据显示样式,直接以用友打印预览的方式显示,允许用自定义显示,打印样式 下图为新的显示样式 下图 ...