转LarryBank-在资源受限的 MCU 上使用电子纸/技术向博文/嵌入式/物联网
https://bitbanksoftware.blogspot.com/2022/10/using-e-paper-displays-on-resource.html
基于互惠互利的平等互信合作原则以及我本人是war chest真金白银资助Larry Bank的前提下,我很荣幸地帮他进行转内销。
背景
除了代码和电源效率方面的挑战,我偶尔会发现其他吸引我的挑战。 不久前,我开始与 Aaron Christophel 合作进行他的各种电子货架标签和电子纸项目。 我的想法之一是让控制价格标签的 MCU 板有更多的自主权,这样它们就可以做更多的事情,而不仅仅是从服务器无线接收图像。 最初的项目使用带有 ARM MCU 和大量 RAM 的价格标签。 我能够在其上运行我的 TIFF G4 解码器,并在将其发送到电子纸显示器之前将整个结果图像保存在 RAM 中:



Aaron 最近(和更大)的设备系列中的 MCU 包含一个 8051 型 CPU,具有有限的 FLASH 空间和更有限的 RAM。 我认为看看我可以在那些 8 位 CPU 上运行多少独立功能(文本/GFX)将是一个很好的挑战。
问题
让我们从一个示例电子纸显示器开始——一个 2.9 英寸的黑白显示器,128x296 像素。
如果每个像素都是 1 位,那么我们需要 (128x296)/8 = 4736 字节来保存 RAM 中的整个像素阵列。 使用这些显示器的传统方法是在 RAM 中准备图像,然后将图像发送到显示器并告诉显示控制器进行刷新。 电子纸面板的控制器芯片内部通常有两个独立的 1 位内存平面来保存所有像素。 它们的三种典型使用方式:
- 当前和以前的缓冲区,用于快速更新已更改的像素
- 每像素 2 位缓冲区以显示 4 级灰度
- 每像素 2 位缓冲区,用于保存红色/黑色/白色像素
与 LCD 和 OLED 控制器类似,电子纸控制器允许您定义内存窗口,以便可以将数据写入显示器的子区域而不会干扰其他像素。 如果您没有足够的 RAM 来将整个帧保存在内存中,则 GxEPD 等库使用它来允许更新较小部分的显示。 尽管如此,这还是假定您首先在 CPU RAM 中准备图形,然后将像素发送到电子纸的内存中。
低内存方法
我认为尝试不同的方法是值得的。 我的 OneBitDisplay 已经可以选择支持使用 OLED 和 LCD 显示器,方法是将数据直接写入显示器的内部存储器,而不是先在 CPU RAM 中准备数据。 这允许您显示有用的文本和 GFX(有一些限制)而不需要任何显着的本地 RAM。 在下图中,ATtiny85(512 字节 RAM)能够在 SSD1306(128x64 = 1K RAM 内部缓冲区)上绘制文本。

由于电子纸控制器非常相似,因此它们应该允许采用类似的方法。 首先,让我们来看看非常流行的 SSD1306 128x64 OLED 显示器的内存布局。

在上图中,“页面”指的是一行 128 字节,其中每列 8 个像素由一个字节中的 8 位控制,第一行中的最低有效位 (LSB)。 写入第一个字节位置的字节值 7(00000111 二进制)将绘制像素 (0,0)、(0,1) 和 (0,2),而 PAGE 中的其他 5 个像素不亮。 通过写入与此内部显示 RAM 相同方向的字符图像,可以非常轻松地将 8 像素高的倍数的文本行写入显示器,而无需先将其缓冲到本地 RAM 中。 这正是我在没有本地缓冲区的情况下在 OneBitDisplay 中所做的。
现在让我们来看看电子纸显示器的内存布局。

术语略有不同 - source(列)和 gate(行),但如果您将头向右倾斜 90 度,则与 OLED 内存布局有相似之处🙃。 字节水平排列,MSB(最高有效位)在左边。 一些显示器可以在另一个方向翻转位顺序,但这是大多数显示器的默认设置。 在我们的 2.9" 电子纸示例中,显示内存为纵向,128 像素(16 字节)宽,296 行高。
一个实际例子
显示控制器的内存窗口功能允许通过定义 1 字节宽和 N 字节高的区域来模仿 OLED 的“PAGE”排列。 我用它来允许在垂直列中写入字符图形,并且能够通过将电子纸本机方向逆时针旋转 90 度来重新使用相同的“LSB on top”方向。 绘制下面字符的代码与在 OLED 显示器上绘制的代码没有变化。

两个高度压缩的 TIFF G4 图像存储在 Arduino Uno 的闪存中。 它们各自以 10:1 的比例压缩。 我最新发布的 TIFF_G4 和 OneBitDisplay 库支持直接解压到电子纸帧缓冲区。 在上面的视频中,电子纸内存的像素(0,0)是显示屏的右上角。 图像以垂直线从右边缘向左解码。 下面是 TIFFDraw 回调函数,展示了它是如何完成的。 当图像被解码时,为每一行像素调用此函数。

当接收到第一行图像时,调用 setPosition() 方法将内存窗口设置为与解码图像相同的大小。 它没有在演示代码中以显示为中心,但可以。 水平定位只能在字节边界上,以避免必须移动和重新组合每行的所有字节。 TIFF 解码器生成的像素恰好与电子纸存储器(左侧的 MSB)的方向相同; 这是 1-bpp 设备和文件最常见的字节方向。 由于电子纸颜色为黑色=0,白色=1,TIFF G4 颜色为黑色=1,白色=0,因此可以在反转后将它们写入显示器。 一旦图像完成解码,就会向电子纸发送一个完整的更新命令。 对于 Arduino Uno 和其他 AVR 目标,我将 TIFF 解码器缓冲区大小减少到总共 1K RAM。 在这种显示情况下,最终输出行缓冲区只需 16 字节(128 像素)。
包起来
我希望所有这些努力和代码能够为具有非常小 RAM 的微控制器提供新的用例,使其能够独立地与电子纸显示器一起工作,而不仅仅是接收其他计算机上生成的图形。 我的 CCITT G4 图像解码的 Arduino Uno 示例旨在打破关于 8 位 CPU 上的一点点 RAM 可能实现的假设。 我很想听听这些方面的新想法和用例。
https://github.com/bitbank2/TIFF_G4
