# I2C总线接口 - [概要](#概要) - [什么是I2C](#什么是i2c) - [通信原理](#通信原理) - [物理接线](#物理接线) - [数据有效性](#数据有效性) - [起始条件和停止条件](#起始条件和停止条件) - [数据格式](#数据格式) - [响应ACK](#响应ack) - [PDU](#pdu) - [I2C传输通用方法集合](#i2c传输通用方法集合) - [I2C引脚定义](#i2c引脚定义) - [machine.I2C API详解](#machinei2c-api详解) - [类](#类) - [class I2C(id,scl,sda,freq)](#class-i2cidsclsdafreq) - [构造I2C](#构造i2c) - [函数](#函数) - [扫描I2C地址](#扫描i2c地址) - [写数据](#写数据) - [读数据](#读数据) - [示例](#示例) ## 概要 本节简要介绍I²C总线协议,并以一个简单的例程介绍如何在通过 `Python` 使用 `OpenHarmony` 的 I²C 接口 ### 什么是I2C - I²C(Inter-integrated Circuit)最早是飞利浦在1982年开发设计的一种简单、双向二线制同步串行总线协议。 - I²C总线支持设备之间的短距离通信,用于处理器和一些外围设备之间的接口,它只需要两根信号线来完成信息交换。 ### 通信原理 #### 物理接线  I²C最少只需要两根线,和异步串口类似,但可以支持多个从(slave)设备,和SPI不同的是,I²C可以支持多主机(mul-master)系统,允许有多个master并且每个master都可以与所有的slaves通信(master之间不可通过I²C通信,并且每个master只能轮流使用I²C总线)。master是指启动数据传输的设备并在总线上生成时钟信号以驱动该传输,而被寻址的通信设备都作为slaves。 I²C通讯只需要2条双向总线: | 功能编号 | 含义 | | ------------------------------ | --------------------------------------------------- | | SDA(serial data:串行数据线) | 传输数据,SDA线传输数据是大端传输,每次传输一个字节 | | SCL(serial clock:串行时钟线) | 同步数据收发 | 一个主设备和两个从设备的连接示意图如下图所示:  I²C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,当主设备需要和某一个从设备通信时,通过广播的方式,将从设备地址写到总线上,如果某个从设备符合此地址,将会发出应答信号,建立传输。 #### 数据有效性 SDA 线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。 换言之,SCL为高电平时表示有效数据,SDA为高电平表示“1”,低电平表示“0”;SCL为低电平时表示无效数据,此时SDA会进行电平切换,为下次数据表示做准备。如图为数据有效性的时序图。  #### 起始条件和停止条件 起始条件S:当SCL高电平时,SDA由高电平向低电平转换; 停止条件P:当SCL高电平时,SDA由低电平向高电平转换。 起始和停止条件一般由主机产生。总线在起始条件后处于busy的状态,在停止条件的某段时间后,总线才再次处于空闲状态。如下图为起始和停止条件的信号产生时序图。  #### 数据格式  I²C数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。 I²C传输的数据以字节为单位,每个字节必须为8位,可以传输任意多个字节,上图中以一个字节的数据为例进行分析,I2C的数据格式具有以下特点: - 每个字节后必须跟一个响应位 ACK(如上图中的SCL上ACK),因此实际上传输一个字节(8位)的数据需要花费9位的时间。 - SDA上首先传输字节的最高位,从上图中我们可以看出,位数编号的发送顺序从左至右 是 Bit7-Bit0 ##### 响应ACK 数据接收方收到传输的一个字节数据后,需要给出响应,此时处在第九个时钟,发送端释放SDA线控制权 ,将SDA电平拉高,由接收方控制。 接收方表示成功的接收到了8位一个字节的数据,便将SDA拉低为低电平,即ACK信号,表示应答  ##### PDU I²C的PDU(Protocol Data Unit:协议数据单元,即数据格式):    ### I2C传输通用方法集合 - I²C接口定义了完成I²C传输的通用方法集合,包括: - I²C控制器管理: 打开或关闭I2C控制器 - I²C消息传输:通过消息传输结构体数组进行自定义传输 ## I2C引脚定义 waffle nano 共有7个引脚可以用作 I²C 的通讯引脚。拥有 2 个 I²C 总线端口,根据用户的配置,总线端口可以用作 I²C 主机。 > 其中板载内置传感器连接到引脚:`Pin9,Pin10`。所以在操作板载传感器时需配置 `0` 号端口 I²C 总线对象,比如 `i2c = I2C(0, sda=Pin(9), scl=Pin(10)`。 | I²C 端口号 | SDA | SCL | | --------- | ------------ | ------------- | | I2C 1 | pin 0 | pin 1 | | I2C 0 | pin 9 | pin 10 | | I2C 0 | pin 13 | pin 14 | ## machine.I2C API详解   使用`from machine import I2C`导入`machine`模块的I2C总线接口类`I2C`   再使用`TAB` 按键来查看`I2C`中所包含的内容: ```python >>>from machine import I2C >>>I2C. read write readfrom_mem scan writeto_mem ``` ### 类 #### class I2C(id, scl, sda, freq=400000) - `id` — I²C设备组号,可以为0或1 - `scl` — I²C设备时钟引脚对象 - `sda` — I²C设备数据线引脚对象 - `freq` — SCL时钟频率 0 <`freq`≤ 500000(Hz) #### 构造I2C   用I2C对象的构造器函数   示例: ```python >>> from machine import I2C, Pin >>> i2c = I2C(0, sda=Pin(9), scl=Pin(10),freq=100000)#构建I2C0,时钟频率100000(时钟屏幕根据具体传感器而定,一般使用100000或400000) >>> i2c = I2C(1, sda=Pin(0), scl=Pin(1),freq=100000)##用pin1引脚当I2C1的scl功能成功 ```   使用ID直接构造   I2C的id只能取`0`或`1`,可以通过id来直接构造这两组接口。无法直接使用第1组I2C资源,默认I2C的SCL功能是由pin4引脚实现,会报`invalid scl pin(4)`错误,因为`IO3`、`IO4`被编程串口所占用。   示例: ```python >>> from machine import I2C, Pin >>> i2c0=I2C(0)#构建I2C0 >>> i2c0 I2C(0, scl=10, sda=9, freq=400000) >>> i2c1=I2C(1)#构建I2C1会报ValueError Traceback (most recent call last): File "<stdin>", in <module> ValueError: invalid scl pin(4) >>> i2c1 I2C(1, scl=4, sda=3, freq=400000) ``` ### 函数   在接下来的示例中, 构造`id=0`的`I2C`对象来列举I2C对象的函数。 ```python >>>from machine import I2C >>>i2c = I2C(0)#构建I2C0 ``` ##### 扫描I2C地址   `i2c.scan()`   函数说明:扫描I²C地址,并返回设备列表 ```python >>>from machine import I2C >>>i2c = I2C(0)#构建I2C0 >>> i2c.scan()#扫描I²C地址 [104, 118] ```   waffle nano本身有104和118两个地址的I²C总线,104是陀螺仪传感器,118是温湿度传感器。   I²C读写数据需要先扫描I²C地址,在相应的地址上读写数据 ##### 写数据   `i2c.write(addr,data)`   函数说明:向I2C写入(发送)数据,返回True或False表示是否写入成功.   `addr`I²C设备地址,可由scan函数读取出来   `data`: 需要写入(发送)的数据   示例: ```python >>>from machine import I2C >>>i2c = I2C(0)#构建I2C0 >>>i2c.write(104,b'\x01\x00\x09\x08')#向I2C0的104地址写入数据`\x01\x00\x09\x08` True ```   注意:   一般的IIC传感器都有寄存器的概念。视不同传感器而言,一般发送的数据前一位为寄存器地址,也就是`\x01\x00\x09\x08`中`0x01`为其寄存器地址,向`0x01`寄存器(含)往后将`x00\x09\x08`数据依次写入寄存器中去。也有些传感器使用的寄存器比较多,需要用两个字节表示寄存器地址,那么此时`\x01\x00\x09\x08`中`\x01\x00`表示其寄存器地址。向`0x01\x00`寄存器(含)往后将`x09\x08`数据依次写入寄存器中去。 ##### 读数据   `i2c.read(addr,length)`   函数说明:从指定地址设备读取数据,返回读取对象,这个对象与I²C设备有关   `addr`: I²C设备地址,可由scan函数读取出来   `length`: 读入的字节数 ```python from machine import I2C i2c = I2C(0)#构建I2C0 i2c.write(118,b'\x08')#从第0x08寄存器开始读取 print(i2c.read(118,10))#读取118地址上0x08寄存器往后的10个字节的数据 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ``` ##### 从寄存器中读取数据   `i2c.readfrom_mem(addr,register,length)`   函数说明:从指定地址设备的指定寄存器开始往后连续读取各寄存器数据。   `addr`:从机设备IIC地址。`register`:操作的起始寄存器地址。`length`: 读入的字节数。   注意:此方法仅仅适用于寄存器用一个字节描述的IIC元器件 ##### 向寄存器写入数据   `i2c.writeto_mem(addr,register,data)`   函数说明:向指定地址设备的指定寄存器开始往后连续将数据写入各寄存器中。   `addr`:从机设备IIC地址。`register`:操作的起始寄存器地址,`data`: 需要写入(发送)的数据。   注意:此方法仅仅适用于寄存器用一个字节描述的IIC元器件 ## 示例   将waffle nano的一些GPIO引脚构造成I2C总线,读写相应数据(需要连接相应的传感器)。 ```python from machine import I2C, Pin i2c = I2C(1, sda=Pin(0), scl=Pin(1),freq=100000) #构造IIC对象,将1号引脚设置为scl,将0号引脚设置为sda print(i2c.scan()) #扫描IIC总线上的传感器,返回传感器ID i2c.write(74,b'\x03') #向74号ID的传感器发送0x03数据 print(i2c.read(74,10)) #从74号ID的传感器读取10个字节数据 i2c.writeto_mem(105,0x03,b'\x01\x02') #向105号ID的传感器的0x03寄存器(含)以及其往后的寄存器中依次填入数据`0x01`、`0x02,每个寄存器分到1个字节。 print(i2c.readfrom_mem(105,0x80,128)) #从105号ID的传感器的0x80寄存器开始(含)依次读取128个寄存器的数据,并返回。 ```