大家好,上次我们讲解了OLED显示器的显示编程,但还有一些关于显示的进阶知识,这次我们继续讲解。
显示的方式:静态,动态
上次我们已经了解了显示屏的显示原理,知道了显示屏是由一个个的点组成的矩形阵列。可是目前绝大多数类型的显示屏都不能保持显示状态,拿我们的OLED为例,每个像素点都是一个LED,但LED本身是不能保持其发光状态的。只有在相应的电路接通时才会点亮。那么显示驱动芯片会不会一直保持其电路状态呢?答案是否定的,因为如果要始终保持屏幕上每个像素的电路状态,就需要和屏幕像素数量相同的电路,以我们使用的OLED屏计算,横向128像素为一行,纵向共有64行,如下图:
屏幕上共有128x64=8192个像素,也就是共有8192个LED,如果每个LED都需要一套电路保持它的亮灭状态的话,需要消耗大量的资源。当然这种能够保持自己状态的显示方法称为静态显示。静态显示法显然很耗资源,同时对于OLED屏这种方法还有一个致命缺点,那就是如果始终保持LED的状态的话,会加速常亮LED的老化速度。因此,业界通用的做法是使用动态显示方法,这种显示方法不是时刻保持像素的状态,而是利用人眼的视觉暂留特性,按照一定频率快速的刷新显示每个像素的状态,使用一套电路,按照从做至右(x轴),从上到下(y轴)的(也有先上下后左右的)顺序,逐个按照需要点亮或不点亮像素。只要足够快,亮的像素在我们眼中看来就是始终常量的状态了。
【关于人眼视觉暂留特性,就是我们晚上在昏暗环境下,将一个点燃的香头(或烟头)在眼前移动,如果移动的很慢,我们看到的只是一个移动的光点,但要是快速移动时,我们就可以看到一个光亮的轨迹了。这就是视觉暂留特性。】
对于屏幕上一个亮像素,其实是在不停的闪动,如果闪动速度足够快的话,我们就感觉不出这个像素是闪动的,而是觉得这个像素始终是亮着的。这种动态显示方法只需要一套LED点亮(驱动)电路,节省了资源。其缺点是,虽然我们感觉不出来,但实际上屏幕显示的内容始终都是不停的闪动的,这种闪动会造成眼睛疲劳,损伤视力。这也就是为什么我们不能长时间盯着电脑,电视,手机屏幕看的另一个原因了。
题外说一句,前面说了绝大多数类型的显示屏都无法自己保存显示内容,因此,绝大多数的显示屏都是采用动态的显示方式。那么,有没有静态显示的显示屏呢?当然有,上一次提到过的电纸屏(或称墨水屏)就是个特例。它的特殊结构是每个像素都是一个墨囊,可根据需要设置不同深度的黑色来,并且还能再无电的情况下保持住。这样,就可以使用静态的方式显示内容了,因此这种显示屏非常省电(因为一旦显示好内容后就不需要电了),而且和我们看纸面的字迹的原理是相同的,显示的内容不会闪动。当然,缺点也是有的,成本高价格贵,而且,要想更改屏幕上的内容也很不容易等。所以这种屏非常适合制作电子书或电子标签等显示内容不经常变化的设备。
动态显示是如何工作的:
我们现在知道了显示屏都需要以动态的显示方式来显示内容,那么具体是如何实现的呢?这就需要我们将整个屏幕每一个像素的显示内容先保存到一个专用的存储空间中,然后按照一个固定的频率,已固定的顺序逐个读取存储空间的显示数据,如果对应的数据要求点亮LED,则通过电路点亮屏幕上对应的像素(LED),反之,则不点亮对应的像素(LED)。如下图所示:
最上面的OLED屏是像素(LED)形成的矩阵。在中间部分,有个GRAM(Graphics RAM图形随机存储器)的存储区保存这OLED屏上每个像素(LED)的显示状态,图形处理单元(英语:Graphics Processing Unit,缩写:GPU)负责按照顺序读取GRAM中的数据,并更新OLED屏上对应像素(LED)的状态。显然,这样的操作如果让我们的MCU编程去做会浪费很多处理器的时间,而且由于结构限制,我们的通用MCU处理器并不适合干这样的工作。所谓"专业的事交给专业的人做",SO,这就要采用专用的显示屏驱动芯片了。SSD1306显然就是专为此而设计的芯片。下图是SSD1306芯片手册上截取的功能框图:
由图中,我们可以看出,SSD1306芯片内部带有一个GDDRAM的显示数据存储器,通过MCU Interface接口(SPI/I2C),可将我们要显示的数据保存到SSD1306中的GDDRAM中,然后,由芯片内部Display Controller(显示控制)电路将数据显示到显示屏(OLED屏)上。参看前图,我们的MCU只需要决定要显示什么内容就行了,通过SPI或I2C将显示内容发送到SSD1306中即可。
【需要说明的是SSD1306内部的Display Controller(显示控制)电路并不是真正意义上的GPU,通常GPU除了负责显示控制电路,一般GPU带有图形/图像处理能立,高端的还带有3D渲染能力,不过这超出了本文讨论范围。】
图形编程进阶-直接访问GRAM:
参考下图:
虽然,SSD1306芯片内部的GRAM(GDDRAM以下简称GRAM)是可读写的,但是由于uPyBoard的接口方式以及考虑到SSD1306芯片通讯稳定和传输速度等问题,这里采用的是单向通讯方式(也成为单工通信),我们只能将显示数据写入进去。因此,需要在显示编程时在我们自己的存储器(RAM)建立一个和屏幕像素数同样大小的显示区,通过程序修改这个显示区中的内容并在完全修改好后,一次性将显示区所有内容传到驱动芯片中更新OLED上的显示。
还记得上次的示例程序吗?
这个程序,就是先建立一个5字节的字节数组,并填好内容,通过调用:
oled.write_data(buffer)
将buffer中的数据,传输到SSD1306内部的GRAM中去。任何写入GRAM中的数据都会在OLED的屏幕上显示出来。因此,这是一种直接写显存(GRAM)的方式。这种方式简单直接。但是我们详细研究一下,就会发现显示和我们写入数据的对应关系有些特殊。
我们稍微修改一下上面的数据:
显示如下:
为什么显示成这样?这是因为,SSD1306的GRAM的组织并非我们前面所述的从左至右,从上到下的顺序,而是有其独特顺序(事实上,不同的驱动芯片顺序都不太相同,因此要仔细查看芯片手册),查看手册,我们知道,他是先在纵向上将8个像素组成一个数据字节,再横向排128个字节:
是不是很乱?那我们先来看看我们的数据和显示结果的对应关系吧:
图中,我将两次设置的数据分别用浅蓝色和深蓝色区分开,而白色其实代表实际屏幕上的黑色底。请注意,程序中分别发送了两次5字节的数据,第一次,5个字节都是0x55(十六进制),第二次,将0字节和2字节(buf[0],buf[2])修改为0xff(十六进制)。
0x55转换为二进制数就是:01010101,这里0表示背景黑色(图中白色格子),1表示点亮像素也就是图中蓝色格子。竖直方向第0列从第7行到第0行,正好是不亮,亮,不亮,亮,不亮,亮,不亮,亮,转换为二进制数就是01010101=0x55。以此类推,第5列,所有像素都亮,转为二进制数就是11111111=0xff。因此,可以发现两个结论:
1 SSD1306的显示顺序是先上下8个点,再左右128个列组成一个矩阵。
2 用oled.write_data(buffer)写入字节时,SSD1306会自动将数据依次排列到GRAM中。
知道,GRAM和屏幕像素的对映关系后,我们就能讲要显示的图像直接写入到GRAM中了。作为例子,我们显示这样一个图案:
嗯,我们暂且认为它是个小猫脸吧,一共占用25个字节,按照前面的顺序,我们转换成16进制数据如下:
编写程序如下:
LOOK,显示结果如下:
以上内容,如果你没听懂没有关系,讲这些就是让你知道我们知道直接操作SSD1306的GRAM是一件让人抓狂的事。不过庆幸的是,SSD1306库本身已经含有基础的图形绘制函数了,可以让我们轻松搞定显示操作,这种方法下次接着讲。另外,对于初学者不懂数制进制的同学也不要捉急,后面,我计划会抽出一些时间来写一些硬件入门的知识。
今天就先讲到这里,下次继续。请继续关注:创客DIY乐园(MakerDIY-Park),您的关注就是我前进的动力。