Jetbot实战系列06:I2C总线与PiOLED

在“Jetbot实战系列05-Jetson的40针引脚”一文,已经将SFIO与GPIO之间的关系分析清楚,接下来就是带着大家更深入了解I2C引脚与开发库的使用,以及调整PiOLED显示内容的方法。

在“Jetbot实战系列05-Jetson40针引脚”一文,已经将SFIOGPIO之间的关系分析清楚,接下来就是带着大家更深入了解I2C引脚与开发库的使用,以及调整PiOLED显示内容的方法,这两个部分都是Jetbot智能小车项目非常关键的一环,但我们更希望读者能将执行技术使用在更广泛的场景中。

  • I2C总线的特性

前面提到过,在Jetson40根引脚中有18SFIO类的引脚与电路板直连的,这是不能改变功能的引脚,其中就有两组I2C[3, 5][27,28]引脚,是不需要透过Jetson-IO指定就能使用的。

全名为“集成电路总线(Inter-Integrated Circuit)”的I2C是由飞利浦公司在1980年代初所设计的,是一种“多主从(master/slave)架构的串行通信总线”,由于结构设计得非常简单并且有很大的弹性空间,使得这个总线的普及度相当高,广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。

这里并不讲解I2C的工作原理与电子电路图,这些太过底层的知识对于应用工程师来说意义并不大,就算不懂也不会影响代码的操作。不过I2C总线的特性,倒是需要了解一下,这对于应用开发是有帮助的。以下简单列出I2C总线的一些特性:

  1. 只需要SDA数据线和SCL时钟线就能完成所有工,总线界面已经集成在芯片内部,不需要特殊的界面电路;
  2. 是一个真正的多主机总线:
    1. 如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。
    2. 数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。
    3. 最大主(master)设备数量:无限制;最大从(slave)机数量:127
  3. 可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也利于标准化和模块化,缩短开发时间。
  4. 连接到相同总线上的IC数量只受总线最大电容的限制,串行的8位双向数据传输位速率在标准模式下可达每秒100Kbit、快速模式下可达每秒400Kbit、高速模式下可达每秒3.4Mbit、超高速模式下可达每秒5Mbit性能。
  5. 总线具有极低的电流消耗、抗高噪声干扰,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15米,兼容不同电压等级的器件与工作温度范围宽。

看完以上这些特性之后,就能很清楚地了解为何I2C总线协议会受到广泛的青睐,不仅扩充性强而且稳定性高,并且成本还比较低,接下去就来看看如何使用代码来透过这个总线去控制周边设备。

  • I2C总线的检测工具- i2c-tools

首先要捋清楚I2C的归类是SFIO而不是GPIO类型,不能使用前一篇文章最后所提到的Jetson.GPIO库进行开发。

I2C有自己专属的i2c-tools检测工具,可以用指令检测设备上I2C的状态,并使用SMBUS总线开发库来进行开发,Jetbot系统的机电操控指令也是基于这个库进行高阶封装的。虽然在Jetbot系统里已经将这些工具与库都安装调试好,不过这里还是提供这个工具与开发库的安装与调试步骤,这样就能在其他Jetson系列设备上调用。

安装i2c-toolssmbus库的步骤很简单,请执行以下指令

$

sudo apt update && sudo apt install -y i2c-tools

不过这些牵涉到底层的调用,因此需要配置使用的权限,如下步骤:

$

sudo usermod -aG i2c $USER

现在就可以执行以下指令,检查看看设备里有几个i2c总线:

$

i2cdetect -l

从下图可以看到在Jetson Nano 2GB里有7I2C总线。

Jetbot实战系列06:I2C总线与PiOLED

如果您手边还有Jetson NanoAGX XavierXavier NX开发套件,可以执行这个指令看看不同设备的I2C总线数量,可以发现Jetson NanoAGX Xavier各有9组,而Xavier NX11组。

不过不管芯片提供有多少组总线,目前在40针引脚上只用到两组,那到底用到那两组?请使用前一篇文章里面提到的 “sudo /opt/nvidia/jetson-io/jetson-io.py” 这个工具,进去就能看到现在使用I2C总线编号,其他可能用在与一些内部设备相连接的总线,在这里就不做探索。

接下来就可以检查个别总线上所连接的设备,例如将Jetbot用到的PiOLED显示屏,按照要求连接在Jetson Nano 2GB的第3引脚与第5引脚上,对照40针引脚说明,可以知道这两根引脚属于I2C_2这一组总线,于是要检查这组所接设备时,需要执行以下指令:

$

i2cdetect -y -r 1

注意,这个指令最後面所选的编号是从”0”开始,因此对应到I2C总线编号时,需要经过“1”的处理。执行之后会看到如下图的结果,有个16进制编号为“0x3c”的设备正连接在I2C_2总线上面。

Jetbot实战系列06:I2C总线与PiOLED

网上大部分的说明就是告诉我们“找到设备”了,但我们如何识别这个设备是什么?却没有人说个明白。经过几番搜索之后,才在 https://i2cdevices.org/addresses 里面找到这些编号的对应设备,例如0x3c的设备总共有6种(如下图),每一种都有可能。

Jetbot实战系列06:I2C总线与PiOLED

经过一一查看之后,发现这6个全部都属于“显示类”的设备,而SSD1306的规格符合我们连接的PiOLED显示器规格,如下图:

Jetbot实战系列06:I2C总线与PiOLED

这一番摸索之后,大概就能明确如何透过指令来确认所连接的设备,大家只要依循这样的逻辑与资源,就能非常容易地捋顺这个I2C总线的使用。现在已经能使用i2c-tools工具来检测I2C总线所连接的设备,接着就来看看在代码层面该如何对I2C设备执行控制!

  • PiOLED显示板

这是创客树莓派极为常用的显示设备,在网上可以找到以下三种:

  1. Adafruit原版(下图最左边):约150人民币
  2. 国产树莓派PiOLED屏(下图中间):约30人民币
  3. 0.91OLED显示屏(下图右边):约10人民币

Jetbot实战系列06:I2C总线与PiOLED

其实三种的关键元件都一样,就是右边那个最便宜的0.91OLED显示屏,左边两个只是再添加一个很简单很便宜的电路转接板,然后再加以焊接加工而已。如果您打算按照Jetbot提供的自行组装方式,包括3D打印车体、单独采购PCA9685+TB6612控制板,那么“自行焊接加工”的步骤是跳不过去的。

对初学者来说,推荐使用中间的“树莓派PiOLED屏”会比较方便,现在以这个为例子,按照前面的下图接到Jetson Nano 2GB上,就能在前面的 ”i2cdetect -y -r 1”指令下看到 ”0x3c” 设备。

Jetbot实战系列06:I2C总线与PiOLED

如果你在Jetbot系统上,并且正常开机的状况下,应该就能看到显示器上出现如下图的信息,这个信息是由 ~/jetbot/jetbot/apps/stats.py 所控制,现在就来看一下这只代码的内容,下面将一些比较关键的代码列出来说明:

23

28

33

60

 

80

81

82

83

84

85

 

89

90

91

92

 

95

96

97

import Adafruit_SSD1306

from jetbot.utils.utils import get_ip_address

disp = Adafruit_SSD1306.SSD1306_128_32(rst=None, i2c_bus=1, gpio=1)

draw.rectangle((0,0,width,height), outline=0, fill=0)  # 将屏涂黑

# 下面代码负责收集CPU、内存、硬盘等计算资源使用的动态信息:

cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"

CPU = subprocess.check_output(cmd, shell = True )

cmd = "free -m | awk 'NR==2{printf \"MEM: %s/%sMB %.2f%%\", ,,*100/ }'"

MemUsage = subprocess.check_output(cmd, shell = True )

cmd = "df -h | awk '$NF==\"/\"{printf \"DISK: %d/%dGB %s\", ,,}'"

Disk = subprocess.check_output(cmd, shell = True )

# 下面将要显示到OLED屏的信息进行整理,不过最多只能显示4

draw.text((x, top),    "eth0: " + str(get_ip_address('eth0')),  font=font, fill=255)

draw.text((x, top+8), "wlan0: " + str(get_ip_address('wlan0')), font=font, fill=255)

draw.text((x, top+16), str(MemUsage.decode('utf-8')),  font=font, fill=255)

draw.text((x, top+25), str(Disk.decode('utf-8')),  font=font, fill=255)

 

disp.image(image)

disp.display()

time.sleep(1)

以下是正OLED屏的调用细节:

  1. 这个屏的尺寸是128x32像素,与SSD1306规格相同,因此直接使用Adafruit所提供的 Adafruit_SSD1306
  2. 接下来用 Adafruit_SSD1306.SSD1306_128_32创建disp对象,里面的参数
    1. rstRaspberry Pi pin configuration的引脚定义,在这里并不使用(none)
    2. i2c_bus指定所使用的总线编号,前面说过这里使用的是第二组(i2c_2),但在代码里的编号是”1”,如果用的是第一组则编号为”0”
    3. gpio表示这个脚位是否被占用?设置为”1”就表示已占用。
  3. 显示屏是以“图像”方式来处理,第60 ”draw.rectangle” 是先将显示屏涂黑;
  4. 80~85行利用Linux针对CPU、内存、存储空间的检测指令,获取显示的数据,并抽取所需要的内容,分别存入CPUMemUsageDisk三个变量里;
  5. 89~92行就是显示4组数据,因为显示屏的高度为32像素,而每个字符的高度为8像素,因此只能显示4组数据,这里的字符并不支持中文。在代码里面显示的内容是有线网IP、无线网IP、内存使用状况与存储空间的信息。
  6. 95~97行则是将前面四行写入的信息,以图像为单位来现实,每一秒更新一次。

下图是这个OLED所显示的内容,与89~92行的输出是一致的。

Jetbot实战系列06:I2C总线与PiOLED

我们可以尝试修改一下显示的内容,例如有线网IPJetbot来说并不重要,但是CPU使用状态是挺有价值的,因此我们可以稍作修改,包含要显示的顺序,例如无线网IP其实只要知道一次就行,可以放到最下面,而内存与CPU的使用状态是相对敏感的,可以将顺位往上调。下面就是修改后的内容:

89

90

91

92

draw.text((x, top),     str(CPU.decode('utf-8')),  font=font, fill=255)

draw.text((x, top+25), "wlan0: " + str(get_ip_address('wlan0')), font=font, fill=255)

draw.text((x, top+8),  str(MemUsage.decode('utf-8')),  font=font, fill=255)

draw.text((x, top+16), str(Disk.decode('utf-8')),  font=font, fill=255)

如果您使用镜像版或者脚本(create-sdcard-image-from-scratch.sh)安装Jetbot,应该修改完存档之后,就会看到OLED屏的显示发生变化。

如果使用容器安装Jetbot的话,这部分需要重建basedisplay两个容器,需要执行以下步骤让这个改变生效。

$

$

 

$

$

$

cd ~/jetbot/docker && ./disable.sh

source configure.sh

# 只需要重建basedisplay两个容器

cd base && ./build.sh && cd ..

cd display && ./build.sh && cd ..

./enable.sh $HOME/jetbot

重新启动Jetbot容器之后,就会看到显示屏上的内容已经改变(如下图),这样就能轻松改变OLED的显示内容。

Jetbot实战系列06:I2C总线与PiOLED

事实上还有很多方式可以操作I2COLED的显示处理,在Adafruit所提供的开源项目https://github.com/adafruit/Adafruit_Python_SSD1306下面有些范例代码可以尝试,不过操作前先把Jetbot容器关闭,否则I2C会被占用。

现在对Jetson Nano(含2GB)的I2C检测与操作应该有更进一步的了解,这些内容也适用于Jetson系列其他开发套件,主要差别就是得确认所要使用的I2C编号,例如在Xavier AGX上相同位置的I2C是第9组,因此得在代码中修改 ”i2c_bus=8”,其他部分则完全一样。[]

来源:业界供稿

0赞

好文章,需要你的鼓励

2021

12/28

09:12

分享

点赞

邮件订阅
白皮书