4. I2C通讯¶
I2C(IIC)协议是在电子设备中常用的通讯协议,通过它,我们可以对各种各样的电子器件进行控制(注:一般受控的设备为从机),
如常见的 姿态传感器MPU6050
、温度图像传感器MLX90640
、0.96寸OLED显示屏SSD1306
等等,
都有通过I2C通讯协议来和板卡(注:一般主动发起控制的为主机)进行通讯的。
关于I2C协议的具体内容,我们在此不作过多探究,大家可以自行搜索I2C通讯协议的相关知识学习。
本章中,我们的重点是使用前面介绍的 python-periphery 及 Adafruit Blinka 两个Python库, 来使用到我们鲁班猫板卡上的I2C主机。
4.1. I2C实验¶
一般情况下,我们需要一个具体的通讯对象来完成本节实验,即前面提到的各种类型的电子器件。
考虑到大家手中的模块或I2C设备各不相同,所以对上述Python库的使用介绍分为两部分, 一部分为库官方示例Demo介绍,另一部分则为使用库中对应的API,在实际的模块中使用介绍。
笔者使用的是 0.96寸OLED显示屏模块SSD1306
,对上述提到的库进行使用示例的编写。
实际的使用中,鲁班猫板卡上的I2C主机能通讯的器件类型并不受限制。
因此笔者会在代码中对相关的代码进行详细注释,如大家手中没有相同的模块,
也可进行类似的操作,对不同的模块进行实验代码编写。
4.2. 实验准备¶
在开始实验前,需要做一些准备。
4.2.1. 添加I2C资源¶
在板卡上的部分资源可能默认未被开启,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可添加对应设备树插件的加载,重启系统,以在系统中添加对应资源。
如本节实验中,通常情况下在鲁班猫系统中默认使能 I2C
的功能,
如大家发现I2C的设备树插件未加载,请做相应修改。
同时如果大家在使用中发现I2C功能引脚被复用或有冲突,请要将对应占用I2C引脚资源的设备树插件取消加载, 否则I2C资源可能无法使用。若已开启对应资源,忽略以下步骤。
方法参考如下:
添加I2C设备树插件,以使系统支持I2C功能,如下:
# 以鲁班猫i.MX6ULL板卡I2C设备树插件内容为例:
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-i2c1.dtbo
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-i2c2.dtbo
如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。
如出现 Permission denied
或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
# 在终端中输入如下命令,可以查看到I2C资源:
ls /sys/bus/i2c/devices
板卡上的I2C资源示例,仅供参考:
4.2.2. 硬件连接¶
注:硬件连接小节,需要大家根据自身开发环境等情况对本章内容进行参考调整。 如您使用的鲁班猫板卡上已经板载了I2C设备,无需再外接设备也可进行实验。
由于笔者使用的是鲁班猫i.MX6ULL MINI板卡,板上无I2C器件可进行相关实验现象展示,
故本节实验现象,就通过在硬件层面连接器件外围器件 0.96寸OLED显示屏SSD1306
为大家展示。
本章实验中使用的I2C主机为鲁班猫i.MX6ULL MINI板卡上的I2C1主机, 对应的引脚可在 《鲁班猫板卡Pinout》 找到。 大家可根据自身鲁班猫板卡实际设备情况,调整使用的主机和器件连接等等。
笔者连接如图,仅供参考:
4.3. 方式一:使用python-periphery¶
python-periphery 库支持的I2C功能是基于Linux的I2C系统实现的,所以要想利用该库使用到I2C的功能, 需要板卡提供支持。像鲁班猫板卡,就可以完美使用 python-periphery 库I2C通讯功能。 这样这样一来,就不需要我们在软件层面上利用GPIO模拟I2C主机功能了。
关于I2C子系统相关内容,参考 《Linux I2C通讯》 。
4.3.1. 安装 python-periphery¶
重要
如在前面小节中安装了此库,务必跳过此操作。
python-periphery 的安装方式如下:
# 在板卡使用如下命令安装
sudo pip3 install python-periphery
4.3.2. periphery使用I2C功能¶
使用 python-periphery 库进行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
模块编程展示:
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-1 控制器
i2c = I2C("/dev/i2c-1")
# 设备从机地址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设备的从机地址,关于设备从机地址如何去获取, 可以参考 《Linux I2C通讯》 章节。
关于通过I2C主机发送何种命令才能去控制 0.96寸OLED显示屏SSD1306
模块不是本教程的重点,
教程目的是通过提供的示例代码,让大家对相关库的API功能使用有个参考对象。
4.3.2.1. 实验操作¶
示例代码使用LubanCat i.MX6ULL MINI板卡,操作如下:
# 确认已安装python-periphery包
# 确认使能了I2C设备树插件
# 在板卡i2c_test.py所在的目录执行如下命令
python3 i2c_test.py
# 可看到连接的OLED屏幕被清空显示纯色块,并在终端中打印出了读取的寄存器信息
4.4. 方式二:使用Adafruit Blinka¶
Adafruit Blinka 与 python-periphery 功能类似,I2C通讯也是基于Linux的I2C子系统实现的。 只是在代码上的用法上有较大差异。
4.4.1. 安装Adafruit Blinka¶
重要
如在前面小节中安装了此库,务必跳过此操作。
Adafruit Blinka 的安装方式如下:
# 在板卡使用如下命令安装
sudo apt -y install python3-libgpiod python-periphery # 注:如已安装,忽略此步
sudo pip3 install Adafruit-Blinka
4.4.2. Adafruit-Blinka使用I2C主机¶
使用这个 Adafruit Blinka 进行输入输出的示例代码如下:
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
模块通讯的程序:
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模块 """
from board import *
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(SCL, 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板卡资源。
当我们申请对应的板卡资源的时候,它会检测板卡型号,然后根据板卡信息加载相应的资源定义, 所以我们可以直接使用定义好的资源名, 使用起来相对 python-periphery 会更直观。
更详细的引脚定义可直接查看 Adafruit Blinka的源码 :
这些文件里定义的引脚与板卡的关系可查看:敬请期待
4.5. 拓展内容¶
在前面我们通过Blinka库,实现了通过I2C1主机控制 0.96寸OLED显示屏SSD1306
模块,
Blinka库是基于 CircuitPython
重新实现的实时组件对象。
简而言之,就是利用Blinka库开发的Python程序,可以实时去使用到板卡上的硬件资源。
如果对 CircuitPython
熟悉的同学,就会知道基于 CircuitPython
库实现的、
对各种各样的电子器件实施控制的代码Demo是非常多的。
那么也就是说,是不是我们的鲁班猫板卡适配好了Blinka库,即可利用 CircuitPython
丰富的
各种传感器的控制Demo呢?以笔者做上述实验时使用的模块为例,0.96寸OLED显示屏SSD1306
模块的Demo可以正常使用。
那么下面笔者就以该SSD1306模块为例,为大家演示一下,如何将 CircuitPython
为 0.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中的从设备的地址。
CircuitPython
为 0.96寸OLED显示屏SSD1306
模块提供的代码Demo如下图示例:
最后在终端中运行任意Demo,查看一下效果:
python3 ssd1306_stats.py
即可实时显示鲁班猫板卡运行状态等信息: