概要¶
本节教程为大家简要介绍I²C总线协议,并以一个简单的例程教会大家在MicroPython下使用I²C
快速开始
from machine import Pin, I2C
# construct a software I2C bus
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)
# construct a hardware I2C bus
i2c = I2C(0)
i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan() # scan for slave devices
i2c.readfrom(0x3a, 4) # read 4 bytes from slave device with address 0x3a
i2c.writeto(0x3a, '12') # write '12' to slave device with address 0x3a
什么是I²C
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:串行时钟线) | 同步数据收发 |
我们以最为简单的一个主机对应多个从机为例,进行接下来的原理科普。
数据有效性¶
SDA 线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。
换言之,SCL为高电平时表示有效数据,SDA为高电平表示“1”,低电平表示“0”;SCL为低电平时表示无效数据,此时SDA会进行电平切换,为下次数据表示做准备。如图13-1为数据有效性的时序图。
起始条件S和停止条件P¶
-
起始条件S:当SCL高电平时,SDA由高电平向低电平转换;
-
停止条件P:当SCL高电平时,SDA由低电平向高电平转换。
起始和停止条件一般由主机产生。总线在起始条件后处于busy的状态,在停止条件的某段时间后,总线才再次处于空闲状态。如下图为起始和停止条件的信号产生时序图。
数据格式¶
I2C传输的数据以字节为单位,每个字节必须为8位,可以传输任意多个字节,上图中以一个字节的数据为例进行分析,I2C的数据格式具有以下特点:
-
每个字节后必须跟一个响应位 ACK(如上图中的SCL上ACK),因此实际上传输一个字节(8位)的数据需要花费9位的时间。
-
SDA上首先传输字节的最高位,从上图中我们可以看出,位数编号的发送顺序从左至右 是
Bit7-Bit0
响应ACK¶
数据接收方收到传输的一个字节数据后,需要给出响应,此时处在第九个时钟,发送端释放SDA线控制权 ,将SDA电平拉高,由接收方控制。
接收方表示成功的接收到了8位一个字节的数据,便将SDA拉低为低电平,即ACK信号,表示应答
PDU¶
当你理解了时序图之后,接下来我们为大家贴出I2C的PDU(Protocol Data Unit:协议数据单元,即数据格式):
以下图片均来自网络
硬件资源¶
ESP32 本身拥有 2 个 I²C 总线接口,根据用户的配置,总线接口可以用作 I²C 主机或从机模式。
然而遗憾的是,MicroPython ESP32上的I²C是软件模拟的,并没有充分利用到ESP32的硬件资源。
理论上来讲,大部分同时支持输入与输出的GPIO都能够被配置为I²C的管脚资源。
I2C API文档
类
class machine.I2C(scl, sda, freq)
scl
: I²C设备时钟引脚对象
sda
: I²C设备数据线引脚对象
freq
: SCL时钟频率 0 <freq
≤ 500000(Hz)
定义I2C
示例:
from machine import I2C, Pin I2C = I2C(scl=Pin(5), sda=Pin(4), freq=100000)
常用类函数¶
I2C.init(scl, sda, freq)¶
函数说明:初始化构造I²C总线。
scl
:SCL信号线的I/O口
sda
:SDA信号线的I/O口
freq
:SCL时钟频率
示例:
I2C.init(scl=Pin(5), sda=Pin(4), freq=100000)
I2C.scan()¶
函数说明:扫描0x08到0x77之间的I²C地址,并返回设备列表。
示例:
I2C.scan()
I2C.start()¶
函数说明:在总线上触发START状态(SCL为高电平时,SDA转为低电平)。
示例:
I2C.start()
I2C.stop()¶
函数说明:在总线上触发STOP状态 (SCL为高电平时,SDA转为高电平)。
示例:
I2C.stop()
I2C.write(buf)¶
函数说明:buf中的数据写入到总线,并返回写入的字节数。
buf:存储数据的缓冲区
注意:
使用write()函数时要与start函数一起使用,否则无法返回写入的字节数。
示例:
buf = b'123' I2C.start() I2C.write(buf)
I2C.readinto(buf, nack=True)¶
函数说明:从总线上读取数据并存放到buf,无返回值。
buf:存储数据的缓冲区
注意:
读取的字节数是buf的长度。在接收到最后一个字节之前,总线将发送ACK信号。在接收到最后一个字节后,如果nack为True,那么将发送一个NACK信号,否则将发送一个ACK信号。 示例:
buf=bytearray(3) I2C.readinto(buf)
标准总线操作¶
下面介绍的函数是标准的I²C主模式读写操作。
I2C.readfrom(addr, nbytes)¶
函数说明:从指定地址设备读取数据,返回读取对象,这个对象与I²C设备有关。
addr
:I²C设备地址(可由scan函数读取出来)
nbytes
:要读取数据的大小
示例:
>>> print(I2C.scan()) [24] >>> data = I2C.readfrom(24, 8) >>> print(data) b'\x00\x02\x00\x00\xe8\x03\xe8\x03'
I2C.readfrom_into(addr, buf)¶
函数说明:从指定地址设备读取buf.len()个数据到buf。
addr
:I²C设备地址(可由scan函数读取出来)
buf
:存储数据的缓冲区
示例:
>>> buf = bytearray(8) >>> I2C.readfrom_into(24, buf) >>> print(buf) bytearray(b'\x00\x02\x00\x00\xe8\x03\xe8\x03')
I2C.writeto(addr, buf)¶
函数说明:将buf中的数据写入设备,并返回写入数据的大小。
addr
:I²C设备地址(可由scan函数读取出来)
buf
:存储数据的缓冲区
示例:
>>> b = bytearray(3) >>> b[0] = 24 >>> b[1] = 111 >>> b[2] = 107 >>> i = I2C.writeto(24,b) 3
内存操作¶
某些 I²C 设备作为存储设备 (或一组寄存器) ,可以读取或者写入。这种情况下,有两个地址和 I²C 事务相关: 从设备地址和内存地址。下面方法用于和这些设备进行通信。
I2C.readfrom_mem(addr, memaddr, nbytes, addrsize=8)¶
函数说明:从I²C设备的寄存器中读取并返回数据。
addr
:I²C设备地址(可由scan函数读取出来)
memaddr
:寄存器地址
nbytes
:要读取的字节大小
addrsize
:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位)
示例:
b = I2C. readfrom_mem(24, 0x58, 3) print(b)
运行结果:
b'\x00\x02\x01'
I2C.readfrom_mem_into(addr, memaddr, buf, addrsize=8)¶
函数说明:从I²C设备的寄存器中读取buf.len()个数据到buf,无返回值。
addr
:I²C设备地址(可由scan函数读取出来)
memaddr
:寄存器地址
buf
:存储数据的缓冲区
addrsize
:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位),读取数据数量是buf的长度。
示例:
buf=bytearray(8) I2C.readfrom_mem_into(24, 0x58, buf)
I2C.writeto_mem(addr, memaddr, buf, addrsize=8)¶
函数说明: 将buf 中的数据全部写入到从设备 addr 的内存 memaddr。
addr
:I²C设备地址(可由scan函数读取出来)
memaddr
:寄存器地址
buf
:存储数据的缓冲区
addrsize
:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位),读取数据数量是buf的长度。
示例:
buf = b'123' I2C.writeto_mem(24, 0x58, buf)