使用帧缓存加速屏幕刷新

一、基本说明

此次使用的开发板为立创·实战派,环境为 MicroPython,屏幕驱动为 st7789-mpy

二、屏幕显示原理

下面以一个像素为 8×8 的屏幕为例:

显示内容时对应的像素会被填充:

通过以下程序实现“F”的显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义一个列表用来存储“F”的信息
F = [0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, ]

# 在屏幕上逐个显示像素点
for i in range(8):
for j in range(8):
if F[i * 8 + j] == 1:
tft.pixel(j, i, BLACK)
else:
pass
print()

由于屏幕驱动的不同,所以以上代码不具有普适性,我们可以使用“#”和“ ”(空格)在终端输出验证我们的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义一个列表用来存储“F”的信息
F = [0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, ]

# 在屏幕上逐个显示像素点
for i in range(8):
for j in range(8):
if F[i * 8 + j] == 1:
print('#', end='')
else:
print(' ', end='')
print()

输出如下:

图片的显示也是同样的原理,每个像素显示对应的颜色就行了。

实际中,这种显示方式会带来明显的逐行扫描感,显示内容的尺寸越大越明显。比如:

解决办法是帧缓存(Frame Buffer)。

三、RGB565

在帧缓存之前,我需要再对此块屏幕进行一些说明:

此屏幕的颜色格式是 RGB565,生活中一般见到的是 RGB888,比如:

1
2
3
4
5
# 红色:(255, 0, 0) 或者 #FF0000
# 绿色:(0, 255, 0) 或者 #00FF00
# 蓝色:(0, 0, 255) 或者 #0000FF
# 白色:(255, 255, 255) 或者 #FFFFFF
# 黑色:(0, 0, 0) 或者 #000000

RGB888 表示分别用三组 8 位二进制数字来表示红、绿、蓝三种颜色的大小(或者说是光强),进而组合出不同的颜色。

8 位二进制数字等同于 2 位十六进制数字,可以表示的范围是十进制的 0~255,这是容易理解的。一个二进制数字是一个比特(bit,0或1),八个比特是一个字节(byte),那么一个像素需要三个字节来存储。

同样的道理,RGB565 的红、绿、蓝分别采用 5位、6位、5 位二进制数字表示。在十进制中,它们三个各自的范围是:

  • 红色:0~31
  • 绿色:0~63
  • 蓝色:0~31

RGB565 中一个像素需要 16(5+6+5)位二进制数字(bit),也就是需要两个字节(byte)。

下图是一个蓝色像素的例子:

四、帧缓存(Frame Buffer)

在之前我们是得到一个像素,就显示一个像素。逐像素渲染意味着每个像素的渲染和显示是串行进行的,即必须等待前一个像素渲染完成后才能开始下一个像素的渲染。这种串行处理方式会导致显著的渲染延迟。

可以想到一种“空间换时间”的策略:

内存相当于电脑的演算纸,我们可以划分出一块儿区域,让处理器把一张图(一组文字)的所有像素的信息都渲染出来,存到这一块内存。

之后内存一股脑把整块儿信息都给到屏幕,屏幕就可以一次把这些像素都显示出来,不必显示一个像素等一个像素。这其实就是帧缓存(Frame Buffer):

以一张分辨率为 50px*50px 的图片来说,它有 2500 个像素。RGB565 颜色格式下,一个像素 2 个字节,那么帧缓冲区的大小是 5000 bytes,约等于 4.8KB。

下面是一个在 MicroPython 中使用 FrameBuffer 渲染“F”的一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import framebuf


# 定义一个列表用来存储“F”的信息
F = [0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, ]


# 将列表转换为字节串
buffer = bytes(F)
# 创建FrameBuffer对象
fb = framebuf.FrameBuffer(buffer, 8, 8, framebuf.RGB565)
# 显示画布的内容
tft.display(fb, 0, 0) # 此处假设tft是已经初始化的显示屏对象

五、图片刷新测试

下面是我使用帧缓冲刷新图片的测试视频:

(一)随机位置刷新

(二)对角线刷新

dark
sans