4. I2C通讯

I2C(IIC)协议是在电子设备中常用的通讯协议,通过它,我们可以对各种各样的电子器件进行控制(注:一般受控的设备为从机), 如常见的 姿态传感器MPU6050温度图像传感器MLX906400.96寸OLED显示屏SSD1306 等等, 都有通过I2C通讯协议来和板卡(注:一般主动发起控制的为主机)进行通讯的。

关于I2C协议的具体内容,我们在此不作过多探究,大家可以自行搜索I2C通讯协议的相关知识学习。

本章中,我们的重点是使用前面介绍的 python-peripheryAdafruit Blinka 两个Python库, 来使用到我们鲁班猫板卡上的I2C主机。

4.1. I2C实验

一般情况下,我们需要一个具体的通讯对象来完成本节实验,即前面提到的各种类型的电子器件。

考虑到大家手中的模块或I2C设备各不相同,所以对上述Python库的使用介绍分为两部分, 一部分为库官方示例Demo介绍,另一部分则为使用库中对应的API,在实际的模块中使用介绍。

笔者使用的是 0.96寸OLED显示屏模块SSD1306 野火【OLED屏_I2C_0.96寸】 , 对上述提到的库进行使用示例的编写。 实际的使用中,鲁班猫板卡上的I2C主机能通讯的器件类型并不受限制。 因此笔者会在代码中对相关的代码进行详细注释,如大家手中没有相同的模块, 也可进行类似的操作,对不同的模块进行实验代码编写。

4.2. 实验准备

4.2.1. 板卡添加I2C资源

在板卡上的GPIO可能没有复用为I2C,可通过设置设备树插件来加载, LubanCat_RK系列板卡的引脚参考下: 《LubanCat-RK系列-40pin引脚对照图》

下面以LubanCat 2为例开启下i2c的设备树插件(LubanCat 2引出的引脚有两个i2c):

# 不同板卡的配置文件和i2c不同,以LubanCat 2为例,配置文件为uEnvLubanCat 2.txt,
# 可以开启I2c3、i2c5
# 登录系统终端,打开/boot/uEnv/uEnvLubanCat 2.txt文件

sudo vim /boot/uEnv/uEnvLubanCat 2.txt
#进入编辑模式,取消I2c3前面的注释,保存并退出文件,重启系统
broken

重启后,查看板卡的I2C资源是否加载:

# 在终端中输入如下命令,可以查看到I2C资源:
ls -l /dev/i2c-*

板卡上的I2C资源示例,仅供参考:

broken

一些简单i2c相关的测试参考下 《I2C通讯》

4.2.2. 硬件连接

鲁班猫LubanCat 2板卡,板上无I2C器件可进行相关实验现象展示, 故本节实验现象,就通过在硬件层面连接器件外围器件 0.96寸OLED显示屏SSD1306 为大家展示,使用的是I2c3。

连接如图,仅供参考(LubanCat 2的I2c3), oled屏和板卡40pin引脚连接表如下:

OLED屏幕引脚

对应GPIO

LubanCat 2板卡引脚

SDA

GPIO1_A0

I2C3_SDA

SCL

GPIO1_A1

I2C3_SCL

GND

GND

VCC

3.3V

4.3. 使用python-periphery

python-periphery 库支持的I2C功能是基于Linux的I2C系统实现的,所以要想利用该库使用到I2C的功能, 需要板卡提供支持。像鲁班猫板卡,就可以完美使用 python-periphery 库I2C通讯功能。 这样这样一来,就不需要我们在软件层面上利用GPIO模拟I2C主机功能了。

4.3.1. 安装 python-periphery

重要

如在前面小节中安装了此库,务必跳过此操作。

python-periphery 的安装方式如下:

# 在板卡使用如下命令安装
sudo pip3 install python-periphery

4.3.2. periphery使用I2C功能

先参考学习下 python-periphery 库进行I2C功能使用的示例代码:

官方提供的code example -i2c(学习参考)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from periphery import I2C

# 打开i2c1主机
i2c = I2C("/dev/i2c-1")

# 以EEPROM器件为例,从EEPROM内存地址0x100处读取数据,EEPROM器件的I2C设备地址为0x50
msgs = [I2C.Message([0x01, 0x00]), I2C.Message([0x00], read=True)]
# 开启I2C传输
i2c.transfer(0x50, msgs)
# 打印接受到的消息
print("0x100: 0x{:02x}".format(msgs[1].data[0]))
# 操作完成,关闭I2C主机以释放资源。
i2c.close()

代码说明:

  • 第4行,申请I2C资源,占用I2C1主机

  • 第7行,构造消息结构I2C.Message。I2C.Message中第一成员为发送的数据列表, 第二个指定此消息的作用,默认为I2C的写命令,在写命令的情况下,会将数据列表中的数据发送出去, 如果指定 read=True ,则此条消息对应为I2C的读命令,会将读取到的数据存放在data成员中。

  • 第9行,开启I2C传输,发送I2C从机设备的地址与构造好的消息结构

  • 第17行,释放I2C资源,释放I2C1主机

通过 python-periphery 库中给出的对EEPROM器件操作的代码示例, 我们可以掌握该库对I2C主机使用的基本方法,那么接下来就可以针对具体的器件展开编程使用了。

这里笔者使用前面所述的 0.96寸OLED显示屏SSD1306 模块编程展示:

配套代码 io/i2c/i2c_test_periphery.py文件内容
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
""" periphery i2c 测试 使用0.96寸OLED模块 """
import time
from periphery import I2C

# 打开 i2c-3 控制器
i2c = I2C("/dev/i2c-3")

# 设备从机地址0x3c,即OLED模块地址
I2CSLAVEADDR = 0x3C

# 使用periphery i2c库读取功能测试
def i2c_read_reg(devregaddr):
    """
    使用periphery i2c库读取功能测试
    """
    # 构造数据结构
    # 第一个Message为要读取的设备地址
    # 第二个Message用于存放读取回来的消息,注意其中添加的read=True
    msgs = [I2C.Message([devregaddr]), I2C.Message([0x00], read=True)]
    # 发送消息,发送到i2cSlaveAddr
    i2c.transfer(I2CSLAVEADDR, msgs)
    print("从寄存器 0x{:02x} 读取出: 0x{:02x}".format(devregaddr, msgs[1].data[0]))


def oled_write_cmd(cmd):
    """
    使用periphery i2c库发送功能测试
    """
    msgs = [I2C.Message([0x00, cmd])]
    i2c.transfer(I2CSLAVEADDR, msgs)


def oled_write_data(data):
    """
    使用periphery i2c库发送功能测试
    """
    msgs = [I2C.Message([0x40, data])]
    i2c.transfer(I2CSLAVEADDR, msgs)


def oled_init():
    """
    使用OLED模块进行代码功能测试 0.96寸OLED模块初始化
    """
    time.sleep(1)
    oled_write_cmd(0xAE)
    oled_write_cmd(0x20)
    oled_write_cmd(0x10)
    oled_write_cmd(0xB0)
    oled_write_cmd(0xC8)
    oled_write_cmd(0x00)
    oled_write_cmd(0x10)
    oled_write_cmd(0x40)
    oled_write_cmd(0x81)
    oled_write_cmd(0xFF)
    oled_write_cmd(0xA1)
    oled_write_cmd(0xA6)
    oled_write_cmd(0xA8)
    oled_write_cmd(0x3F)
    oled_write_cmd(0xA4)
    oled_write_cmd(0xD3)
    oled_write_cmd(0x00)
    oled_write_cmd(0xD5)
    oled_write_cmd(0xF0)
    oled_write_cmd(0xD9)
    oled_write_cmd(0x22)
    oled_write_cmd(0xDA)
    oled_write_cmd(0x12)
    oled_write_cmd(0xDB)
    oled_write_cmd(0x20)
    oled_write_cmd(0x8D)
    oled_write_cmd(0x14)
    oled_write_cmd(0xAF)


def oled_fill(filldata):
    """
    清空OLED屏幕
    """
    for i in range(8):
        oled_write_cmd(0xB0 + i)
        # page0-page1
        oled_write_cmd(0x00)
        # low column start address
        oled_write_cmd(0x10)
        # high column start address
        for j in range(128):
            oled_write_data(filldata)


# 代码测试
try:
    # 初始化OLED屏幕,SSD1306必要
    oled_init()
    # 清空OLED屏幕
    oled_fill(0xFF)
    # 读取寄存器测试
    i2c_read_reg(0x10)
finally:
    print("测试正常结束")
    # 释放资源
    i2c.close()

关于通过I2C主机发送何种命令才能去控制 0.96寸OLED显示屏SSD1306 模块不是本教程的重点, 教程目的是通过提供的示例代码,让大家对相关库的API功能使用有个参考对象。

4.3.2.1. 实验操作

示例代码使用LubanCat 2板卡,操作如下:

# 在板卡i2c_test.py所在的目录执行如下命令
sudo python3 i2c_test_periphery.py

# 可看到连接的OLED屏幕被清空显示色块,并在终端中打印出了读取的寄存器信息

显示参考(随意拿的一个旧的屏幕~):

broken

4.4. 方式二:使用Adafruit Blinka

Adafruit Blinkapython-periphery 功能类似,I2C通讯也是基于Linux的I2C子系统实现的。 只是在代码上的用法上有较大差异。

4.4.1. 安装Adafruit Blinka

重要

如在前面小节中安装了此库,务必跳过此操作。

Adafruit Blinka 的安装方式如下:

# 在板卡使用如下命令安装
sudo apt -y install python3-libgpiod python-periphery # 注:如已安装,忽略此步
sudo pip3 install Adafruit-Blinka

sudo pip3 install machine

4.4.2. Adafruit-Blinka使用I2C主机

使用这个 Adafruit Blinka 进行输入输出的示例代码如下:

官方提供的code example - i2c
1
2
3
4
5
6
7
8
9
import busio
from board import *

# 申请I2C资源,占用I2C1主机
i2c = busio.I2C(SCL, SDA)
# 打印扫描到的挂载在I2C1主机下的从机设备
print(i2c.scan())
# 操作完成,关闭I2C主机以释放资源
i2c.deinit()

Adafruit Blinka 库提供的I2C使用示例较为简单,仅仅只是提供了扫描挂载在I2C1主机下的设备, 其他的API功能具体的使用方法大家可以参考: Blinka I2C 下给出的介绍。

这里直接展示使用I2C1主机与 0.96寸OLED显示屏模块SSD1306 模块通讯的程序:

配套代码 io/i2c/i2c_test_adafruit.py文件内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
""" blinka i2c 测试 使用0.96寸OLED模块 """
import board
import busio

# 初始化OLED命令字节数组
OledInitBuf = bytes(
    [
        0xAE,
        0x20,
        0x10,
        0xB0,
        0xC8,
        0x00,
        0x10,
        0x40,
        0x81,
        0xFF,
        0xA1,
        0xA6,
        0xA8,
        0x3F,
        0xA4,
        0xD3,
        0x00,
        0xD5,
        0xF0,
        0xD9,
        0x22,
        0xDA,
        0x12,
        0xDB,
        0x20,
        0x8D,
        0x14,
        0xAF,
    ]
)

# 数据发送、接受缓冲区
OutBuffer = bytearray(1)
InBuffer = bytearray(1)

try:
    # 申请i2c资源
    i2c = busio.I2C(board.I2C3_SCL, board.I2C3_SDA)
    # 扫描I2C设备地址,测试
    print("挂载I2C总线上的I2C设备地址有")
    for i in i2c.scan():
        print("0x%02x " % i)

    # IIC发送命令测试(writeto),初始化OLED
    # 初始化OLED,初始命令存放于字节数组oledInitBuf中
    i2c.writeto(0x3C, OledInitBuf)

    # IIC读取命令测试(writeto_then_readfrom),读取OLED的0x10寄存器
    # 发送数据,发送数据完成后不产生停止信号,并重新生产起始信号进行读取。

    # 发送数据内容存放在字节数组outBuffer中,内容为OLED寄存器地址:0x10
    OutBuffer[0] = 0x10
    # 发送,发送寄存器地址0x10,并将寄存器地址0x10下的内容读取到inBuffer中
    i2c.writeto_then_readfrom(0x3C, OutBuffer, InBuffer)
    # 打印数据信息
    print("(writeto_then_readfrom)读取到的内容为:0x%02x" % InBuffer[0])

    # IIC读取测试(readfrom_into),从设备内部的地址读,则不需要指定读取的地址
    i2c.readfrom_into(0x3C, InBuffer)
    # 打印数据信息
    print("(readfrom_into)读取到的内容为:0x%02x" % InBuffer[0])

    # OLED清屏
    for i in range(8):
        i2c.writeto(0x3C, bytearray([0x00, 0xB0 + i]))  # page0-page1
        i2c.writeto(0x3C, bytearray([0x00, 0x00]))  # low column start address
        i2c.writeto(0x3C, bytearray([0x00, 0x10]))  # high column start address
        for j in range(128):
            i2c.writeto(0x3C, bytearray([0x40, 0xFF]))

finally:
    # 释放i2c总线资源
    i2c.deinit()

代码说明:

  • 第45行,申请I2C资源,占用I2C1主机

  • 第53行,将字节数组OledInitBuf里的数据,发送到挂载在I2C1主机下的从设备,从设备地址为0x3C

  • 第59~61行,将字节数组OutBuffer里的数据,发送到挂载在I2C1主机下的从设备, 并在从设备寄存器地址0x10处读取一个数据到字节数组InBuffer,从设备地址为0x3C

  • 第66行,在从设备内部的当前地址读,读取一个数据到字节数组InBuffer

  • 第80行,释放I2C资源,释放I2C1主机

实验操作与上一小节相同,将程序在板卡上运行即可。

代码片段的 busio.I2C(SCL, SDA) 中的SCL、SDA是在Blinka库定义好的LubanCat板卡资源。

更详细的引脚定义可直接查看 Adafruit Blinka的源码

4.4.3. Adafruit_CircuitPython_SSD1306

在前面我们通过Blinka库,实现了通过I2C主机控制 0.96寸OLED显示屏SSD1306 模块, Blinka库是基于 CircuitPython 重新实现的实时组件对象。 简而言之,就是利用Blinka库开发的Python程序,可以实时去使用到板卡上的硬件资源。 如果对 CircuitPython 熟悉的同学,就会知道基于 CircuitPython 库实现的、 对各种各样的电子器件实施控制的代码Demo是非常多的。

那么也就是说,是不是我们的鲁班猫板卡适配好了Blinka库,即可利用 CircuitPython 丰富的 各种传感器的控制Demo呢?以笔者做上述实验时使用的模块为例, 0.96寸OLED显示屏SSD1306 模块的Demo可以正常使用。

那么下面笔者就以该SSD1306模块为例,为大家演示一下,如何将 CircuitPython0.96寸OLED显示屏SSD1306 模块提供支持的程序, 运行在我们的鲁班猫板卡上。

首先找到对应的仓库,这里为大家演示的是 0.96寸OLED显示屏SSD1306, 那么大家可以在 Adafruit的Github上 Adafruit Github 搜索关键字 SSD1306, 可以看到 Adafruit_CircuitPython_SSD1306, 这就是要使用到的仓库了。当然大家如果需要, 也可以搜索其他常见的模块比如dht11、mpu6050等等等等,就需要大家自行探索了。

总之更多Demo仓库 关注 Adafruit Github

在对应仓库下方,我们可以看到对应库的添加方法:

# 在板卡使用如下命令安装对应的传感器库,下列语句二选一:

# 在root用户下输入:
pip3 install adafruit-circuitpython-ssd1306
# 如为普通用户输入:
sudo pip3 install adafruit-circuitpython-ssd1306

或者大家可以通过其他途径将下载好的仓库上传到板卡中,并进入仓库目录执行以下语句安装:

sudo python3 setup.py install

由于板卡性能和网络原因,安装过程会十分漫长,请耐心等待。如果安装过程中发送错误, 可以更换上述方法中的另外一种再次尝试。

安装完成后,即可使用仓库中提供的各种示例Demo了!需要注意的是部分Demo中从设备的地址 并非能完全适配你手中的模块,所以当出现报错信息时,可能需要修改代码Demo中的从设备的地址。

CircuitPython0.96寸OLED显示屏SSD1306 模块提供的代码Demo如下图示例:

broken

最后在终端中运行任意Demo,查看一下效果:

sudo python3 ssd1306_stats.py

即可实时显示鲁班猫板卡运行状态等信息:

broken