转自LarryBank-技术博文-使用几个 GPIO 引脚控制大量 OLED 显示器
https://bitbanksoftware.blogspot.com/2019/01/controlling-lots-of-oled-displays-with.html
原文翻译:
在我尽可能多地了解“物联网”和市场上流行的所有配件/传感器/显示器的使命中,我遇到了一些使用多个 OLED 显示器来形成仪表板和控制面板的项目。 我没有考虑过这种用法,但考虑到它们的便宜(和小)是有道理的。 这个想法带来的挑战是,廉价的业余爱好部件要么具有固定的 I2C 地址(通常为 0x3C),要么具有用于选择备用地址(通常为 0x3D)的单个跳线。

上图显示了一组现成且价格低廉的 I2C OLED 显示器。 前 3 个有一个地址选择跳线。 底部 2 没有显示任何用于更改地址的特定跳线,尽管一个或多个 SMD 电阻器可能会控制它。 无论哪种方式,大多数爱好者都不准备使用微小的表面贴装电阻器。
如果它们都设置为相同的 I2C 地址,那么为了让超过 1 个显示器从同一个 MCU 工作,您将需要多个 I2C 总线。 我已经在 GPIO 线路上编写了位爆炸 I2C 的代码,因此编写可以使用多条 GPIO 线路管理多条 I2C 总线的代码似乎是一项相对容易的任务。 了解 I2C 协议的工作原理后,我的脑海中形成了另一个想法:“来自同一源的多条 I2C 总线可以共享一条时钟线吗?” 我认为这可能的原因是因为 I2C 启动信号需要时钟和数据线都变低。 如果公共时钟线变低,是否会激活您不打算激活的设备?
我的第一个实验是尝试仅使用 5 条 GPIO 线(4 SDA + 1 SCL)来控制 4 个 OLED 显示器(都具有相同的 I2C 地址)。 我选择 Arduino Pro Micro(又名 Leonardo)作为本次测试的理想电路板。

正如您在上图中所看到的,它有效。 4 个显示器都共享同一条时钟线,并且都可以单独寻址,没有干扰问题。 然后我意识到我已经通过将一个显示器连接到每条 I2C 总线来设计我的 Multi_OLED 库。 我对代码进行了第二次检查并添加了一个新参数,以便为每个显示器指定一个地址和总线编号,以便可以控制每条总线的多个显示器。

在这张照片中,顶部的 2 个显示器设置为不同的地址并共享同一条总线,而下面的 3 个显示器都在自己的 I2C 总线上。 像素操作需要一个独特的后备缓冲区(1K RAM)。 这在大多数 AVR MCU 上是不可行的,因为它们的板载 RAM 很少,但其他 MCU(例如 ESP32、Cortex-M)有足够的 RAM 来分配多个 1K 缓冲区。 对于这些 MCU,线和像素功能可用。 这是在 Adafruit nRF52840 Feather 上运行的 2 个显示器的视频:

我在这里将代码发布在 Github 上:
Github 上的 Multi_OLED 库
我认为将多个 I2C 总线用于 OLED 显示器以外的东西可能会有用,所以我单独分解了 Multi_BitBang 库:
Github 上的 Multi_BitBang 库
我创建的用于显示的函数非常简单。 首先初始化您的显示器:
void Multi_OLEDInit(uint8_t *iBus, uint8_t *iAddr, uint8_t *iType, uint8_t *bFlip, uint8_t *bInvert, uint8_t iCount);
每个参数都是一个列表(一个用于您要控制的每个显示器)
iBus - I2C 总线编号(0 到您使用 Multi_BitBang 库定义的最大总线)
iAddr - I2C 地址(例如 0x3C)
iType - OLED显示类型(枚举值如oled_128x32)
bFlip - 指示显示是否应翻转 180 度的布尔值
bInvert - 一个布尔值,指示显示器是否应启用反色模式
iCount - 显示器总数
我在此处提供了一个演示如何驱动多个 OLED 显示器的示例草图:
Multi_OLED 示例草图

关于我添加到 Multi BitBang 库以加快速度的代码的最后一个注释。 Arduino 核心库包括一种操作 GPIO 引脚的标准方法。 这些函数(pinMode、digitalRead、digitalWrite)隐藏了许多实现细节,使它们能够在 Arduino 工具集支持的所有 MCU 上以相同的方式工作。 在 AVR MCU(例如 Arduino Uno / aka ATmega328P)上,这些功能非常慢。 它们很慢,因为它们会进行表查找、边界检查并尝试将中断放在合适的位置。 对于整个社区来说,这通常无关紧要。 闪烁 LED 或读取按钮状态不需要最高速度,但生成用于通信的数字信号却需要。 使用 Arduino GPIO 函数,Multi_OLED 库能够非常缓慢地更新显示器。 有一种更快的方法,但它需要直接操作端口寄存器。 我想出了一个方案,既能保持引脚功能的简单性,又能提高直接端口访问的速度。 每个 AVR 芯片都有一个独特的端口-> 引脚映射,可将引脚号转换为端口和位。 下图显示了 ATmega32u4 (Pro Micro) 的引出线。 查看数字引脚 2,您可以看到它也标有 PD1。 这意味着引脚 2 实际上是端口 D 的位 1。使用我的编号方案,您可以通过将其引用为引脚 0xD1 来访问该引脚。
(图片来源 - Sparkfun Electronics)
我的代码不需要使用表查找,不需要进行太多边界检查,也不需要禁用中断。 它确实允许您使用现有代码并仅更改传递的密码。 通过简化的引脚编号方案,Multi_OLED 库能够像 AVR MCU 上的普通 I2C 一样快速地驱动显示器。 更快的 MCU(例如 ESP32)不会因为使用原始引脚功能的额外开销而减慢速度。 为了区分这两种情况,小于 0xA0 的引脚编号将调用原始(慢速)引脚功能,否则快速功能将与端口/位编号方案一起使用。 我还将这段代码分离到一个名为“FastIO”的独立库中。 这里是:
Github 上的 FastIO 库
https://github.com/bitbank2/FastIO