1255浏览
查看: 1255|回复: 4

【教程】初步学习I2C总线通信并应用于创客开发

[复制链接]
本帖最后由 威威子爱捣鼓 于 2022-4-17 22:21 编辑

前言:
  在单片机开发中,我们会经常用到一些通信总线。面对I2C是创客在硬件开发中最常遇到的通讯总线,其原理其实非常复杂,对于初学者来说极不易于理解。其实,我们只需要简单了解I2C的历史、特性、原理,就可以上手学习如何在项目中使用它了。

本图来源于https://www.bilibili.com/video/BV1uP4y187cY?spm_id_from=333.337.search-card.all.click

本图来源于https://www.bilibili.com/video/BV1uP4y187cY?spm_id_from=333.337.search-card.all.click
  本篇文章将为你介绍I2C的一些基础知识,总体难度并不深,如果要深入了解有关内容,还行阅读相关专业书籍和文献。
  本篇前面部分我会着重介绍数据发送过程,如果你只是需要进行简单的应用开发,只需要大概了解即可。文章后面部分是一个非常非常简单的实际开发的案例,基于Arduino IDE。

SatApril-202204162445..png
  个人能力有限,文中内容描述如有不当,敬请指正!
一:I2C简介
  关于I2C的写法,其实是写作I²C的,但为了打字方便,多数情况下我们看到的都写作I2CI2C读作"I-squared-C" ,而"I-two-C"则是另一种错误但被广泛使用的读法,在大陆地区,由于小数字2在数学中被读为平方,我们多以"IC"称之。(就我们所说的总线而言,I2C有时候还写成“IIC”)
  那么,到底什么是I2C呢?
  MCU各功能需要通过总线相连,总线其实就是各个功能之间内部的通讯公共干线,通过总线,各个功能得以连接,相互作用。
  I2C其实就是内部整合电路的称呼,是一种串行通讯总线,由飞利浦(Philps)公司于80年代初设计并推广(I2C现已经被恩智浦公司收购),目的是为了在尽可能少的信号线下由尽可能高的通信速率。要知道,那时候的芯片,引脚能少占用就得少占用啊
  现如今,很多电子产品都涉及到I2C,如图是一块OLED小屏幕
SatApril-202204164663..png
  竟然通信线少,I2C到底有几根线呢?
你或许已经发现,其只有两根线,分别是SCL与SDA。
  SCL是数据信号线,用于传输数据信号。
  SDA是时钟信号线。
  I2C有一个很重要的特性,那就是主从设备是相对的,支持多设备(理论上支持127个从机)同时在线,还具有冲突检测等功能。
  每一个从机都会有一个唯一地址,计算机不像我们人类可以快速判断东西,需要通过地址的方式进行记录。
二:工作过程
I2C工作方式及应用连接方式:
我们已经大概了解了I2C,至于其原理,我们先不过多阐述,其中涉及内容过于复杂。
但我们还是要大致了解一下I2C整个的工作过程。

  I2C速率是有快有慢的,因此我们先了解一下其三种工作速率。
  在下表中,我列出了四种模式对应的速率及通常情况下需要上拉电阻的阻值。
模式速率
标准100bps
快速400bps
高速3.4Mbps
超快速(仅单向)5Mbps
  
       在I2C中,SCL与SDA都是开漏输出的。
在开漏输出模式下,当控制输出低电平时,开漏输出对应引脚接地;当要控制输出低file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image007.jpg
电平时,既不输出高电平,也不会输出低电平,处于高阻模式。
  开漏模式不输出电压,因此,我们需要外接一个合适阻值的电阻以在控制输出高电平时候把引脚拉到电阻两端的电压值。
  不同速率的模式下,I2C需要的上拉电阻是不同的。
SatApril-202204169592..png
主设备和从设备数据传输过程:
  在I2C总线上传送没每一位数据都有一个时钟脉冲对应(或者同步控制),在CSL串行时钟的配合下,SDA逐位地串行传输每一位数据(数据位传输为边沿触发)。
  I2C主设备与从设备进行数据传输时,需要遵循一定的数据结构序列,数据怎么进怎么传都是有规则的,数据需要一位一位进行传输。
SatApril-202204164560..png
  根据上图,我们来简单了解一下I2C数据传送的过程。
  1.开始位(绿色),主设备发送开始条件,所有从机被唤醒。
  2.设备地址位(深蓝色)。
  3.应答位(褐色)。
  4.寄存器地址位(浅蓝色)。
  5.数据块(橙色)。
  光看这个图可能会很懵,下面我来详细讲一讲。
  首先,要想传输数据必须要有一个开始条件。主设备发送开始条件,说“我要开始干活了”,这时候所有从机都听到了,即使它们处于睡眠中,也会被强制叫起来“被迫营业”,开始接收工作。
  接着,就是接收地址了。通常,地址位占7位数据。地址位,顾名思义是一串地址。主设备工作后,先接地址,不然怎么知道从机是谁呢?在前面我们说过,每一个从设备都是有一个唯一地址的,在这里,我们收到地址,才能找到它,与它进行通信。
  在图中的浅蓝色部分,还有一个格子上面写着“R/W”,那是什么呢?那其实是读写位,它的作用为确定数据的传输方向。
  有了从设备的地址,我们进入应答位。在图中,我们注意到有三个应答的过程。每完成一次8bits的序列,都需要有一个应答,这很好理解。主机每次发送完设备,都等待从设备的应答。应答位存在两种情况——ACK/NACK。主设备每次发完一段数据后,都要等待应答,从设备应答,要发送应答信号ACK,相当于跟从设备说“你发的数据我收到了,请你继续”。当数据出现错误或者从设备现在很忙的时候等情况下,会引发NACK。当引发NACK的时候,相应主设备收到不能继续发送的消息,会重新发送或者停止发送。
   看到第四部分,这一部分是内部寄存器的地址,或者是从设备的指令数据。
  接着,就是发送的数据块了。
  最后,还有一个停止位,一份数据发送完成,停止发送过程。
  下面,我们来看一下时序图来理解一下I2C的协议。
SatApril-202204162967..png
  上图是我在网上找到的一张时序图(7位地址协议,另外I2C还支持10位地址协议)。为了方便后续讲解,我将其标上字母。
SatApril-202204164049..png
   我们来了解一下我标注 A、B、C的部分。
  先来看A部分,我们看到,开始部分的条件是SCL为高电平,SDA由高跳低电平,此后,SDA随SCL时钟发送数据。
        看到B部分是读写位数据。主设备发送到从设备,为高电平,反之则为低电平。
        再看C部分应答位。若有应答信号ACK,SDA被拉低电平,如果从设备没有收到数据,SDA为高电平。
        从设备寄存器地址和内部寄存器地址要怎么理解?举个例子,我们通过I2C连接一个质量传感器模块,从设备地址位存的就是传感器模块的地址,而质量数据的内部寄存器地址放到内部寄存器地址中去。只有地址正确被配对,才能读取数据。
       我们继续看D部分结束位,结束条件可以看到是SCL高电平,SDA这时候从低电平跳到高电平,一个字节数据发送结束。
三:简单应用
      接下来,我将通过两个例子来实践一下。一是通过I2C实现两块Arduino板子的相互通信,二是控制一个LCD1206液晶显示器模块(模块带IO扩展芯片,接口转I2C)。
       两个例子都比较简单,我会用Arduino IED来编写程序(你需要有对Arduno的一定了解)。
两块Arduino板子间的相互通信:
       第一个例子,我们实现两块Arduino板子间的互相通信。我自己使用的是两块ArduinoUNO板子,一块作为主机发送信息,另一块作为从机接收信息。
       现在先来进行硬件的连接,这非常简单。
将第一块ArduinoUNO板子的SCL、和SDA口(4,5)连接到第二块板子的SCL、SDA口(SCL对应SCL,SDA对应SDA)。 接着,我们将第一块板子的GND口接到第二块板子的GND口。前面我们说过,还需要上拉电阻,在这个实例中,我在SCL和SDA上各上拉一个10千欧的电阻。
SatApril-202204164967..png


SatApril-202204169967..png

         按照图示连电路,我们就可以开始编程了。
  在编程中我们需要用到Arduino中的一个库——Write库,其用于与I2C 设备进行通信。
        下面我们来看示例代码。(原作者:Author
  主机:
  1. #include <Wire.h>   //引入Wire库
  2. void setup()
  3. {
  4.    
  5.     Wire.begin();   //初始化, 加入总线,()内没有指定就是默认以主机身份加入的
  6. }
  7. byte x = 0;   //定义一个byte变量
  8. void loop()
  9. {
  10.     //将数据传送到从设备#8
  11.     Wire.beginTransmission(8);
  12.     //发送数据
  13.     Wire.write("WeiWeizi loves to play tricks ");
  14.     //发送一个字节
  15.     Wire.write(x);
  16.     //停止发送
  17.     Wire.endTransmission();
  18.     x++;    //变量递增
  19.     delay(500);   //延时
  20. }
复制代码


从机:
  1. #include <Wire.h>   //引入Wire库
  2. void setup()
  3. {
  4.    
  5.     Wire.begin(8);    //初始化,以从设备地址8的身份加入总线
  6.    
  7.     Wire.onReceive(receiveEvent);   //注册接受事件函数
  8.    
  9.     Serial.begin(9600);   //初始化串口
  10. }
  11. void loop()
  12. {
  13.     delay(100);
  14. }
  15. void receiveEvent(int howMany)
  16. {
  17.     //循环读取数据
  18.     while (1 < Wire.available())
  19.     {
  20.         //接收并打印
  21.         char c = Wire.read();
  22.         Serial.print(c);
  23.     }
  24.     //接收并打印
  25.     int x = Wire.read();
  26.     Serial.println(x);
  27. }
复制代码



      设置好开发板和端口,分两次上传程序(烧写程序时候先断开SDA、SCL),打开从机端口的串口监视器,我们就可以看到效果了。
      注意:我们前面介绍的I2C地址8位ArduinoWire库始终使用的是7位地址如果我们使用了8位,则需要删除低位才能得到得到0到127之间的地址。但地址从07 就被保留了, 因此我们从8开始用,先前先不要乱搞。
控制一个LCD1206液晶显示器模块
      我们来进行第二个实例吧。
      首先,连接好硬件。
  SCL和SDA分别接到屏幕模块上的SCL和SDA,5V和GND分别接到模块的VCC和GND上。
        下面我们开始编程。
        注意先安装好LiquidCrystal_I2C库。
        首先,要确定I2C的地址详见https://github.com/nickgammon

        然后画一个像素画,内容为:Hello World!”:
  1. #include <LiquidCrystal_I2C.h>
  2. LiquidCrystal_I2C lcd(0x3F,16,2);  //设置LCD地址为Ox3F,为16*2显示
  3. void setup() {
  4. lcd.init();
  5. lcd.clear();         
  6. lcd.backlight();      //确保背光灯亮着
  7. //在LCD的两行上打印一条信息
  8. lcd.setCursor(2,0);   //将光标指向第0行上的字符2
  9. lcd.print("Hello world!");
  10. lcd.setCursor(2,1);   //移动光标到第一行的字符2
  11. lcd.print("LCD Tutorial");
  12. }
  13. void loop() {
  14. }
复制代码


      注意:不同厂商的LCD模块的地址可能是不同的,应用开发中需要自行设置调整。
模块上的IO扩展芯片有多种厂商,例如常见的有:TI(德州仪器公司)的PCF8574和NXP(恩智浦半导体公司)的PCF8574,如果你想了解更多,可以去芯片厂家官网查看相应的数据手册。

结语:

     一下就写了三千多字了,本篇文章的主要内容到这里就要结束了,希望你们能有所收获。
  参考文献:
  [1]I2C-busspecification and user manual[D], 20211001.
  [2]程晨. Arduino开发实战指南:AVR篇[M].机械工业出版社, 201203.

    个人能力有限,文中内容描述如有不当,敬请指正!
[size=29.3333px]                                          (部分内容来自文献资料和网络,转载本文请说明原作者!)

Hockel  中级技匠

发表于 2022-4-17 08:57:50

目测会火,可以考虑再出一期:如何封装一个IIC的库。
回复

使用道具 举报

威威子爱捣鼓  中级技师
 楼主|
来自手机

发表于 2022-4-17 10:52:53

Hockel 发表于 2022-4-17 08:57
目测会火,可以考虑再出一期:如何封装一个IIC的库。

关于总线的一些教程正在研究中,平时大家应用应该都挺多,深入起来其实还需要研究一下的。
会考虑继续写一些总线相关的和库相关的~
回复

使用道具 举报

天明zzb  见习技师

发表于 2022-7-19 23:25:23

您好大佬,我的一个模块是树上科技的rgb全彩灯,他是用I2c控制的,如果让他在arduino下进行使用呢?我并没有找到相关的资料,求教
回复

使用道具 举报

威威子爱捣鼓  中级技师
 楼主|

发表于 2022-8-12 10:59:52

天明zzb 发表于 2022-7-19 23:25
您好大佬,我的一个模块是树上科技的rgb全彩灯,他是用I2c控制的,如果让他在arduino下进行使用呢?我并没 ...

不是很了解树上科技
不过RGB模块比较简单的,应该都有官方资料的啊
回复

使用道具 举报

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

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2022 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail