本帖最后由 PY学习笔记 于 2024-12-1 20:01 编辑
行空板基于micropython-easydisplay和 micropython-ili9341 库实现英文,中文和日文显示。
首先,是easydisplay的代码:
# Github: https://github.com/funnygeeker/micropython-easydisplay
# Author: funnygeeker
# Licence: MIT
# Date: 2023/2/18
#
# 参考项目:
# https://github.com/AntonVanke/micropython-ufont
# https://github.com/boochow/MicroPython-ST7735/blob/master/tftbmp.py
#
# 参考资料:
# PBM图像显示:https://www.bilibili.com/video/av798158808
# PBM文件格式:https://www.cnblogs.com/SeekHit/p/7055748.html
# PBM文件转换:https://blog.csdn.net/jd3096/article/details/121319042
# 灰度化、二值化:https://blog.csdn.net/li_wen01/article/details/72867057
# Framebuffer 的 Palette: https://forum.micropython.org/viewtopic.php?t=12857
from io import BytesIO
from struct import unpack
from framebuf import FrameBuffer, MONO_HLSB, RGB565
class EasyDisplay:
READ_SIZE = 32 # Limit the picture read size to prevent memory errors in low-performance development boards
def __init__(self, display,
color_type,
font: str = None,
key: int = -1,
show: bool = None,
clear: bool = None,
invert: bool = False,
color: int = 0xFFFF,
bg_color: int = 0,
size: int = None,
auto_wrap: bool = False,
half_char: bool = True,
line_spacing: int = 0,
*args, **kwargs):
"""
初始化 EasyDisplay
Args:
display: The display instance
表示显示的实例
color_type: Color type of screen, "MONO" or "RGB565"
屏幕的颜色类型,"MONO" 或者 “RGB565”
font: The location of the font file
字体文件位置
key: The specified color will be treated as transparent (only applicable for Framebuffer mode)
指定的颜色将被视为透明(仅适用于 Framebuffer 模式)
show: Show immediately (only applicable for Framebuffer mode)
立即显示(仅适用于 Framebuffer 模式)
clear: Clear the screen
清理屏幕
invert: Invert colors
反转颜色
color_type: Image format, "RGB565" for RGB565 screen, "MONO" for black and white screen
图像格式,RGB565 屏幕用 "RGB565",黑白 屏幕用 "MONO"
color: The main color of the image (only effective when displaying black and white images on a color screen)
图像主体颜色(仅彩色屏幕显示黑白图像时生效)
bg_color: The background color of the image (only effective when displaying black and white images on a color screen)
图像背景颜色(仅彩色屏幕显示黑白图像时生效)
size: Font size
文本字体大小
auto_wrap: Automatically wrap text
文本自动换行
half_char: Display ASCII characters in half width
半宽显示 ASCII 字符
line_spacing: Line spacing for text
文本行间距
"""
self.display = display
self._buffer = hasattr(display, 'buffer') # buffer: 驱动是否使用了帧缓冲区,False(SPI 直接驱动) / True(Framebuffer)
self._font = None
self._key = key
self._show = show
self._clear = clear
self.invert = invert
self.color_type = color_type
self.color = color
self.bg_color = bg_color
self.size = size
self.auto_wrap = auto_wrap
self.half_char = half_char
self.line_spacing = line_spacing
self.font_size = None
self.font_bmf_info = None
self.font_version = None
self.font_file = None
self.font_map_mode = None
self.font_start_bitmap = None
self.font_bitmap_size = None
if font:
self.load_font(font)
# Framebuffer Function: https://docs.micropython.org/en/latest/library/framebuf.html
def fill(self, *args, **kwargs):
self.display.fill(*args, **kwargs)
def pixel(self, *args, **kwargs):
return self.display.pixel(*args, **kwargs)
def hline(self, *args, **kwargs):
self.display.hline(*args, **kwargs)
def vline(self, *args, **kwargs):
self.display.vline(*args, **kwargs)
def line(self, *args, **kwargs):
self.display.line(*args, **kwargs)
def rect(self, *args, **kwargs):
self.display.rect(*args, **kwargs)
def fill_rect(self, *args, **kwargs):
self.display.fill_rect(*args, **kwargs)
def scroll(self, *args, **kwargs):
self.display.scroll(*args, **kwargs)
def blit(self, *args, **kwargs):
self.display.blit(*args, **kwargs)
def ellipse(self, *args, **kwargs):
self.display.ellipse(*args, **kwargs)
def poly(self, *args, **kwargs):
self.display.poly(*args, **kwargs)
# Only partial screen driver support
def circle(self, *args, **kwargs):
self.display.circle(*args, **kwargs)
def fill_circle(self, *args, **kwargs):
self.display.fill_circle(*args, **kwargs)
def clear(self):
"""
Clear screen
"""
self.display.fill(0)
def show(self):
"""
Display
"""
try:
self.display.show()
except AttributeError:
pass
@staticmethod
def rgb565_color(r, g, b):
"""
Convert red, green and blue values (0-255) into a 16-bit 565 encoding.
"""
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3
def _get_index(self, word: str) -> int:
"""
Get Text Index 获取文字索引
Args:
word: Character 字符
"""
word_code = ord(word)
start = 0x10
end = self.font_start_bitmap
_seek = self._font.seek
_font_read = self._font.read
while start <= end:
mid = ((start + end) // 4) * 2
_seek(mid, 0)
target_code = unpack(">H", _font_read(2))[0]
if word_code == target_code:
return (mid - 16) >> 1
elif word_code < target_code:
end = mid - 2
else:
start = mid + 2
return -1
# @timeit
@staticmethod
def _hlsb_font_size(bytearray_data: bytearray, new_size: int, old_size: int) -> bytearray:
"""
Scale HLSB Characters 缩放字符
Args:
bytearray_data: Source char data 源字符数据
new_size: New char size 新字符大小
old_size: Old char size 旧字符大小
Returns:
Scaled character data 缩放后的数据
"""
r = range(new_size) # Preload functions to avoid repeated execution and improve efficiency
if old_size == new_size:
return bytearray_data
_t = bytearray(new_size * ((new_size >> 3) + 1))
_new_index = -1
for _col in r:
for _row in r:
if _row % 8 == 0:
_new_index += 1
_old_index = int(_col / (new_size / old_size)) * old_size + int(_row / (new_size / old_size))
_t[_new_index] = _t[_new_index] | (
(bytearray_data[_old_index >> 3] >> (7 - _old_index % 8) & 1) << (7 - _row % 8))
return _t
def get_bitmap(self, word: str) -> bytes:
"""
Get Dot Matrix Image 获取点阵图
Args:
word: Single character 单个字符
Returns:
Bytes representing the dot matrix image of the character 字符点阵
"""
index = self._get_index(word)
if index == -1:
return b'\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x0f\xcf\xf3\xcf\xf3\xff\xf3\xff\xcf\xff?\xff?\xff\xff\xff' \
b'?\xff?\xff\xff\xff\xff' # Returns the question mark icon
self._font.seek(self.font_start_bitmap + index * self.font_bitmap_size, 0)
return self._font.read(self.font_bitmap_size)
def load_font(self, file: str):
"""
Load Font File 加载字体文件
Args:
file: Path to the font file 文件路径
"""
self.font_file = file
self._font = open(file, "rb")
# 获取字体文件信息
# 字体文件信息大小 16 byte ,按照顺序依次是
# 文件标识 2 byte
# 版本号 1 byte
# 映射方式 1 byte
# 位图开始字节 3 byte
# 字号 1 byte
# 单字点阵字节大小 1 byte
# 保留 7 byte
self.font_bmf_info = self._font.read(16)
# 判断字体是否正确,文件头和常用的图像格式 BMP 相同,需要添加版本验证来辅助验证
if self.font_bmf_info[0:2] != b"BM":
raise TypeError("Incorrect font file format: {}".format(file))
self.font_version = self.font_bmf_info[2]
if self.font_version != 3:
raise TypeError("Incorrect font file version: {}".format(self.font_version))
# 映射方式,目前映射方式并没有加以验证,原因是 MONO 最易于处理
self.font_map_mode = self.font_bmf_info[3]
# 位图开始字节,位图数据位于文件尾,需要通过位图开始字节来确定字体数据实际位置
self.font_start_bitmap = unpack(">I", b'\x00' + self.font_bmf_info[4:7])[0]
# 字体大小,默认的文字字号,用于缩放方面的处理
self.font_size = self.font_bmf_info[7]
if self.size is None:
self.size = int(self.font_size)
# 点阵所占字节,用来定位字体数据位置
self.font_bitmap_size = self.font_bmf_info[8]
def text(self, s: str, x: int, y: int,
color: int = None, bg_color: int = None, size: int = None,
half_char: bool = None, auto_wrap: bool = None, show: bool = None, clear: bool = None,
key: bool = None, invert: bool = None, line_spacing: int = None, *args, **kwargs):
"""
Args:
s: String
字符串
x: X-coordinate of the string
x 坐标
y: Y-coordinate of the string
y 坐标
color: Text color (RGB565 range is 0-65535, MONO range is 0 and greater than zero - typically use 1)
文字颜色 (RGB565 范围为 0-65535,MONO 范围为 0 和 大于零-通常使用 1)
bg_color: Text background color (RGB565 range is 0-65535, MONO range is 0 and greater than zero - typically use 1)\
文字背景颜色 (RGB565 范围为 0-65535,MONO 范围为 0 和 大于零-通常使用 1)
key: Transparent color, when color matches key, it becomes transparent (only applicable in Framebuffer mode)
透明色,当颜色与 key 相同时则透明 (仅适用于 Framebuffer 模式)
size: Text size
文字大小
show: Show immediately
立即显示
clear: Clear buffer / Clear screen
清理缓冲区 / 清理屏幕
invert: Invert (MONO)
逆置(MONO)
auto_wrap: Enable auto wrap
自动换行
half_char: Display ASCII characters in half width
半宽显示 ASCII 字符
line_spacing: Line spacing
行间距
"""
if color is None:
color = self.color
if bg_color is None:
bg_color = self.bg_color
if key is None:
key = self._key
if size is None:
size = self.size
if show is None:
show = self._show
if clear is None:
clear = self._clear
if invert is None:
invert = self.invert
if auto_wrap is None:
auto_wrap = self.auto_wrap
if half_char is None:
half_char = self.half_char
color_type = self.color_type
if line_spacing is None:
line_spacing = self.line_spacing
# 如果没有指定字号则使用默认字号
font_size = size or self.font_size
# 记录初始的 x 位置
init_x = x
try:
_seek = self._font.seek
except AttributeError:
raise AttributeError("The font file is not loaded... Did you forgot?")
dp = self.display
font_offset = font_size // 2
# 颜色反转
if invert:
color, bg_color = bg_color, color
# 配置调色板
if color_type == "MONO":
palette = FrameBuffer(bytearray(1), 2, 1, MONO_HLSB) # MONO pixels occupy 1 byte for every 8 pixels
elif color_type == "RGB565":
palette = FrameBuffer(bytearray(4), 2, 1, RGB565) # RGB565 pixels occupy 2 bytes for every 1 pixel
else:
raise KeyError("Unsupported color_type: {}".format(color_type))
palette.pixel(1, 0, color)
palette.pixel(0, 0, bg_color)
# 清屏
if clear:
self.clear()
for char in s:
if auto_wrap and ((x + font_offset > dp.width and ord(char) < 128 and half_char) or
(x + font_size > dp.width and (not half_char or ord(char) > 128))):
y += font_size + line_spacing
x = init_x
# 对控制字符的处理
if char == '\n':
y += font_size + line_spacing
x = init_x
continue
elif char == '\t':
x = ((x // font_size) + 1) * font_size + init_x % font_size
continue
elif ord(char) < 16:
continue
# 超过范围的字符不会显示
if x > 240 or y > 320:
continue
# 获取字体的点阵数据
byte_data = self.get_bitmap(char)
# 准备并缩放字符数据
byte_data = bytearray(byte_data)
if font_size != self.font_size:
byte_data = self._hlsb_font_size(byte_data, font_size, self.font_size)
# 显示字符
fbuf = FrameBuffer(byte_data, font_size, font_size, MONO_HLSB)
if self._buffer: # FrameBuffer Driven
dp.blit(fbuf, x, y, key, palette)
else:
if color_type == "RGB565":
n_fbuf = FrameBuffer(bytearray(font_size * font_size * 2), font_size, font_size, RGB565)
n_fbuf.blit(fbuf, 0, 0, key, palette) # Render black and white pixels to color
elif color_type == "MONO":
n_fbuf = fbuf # Not tested
else:
raise ValueError("Unsupported color_type: {}".format(color_type))
dp._writeblock(x, y, x + font_size - 1, y + font_size - 1)
dp._data(n_fbuf)
# 英文字符半格显示
if ord(char) < 128 and half_char:
x += font_offset
else:
x += font_size
self.show() if show else 0
def ppm(self, *args, **kwargs):
self.pbm(*args, **kwargs)
def pbm(self, file, x, y, key: int = None, show: bool = None, clear: bool = None, invert: bool = False,
color: int = None, bg_color: int = None):
"""
Display PBM / PPM Image
显示 pbm / ppm 图片
# You can use the Pillow library in python3 to convert the image to PBM format. For example:
# 您可以通过使用 python3 的 pillow 库将图片转换为 pbm 格式,比如:
# convert_type = "1" # "1" for black and white image, "RGBA" for colored image
# convert_type = "1" # 1 为黑白图像,RGBA 为彩色图像
#
# from PIL import Image
# with Image.open("filename.png", "r") as img:
# img2 = img.convert(convert_type)
# img2.save("filename.pbm")
Args:
file: PBM file
pbm 文件
File path (str)
文件路径
Raw data (BytesIO)
原始数据
x: X-coordinate
X 坐标
y: Y-coordinate
Y 坐标
key: Specified color to be treated as transparent (only applicable in Framebuffer mode)
指定的颜色将被视为透明(仅适用于 Framebuffer 模式)
show: Show immediately (only applicable in Framebuffer mode)
立即显示(仅适用于 Framebuffer 模式)
clear: Clear screen
清理屏幕
invert: Invert colors
反转颜色
color: Image main color (only effective when displaying black and white image on a color screen)
图像主体颜色(仅彩色屏幕显示黑白图像时生效)
bg_color: Image background color (only effective when displaying black and white image on a color screen)
图像背景颜色(仅彩色屏幕显示黑白图像时生效)
"""
if key is None:
key = self._key
if show is None:
show = self._show
if clear is None:
clear = self._clear
if invert is None:
invert = self.invert
color_type = self.color_type
if color is None:
color = self.color
if bg_color is None:
bg_color = self.bg_color
if clear: # 清屏
self.clear()
dp = self.display
if isinstance(file, BytesIO):
func = file
else:
func = open(file, "rb")
with func as f:
file_format = f.readline() # 获取文件格式
_width, _height = [int(value) for value in f.readline().split()] # 获取图片的宽度和高度
f_read = f.read
if file_format == b"P4\n": # P4 位图 二进制
# 颜色反转
if invert:
color, bg_color = bg_color, color
# 配置调色板
if color_type == "MONO":
palette = FrameBuffer(bytearray(1), 2, 1, MONO_HLSB)
elif color_type == "RGB565":
palette = FrameBuffer(bytearray(4), 2, 1, RGB565)
else:
raise KeyError("Unsupported color_type: {}".format(color_type))
palette.pixel(1, 0, color)
palette.pixel(0, 0, bg_color)
if self._buffer: # Framebuffer 模式
data = bytearray(f_read()) # 读取并显示图像
fbuf = FrameBuffer(data, _width, _height, MONO_HLSB)
dp.blit(fbuf, x, y, key, palette)
else: # 直接驱动
write_data = dp.write_data
dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口
buffer_size = self.READ_SIZE
width = buffer_size * 8
# Use different types of buffers according to different color types
if color_type == "RGB565":
data_fbuf = FrameBuffer(bytearray(buffer_size * 16), width, 1, RGB565)
elif color_type == "MONO":
data_fbuf = FrameBuffer(bytearray(buffer_size), width, 1, MONO_HLSB) # Not tested
else:
raise ValueError("Unsupported color_type: {}".format(color_type))
data_fbuf_blit = data_fbuf.blit
# Read a picture several times, taking a part of it each time
data = bytearray(f_read(buffer_size))
while data:
fbuf = FrameBuffer(data, width, 1, MONO_HLSB)
data_fbuf_blit(fbuf, 0, 0, key, palette) # Render MONO pixels into RGB565 pixels
len_data = len(data)
if len_data < buffer_size: # Limit the data sent to no more than the Buffer size, so as to avoid data overflow and affect the display
if color_type == "RGB565":
fbuf_data = bytearray(data_fbuf)[:len_data * 16]
elif color_type == "MONO":
fbuf_data = bytearray(data_fbuf)[:len_data]
else:
raise ValueError("Unsupported color_type: {}".format(color_type))
else:
fbuf_data = bytearray(data_fbuf)
write_data(fbuf_data)
data = bytearray(f_read(buffer_size))
elif file_format == b"P6\n": # P6 像素图 二进制
max_pixel_value = f.readline() # 获取最大像素值
r_height = range(_height)
r_width = range(_width)
color_bytearray = bytearray(3) # 为变量预分配内存
f_rinto = f.readinto
try:
dp_color = dp.color
except AttributeError:
dp_color = self.rgb565_color
dp_pixel = dp.pixel
if self._buffer: # Framebuffer 模式
if color_type == "RGB565":
buffer = bytearray(_width * 2)
for _y in r_height: # 逐行显示图片
for _x in r_width:
f_rinto(color_bytearray)
r, g, b = color_bytearray[0], color_bytearray[1], color_bytearray[2]
if invert:
r = 255 - r
g = 255 - g
b = 255 - b
if color_type == "RGB565":
buffer[_x * 2: (_x + 1) * 2] = dp_color(r, g, b).to_bytes(2, 'big') # 通过索引赋值
elif color_type == "MONO":
_color = int((r + g + b) / 3) >= 127
if _color:
_color = color
else:
_color = bg_color
if _color != key: # 不显示指定颜色
dp_pixel(_x + x, _y + y, _color)
if color_type == "RGB565":
fbuf = FrameBuffer(buffer, _width, 1, RGB565)
dp.blit(fbuf, x, y + _y, key)
else: # 直接驱动
dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口
buffer = bytearray(_width * 2)
for _y in r_height: # 逐行显示图片
for _x in r_width:
color_bytearray = f_read(3)
r, g, b = color_bytearray[0], color_bytearray[1], color_bytearray[2]
if invert:
r = 255 - r
g = 255 - g
b = 255 - b
if color_type == "RGB565":
buffer[_x * 2: (_x + 1) * 2] = dp_color(
r, g, b).to_bytes(2, 'big') # 通过索引赋值
elif color_type == "MONO":
_color = int((r + g + b) / 3) >= 127
if _color:
_color = color
else:
_color = bg_color
if _color != key: # 不显示指定颜色
dp_pixel(_x + x, _y + y, _color)
if color_type == "RGB565":
dp.write_data(buffer)
else:
raise TypeError("Unsupported File Format Type.")
self.show() if show else 0 # 立即显示
def bmp(self, file, x, y, key: int = None, show: bool = None, clear: bool = None, invert: bool = False,
color: int = None, bg_color: int = None):
"""
Display BMP Image 显示 bmp 图片
# You can convert the image to `24-bit` `bmp` format using the Paint application in Windows.
# Alternatively, you can use software like `Image2Lcd` to convert the image to `24-bit` `bmp` format (horizontal scan, includes image header data, 24-bit grayscale).
# 您可以通过使用 windows 的 画图 将图片转换为 `24-bit` 的 `bmp` 格式
# 也可以使用 `Image2Lcd` 这款软件将图片转换为 `24-bit` 的 `bmp` 格式(水平扫描,包含图像头数据,灰度二十四位)
Args:
file: bmp file
bmp 文件
File path (str)
文件路径
Raw data (BytesIO)
原始数据
x: X-coordinate
X 坐标
y: Y-coordinate
Y 坐标
key: Specified color to be treated as transparent (only applicable in Framebuffer mode)
指定的颜色将被视为透明(仅适用于 Framebuffer 模式)
show: Show immediately (only applicable in Framebuffer mode)
立即显示(仅适用于 Framebuffer 模式)
clear: Clear screen
清理屏幕
invert: Invert colors
反转颜色
color: Image main color (only effective when displaying black and white image on a color screen)
图像主体颜色(仅彩色图片显示以黑白形式显示时生效)
bg_color: Image background color (only effective when displaying black and white image on a color screen)
图像背景颜色(仅彩色图片显示以黑白形式显示时生效)
"""
if key is None:
key = self._key
if show is None:
show = self._show
if clear is None:
clear = self._clear
if invert is None:
invert = self.invert
color_type = self.color_type
if color is None:
color = self.color
if bg_color is None:
bg_color = self.bg_color
if isinstance(file, BytesIO):
func = file
else:
func = open(file, "rb")
with func as f:
f_read = f.read
f_rinto = f.readinto
f_seek = f.seek
f_tell = f.tell()
dp = self.display
try:
dp_color = dp.color
except AttributeError:
dp_color = self.rgb565_color
dp_pixel = dp.pixel
if f_read(2) == b'BM': # 检查文件头
dummy = f_read(8) # 文件大小占四个字节,文件作者占四个字节,file size(4), creator bytes(4)
int_fb = int.from_bytes
offset = int_fb(f_read(4), 'little') # 像素存储位置占四个字节
hdrsize = int_fb(f_read(4), 'little') # DIB header 占四个字节
_width = int_fb(f_read(4), 'little') # 图像宽度
_height = int_fb(f_read(4), 'little') # 图像高度
if int_fb(f_read(2), 'little') == 1: # 色彩平面数 planes must be 1
depth = int_fb(f_read(2), 'little') # 像素位数
# 转换时只支持二十四位彩色,不压缩的图像
if depth == 24 and int_fb(f_read(4), 'little') == 0: # compress method == uncompressed
row_size = (_width * 3 + 3) & ~3
if _height < 0:
_height = -_height
flip = False
else:
flip = True
if _width > dp.width: # Limit the maximum size of image display
_width = dp.width
if _height > dp.height:
_height = dp.height
_color_bytearray = bytearray(3) # 像素的二进制颜色
if clear: # 清屏
self.clear()
buffer = bytearray(_width * 2)
self_buf = self._buffer
if not self_buf:
dp.set_window(x, y, x + _width - 1, y + _height - 1) # 设置窗口
r_width = range(_width)
r_height = range(_height)
for _y in r_height:
if flip:
pos = offset + (_height - 1 - _y) * row_size
else:
pos = offset + _y * row_size
if f_tell != pos:
f_seek(pos) # 调整指针位置
for _x in r_width:
f_rinto(_color_bytearray)
r, g, b = _color_bytearray[2], _color_bytearray[1], _color_bytearray[0]
if invert: # 颜色反转
r = 255 - r
g = 255 - g
b = 255 - b
if self_buf: # Framebuffer 模式
if color_type == "RGB565":
buffer[_x * 2: (_x + 1) * 2] = dp_color(
r, g, b).to_bytes(2, 'big') # 通过索引赋值
elif color_type == "MONO":
_color = int((r + g + b) / 3) >= 127
if _color:
_color = color
else:
_color = bg_color
if _color != key: # 不显示指定颜色
dp_pixel(_x + x, _y + y, _color)
else:
if color_type == "RGB565":
buffer[_x * 2: (_x + 1) * 2] = dp_color(
r, g, b).to_bytes(2, 'big') # 通过索引赋值
elif color_type == "MONO":
_color = int((r + g + b) / 3) >= 127
if _color:
_color = color
else:
_color = bg_color
if _color != key: # 不显示指定颜色
dp_pixel(_x + x, _y + y, _color)
if color_type == "RGB565":
if self_buf:
fbuf = FrameBuffer(buffer, _width, 1, RGB565)
dp.blit(fbuf, x, y + _y, key)
else:
dp.write_data(buffer)
self.show() if show else 0 # 立即显示
else:
raise TypeError("Unsupported file type: only 24-bit uncompressed BMP images are supported.")
else:
raise TypeError("Unsupported file type: only BMP images are supported.")
def dat(self, file, x, y, key=None):
"""
Display screen raw data file, with extremely high efficiency, only supports RGB565 format.
显示表示屏幕原始数据的文件,拥有极高的效率,仅支持 RGB565 格式
Args:
file: dat file dat 文件
File path (str) 文件路径
Raw data (BytesIO) 原始数据
x: X-coordinate X坐标
y: Y-coordinate Y 坐标
key: Specified color to be treated as transparent (only applicable in Framebuffer mode)
指定的颜色将被视为透明(仅适用于 Framebuffer 模式)
"""
if key is None:
key = self._key
if isinstance(file, BytesIO):
func = file
else:
func = open(file, "rb")
with func as f:
f_readline = f.readline
f_read = f.read
file_head = f_readline().rstrip(b'\n')
if file_head == b'EasyDisplay': # 文件头
version = f_readline().rstrip(b'\n')
if version == b'V1': # 文件格式版本
_width, _height = f_readline().rstrip(b'\n').split(b' ')
_width, _height = int(_width), int(_height)
if self._buffer: # Framebuffer 模式
data = f_read(_width)
dp_blit = self.display.blit
y_offset = 0
while data:
buf = FrameBuffer(bytearray(data), _width, 1, RGB565)
dp_blit(buf, x, y + y_offset, key)
data = f_read(_width)
y_offset += 1
else: # 直接驱动模式
size = self.READ_SIZE * 10
data = f_read(size)
dp_write = self.display.write_data
self.display.set_window(x, y, x + _width - 1, y + _height - 1)
while data:
dp_write(data)
data = f_read(size)
else:
raise TypeError("Unsupported Version: {}".format(version))
else:
try:
raise TypeError("Unsupported File Type: {}".format(file_head))
except:
raise TypeError("Unsupported File Type!")
复制代码
然后是ILI9341屏幕的代码:
import time
import framebuf
from machine import SPI, Pin , I2C
i2c1 = I2C(0, scl=Pin(48), sda=Pin(47), freq=100000)
'''
class ILI9341:
def __init__(self, width, height):
self.width = width
self.height = height
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
self.framebuf = framebuf.FrameBuffer(
self.buffer, self.width, self.height, framebuf.MONO_VLSB
)
self.spi = SPI(1,baudrate=40000000, phase=0, polarity=0, sck=Pin(12),mosi=Pin(21))
# chip select
self.cs = Pin(14, mode=Pin.OUT, pull=Pin.PULL_UP)
# command
self.dc = Pin(13, mode=Pin.OUT, pull=Pin.PULL_UP)
# initialize all pins high
self.cs.value(1)
self.dc.value(1)
#self.spi.init(baudrate=8000000, phase=0, polarity=0, sck=Pin(12),mosi=Pin(21),miso=-1)
self._i2c = i2c1
self._i2c.writeto(0x20,bytearray([0x02, 0x01]))
self._i2c.writeto(0x20,bytearray([0x06, 0xFE]))
self.init_display()
def init_display(self):
time.sleep_ms(500)
self.write_cmd(0x01)
time.sleep_ms(200)
self.write_cmd(0xCF)
self.write_data(bytearray([0x00, 0x8B, 0x30]))
self.write_cmd(0xED)
self.write_data(bytearray([0x67, 0x03, 0x12, 0x81]))
self.write_cmd(0xE8)
self.write_data(bytearray([0x85, 0x10, 0x7A]))
self.write_cmd(0xCB)
self.write_data(bytearray([0x39, 0x2C, 0x00, 0x34, 0x02]))
self.write_cmd(0xF7)
self.write_data(bytearray([0x20]))
self.write_cmd(0xEA)
self.write_data(bytearray([0x00, 0x00]))
# Power control
self.write_cmd(0xC0)
# VRH[5:0]
self.write_data(bytearray([0x1B]))
# Power control
self.write_cmd(0xC1)
# SAP[2:0];BT[3:0]
self.write_data(bytearray([0x10]))
# VCM control
self.write_cmd(0xC5)
self.write_data(bytearray([0x3F, 0x3C]))
# VCM control2
self.write_cmd(0xC7)
self.write_data(bytearray([0xB7]))
# Memory Access Control
self.write_cmd(0x36)
self.write_data(bytearray([0x08]))
self.write_cmd(0x3A)
self.write_data(bytearray([0x55]))
self.write_cmd(0xB1)
self.write_data(bytearray([0x00, 0x1B]))
# Display Function Control
self.write_cmd(0xB6)
self.write_data(bytearray([0x0A, 0xA2]))
# 3Gamma Function Disable
self.write_cmd(0xF2)
self.write_data(bytearray([0x00]))
# Gamma curve selected
self.write_cmd(0x26)
self.write_data(bytearray([0x01]))
# Set Gamma
self.write_cmd(0xE0)
self.write_data(
bytearray(
[
0x0F,
0x2A,
0x28,
0x08,
0x0E,
0x08,
0x54,
0xA9,
0x43,
0x0A,
0x0F,
0x00,
0x00,
0x00,
0x00,
]
)
)
# Set Gamma
self.write_cmd(0xE1)
self.write_data(
bytearray(
[
0x00,
0x15,
0x17,
0x07,
0x11,
0x06,
0x2B,
0x56,
0x3C,
0x05,
0x10,
0x0F,
0x3F,
0x3F,
0x0F,
]
)
)
# Exit Sleep
self.write_cmd(0x11)
time.sleep_ms(120)
# Display on
self.write_cmd(0x29)
time.sleep_ms(500)
self.fill(0)
def show(self):
# set col
self.write_cmd(0x2A)
self.write_data(bytearray([0x00, 0x00]))
self.write_data(bytearray([0x00, 0xEF]))
# set page
self.write_cmd(0x2B)
self.write_data(bytearray([0x00, 0x00]))
self.write_data(bytearray([0x01, 0x3F]))
self.write_cmd(0x2C)
buffer = bytearray()
buffer_size = 4096*2
for row in range(0, self.pages):
for pixel_pos in range(0, 8):
for col in range(0, self.width):
compressed_pixel = self.buffer[row * 240 + col]
if ((compressed_pixel >> pixel_pos) & 0x1) == 0:
#self.write_data(bytearray([0x00, 0x00]))
buffer.extend(bytearray([0x00, 0x00]))
else:
buffer.extend(bytearray([0xFF, 0xFF]))
#self.write_data(bytearray([0xFF, 0xFF]))
if len(buffer) >= buffer_size:
self.write_data(buffer)
buffer = bytearray()
if len(buffer) > 0:
self.write_data(buffer)
def fill(self, col):
self.framebuf.fill(col)
def pixel(self, x, y, col):
self.framebuf.pixel(x, y, col)
def scroll(self, dx, dy):
self.framebuf.scroll(dx, dy)
def text(self, string, x, y, col=1):
self.framebuf.text(string, x, y, col)
def write_cmd(self, cmd):
self.dc.value(0)
self.cs.value(0)
self.spi.write(bytearray([cmd]))
self.cs.value(1)
def write_data(self, buf):
self.dc.value(1)
self.cs.value(0)
self.spi.write(buf)
self.cs.value(1)
'''
# This is an adapted version of the ILI934X driver as below.
# It works with multiple fonts and also works with the esp32 H/W SPI implementation
# Also includes a word wrap print function
# Proportional fonts are generated by Peter Hinch's Font-to-py
# MIT License; Copyright (c) 2017 Jeffrey N. Magee
# This file is part of MicroPython ILI934X driver
# Copyright (c) 2016 - 2017 Radomir Dopieralski, Mika Tuupola
#
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
#
# Project home:
# https://github.com/tuupola/micropython-ili934x
import time
import ustruct
import glcdfont
import framebuf
from micropython import const
_RDDSDR = const(0x0f) # Read Display Self-Diagnostic Result
_SLPOUT = const(0x11) # Sleep Out
_GAMSET = const(0x26) # Gamma Set
_DISPOFF = const(0x28) # Display Off
_DISPON = const(0x29) # Display On
_CASET = const(0x2a) # Column Address Set
_PASET = const(0x2b) # Page Address Set
_RAMWR = const(0x2c) # Memory Write
_RAMRD = const(0x2e) # Memory Read
_MADCTL = const(0x36) # Memory Access Control
_VSCRSADD = const(0x37) # Vertical Scrolling Start Address
_PIXSET = const(0x3a) # Pixel Format Set
_PWCTRLA = const(0xcb) # Power Control A
_PWCRTLB = const(0xcf) # Power Control B
_DTCTRLA = const(0xe8) # Driver Timing Control A
_DTCTRLB = const(0xea) # Driver Timing Control B
_PWRONCTRL = const(0xed) # Power on Sequence Control
_PRCTRL = const(0xf7) # Pump Ratio Control
_PWCTRL1 = const(0xc0) # Power Control 1
_PWCTRL2 = const(0xc1) # Power Control 2
_VMCTRL1 = const(0xc5) # VCOM Control 1
_VMCTRL2 = const(0xc7) # VCOM Control 2
_FRMCTR1 = const(0xb1) # Frame Rate Control 1
_DISCTRL = const(0xb6) # Display Function Control
_ENA3G = const(0xf2) # Enable 3G
_PGAMCTRL = const(0xe0) # Positive Gamma Control
_NGAMCTRL = const(0xe1) # Negative Gamma Control
_CHUNK = const(1024) #maximum number of pixels per spi write
def color565(r, g, b):
return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3
class ILI9341:
def __init__(self, spi, cs, dc, rst, w, h, r):
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
self._init_width = w
self._init_height = h
self.width = w
self.height = h
self.rotation = r
self.cs.init(self.cs.OUT, value=1)
self.dc.init(self.dc.OUT, value=0)
self.rst.init(self.rst.OUT, value=0)
self.reset()
self.init()
self._scroll = 0
self._buf = bytearray(_CHUNK * 2)
self._colormap = bytearray(b'\x00\x00\xFF\xFF') #default white foregraound, black background
self._x = 0
self._y = 0
self._font = glcdfont
self.scrolling = False
def set_color(self,fg,bg):
self._colormap[0] = bg>>8
self._colormap[1] = bg & 255
self._colormap[2] = fg>>8
self._colormap[3] = fg & 255
def set_pos(self,x,y):
self._x = x
self._y = y
#self.scroll(0)
def reset_scroll(self):
self.scrolling = False
self._scroll = 0
self.scroll(0)
def set_font(self, font):
self._font = font
def init(self):
for command, data in (
(_RDDSDR, b"\x03\x80\x02"),
(_PWCRTLB, b"\x00\xc1\x30"),
(_PWRONCTRL, b"\x64\x03\x12\x81"),
(_DTCTRLA, b"\x85\x00\x78"),
(_PWCTRLA, b"\x39\x2c\x00\x34\x02"),
(_PRCTRL, b"\x20"),
(_DTCTRLB, b"\x00\x00"),
(_PWCTRL1, b"\x23"),
(_PWCTRL2, b"\x10"),
(_VMCTRL1, b"\x3e\x28"),
(_VMCTRL2, b"\x86")):
self._write(command, data)
if self.rotation == 0: # 0 deg
self._write(_MADCTL, b"\x48")
self.width = self._init_height
self.height = self._init_width
elif self.rotation == 1: # 90 deg
self._write(_MADCTL, b"\x28")
self.width = self._init_width
self.height = self._init_height
elif self.rotation == 2: # 180 deg
self._write(_MADCTL, b"\x88")
self.width = self._init_height
self.height = self._init_width
elif self.rotation == 3: # 270 deg
self._write(_MADCTL, b"\xE8")
self.width = self._init_width
self.height = self._init_height
elif self.rotation == 4: # Mirrored + 0 deg
self._write(_MADCTL, b"\xC8")
self.width = self._init_height
self.height = self._init_width
elif self.rotation == 5: # Mirrored + 90 deg
self._write(_MADCTL, b"\x68")
self.width = self._init_width
self.height = self._init_height
elif self.rotation == 6: # Mirrored + 180 deg
self._write(_MADCTL, b"\x08")
self.width = self._init_height
self.height = self._init_width
elif self.rotation == 7: # Mirrored + 270 deg
self._write(_MADCTL, b"\xA8")
self.width = self._init_width
self.height = self._init_height
else:
self._write(_MADCTL, b"\x08")
for command, data in (
(_PIXSET, b"\x55"),
(_FRMCTR1, b"\x00\x18"),
(_DISCTRL, b"\x08\x82\x27"),
(_ENA3G, b"\x00"),
(_GAMSET, b"\x01"),
(_PGAMCTRL, b"\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"),
(_NGAMCTRL, b"\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f")):
self._write(command, data)
self._write(_SLPOUT)
time.sleep_ms(120)
self._write(_DISPON)
self._i2c = i2c1
self._i2c.writeto(0x20,bytearray([0x02, 0x01]))
self._i2c.writeto(0x20,bytearray([0x06, 0xFE]))
def reset(self):
self.rst(0)
time.sleep_ms(50)
self.rst(1)
time.sleep_ms(50)
def _write(self, command, data=None):
self.dc(0)
self.cs(0)
self.spi.write(bytearray([command]))
self.cs(1)
if data is not None:
self._data(data)
def _data(self, data):
self.dc(1)
self.cs(0)
self.spi.write(data)
self.cs(1)
def _writeblock(self, x0, y0, x1, y1, data=None):
self._write(_CASET, ustruct.pack(">HH", x0, x1))
self._write(_PASET, ustruct.pack(">HH", y0, y1))
self._write(_RAMWR, data)
def _readblock(self, x0, y0, x1, y1, data=None):
self._write(_CASET, ustruct.pack(">HH", x0, x1))
self._write(_PASET, ustruct.pack(">HH", y0, y1))
if data is None:
return self._read(_RAMRD, (x1 - x0 + 1) * (y1 - y0 + 1) * 3)
def _read(self, command, count):
self.dc(0)
self.cs(0)
self.spi.write(bytearray([command]))
data = self.spi.read(count)
self.cs(1)
return data
def pixel(self, x, y, color=None):
if color is None:
r, b, g = self._readblock(x, y, x, y)
return color565(r, g, b)
if not 0 <= x < self.width or not 0 <= y < self.height:
return
self._writeblock(x, y, x, y, ustruct.pack(">H", color))
def fill_rectangle(self, x, y, w, h, color=None):
x = min(self.width - 1, max(0, x))
y = min(self.height - 1, max(0, y))
w = min(self.width - x, max(1, w))
h = min(self.height - y, max(1, h))
if color:
color = ustruct.pack(">H", color)
else:
color = self._colormap[0:2] #background
for i in range(_CHUNK):
self._buf[2*i]=color[0]; self._buf[2*i+1]=color[1]
chunks, rest = divmod(w * h, _CHUNK)
self._writeblock(x, y, x + w - 1, y + h - 1, None)
if chunks:
for count in range(chunks):
self._data(self._buf)
if rest != 0:
mv = memoryview(self._buf)
self._data(mv[:rest*2])
def fill(self,c):
self.fill_rectangle(0, 0, self.width, self.height,c)
def blit(self, bitbuff, x, y, w, h):
x = min(self.width - 1, max(0, x))
y = min(self.height - 1, max(0, y))
w = min(self.width - x, max(1, w))
h = min(self.height - y, max(1, h))
chunks, rest = divmod(w * h, _CHUNK)
self._writeblock(x, y, x + w - 1, y + h - 1, None)
written = 0
for iy in range(h):
for ix in range(w):
index = ix+iy*w - written
if index >=_CHUNK:
self._data(self._buf)
written += _CHUNK
index -= _CHUNK
c = bitbuff.pixel(ix,iy)
self._buf[index*2] = self._colormap[c*2]
self._buf[index*2+1] = self._colormap[c*2+1]
rest = w*h - written
if rest != 0:
mv = memoryview(self._buf)
self._data(mv[:rest*2])
def chars(self, str, x, y):
str_w = self._font.get_width(str)
if str_w == 0:
return x + 0
div, rem = divmod(self._font.height(),8)
nbytes = div+1 if rem else div
buf = bytearray(str_w * nbytes)
pos = 0
for ch in str:
glyph, char_w = self._font.get_ch(ch)
for row in range(nbytes):
index = row*str_w + pos
for i in range(char_w):
buf[index+i] = glyph[nbytes*i+row]
pos += char_w
fb = framebuf.FrameBuffer(buf,str_w, self._font.height(), framebuf.MONO_VLSB)
self.blit(fb,x,y,str_w,self._font.height())
return x+str_w
def scroll(self, dy):
self._scroll = (self._scroll + dy) % self.height
#self._write(_VSCRSADD, ustruct.pack(">H", self._scroll))
def next_line(self, cury, char_h):
global scrolling
if not self.scrolling:
res = cury + char_h
self.scrolling = (res >= self.height)
if self.scrolling:
self.scroll(char_h)
res = (self.height - char_h + self._scroll)%self.height
self.fill_rectangle(0, res, self.width, self._font.height())
return res
def write(self, text): #does character wrap, compatible with stream output
curx = self._x; cury = self._y
char_h = self._font.height()
width = 0
written = 0
for pos, ch in enumerate(text):
if ch == '\n':
if pos>0:
self.chars(text[written:pos],curx,cury)
curx = 0; written = pos+1; width = 0
cury = self.next_line(cury,char_h)
else:
char_w = self._font.get_width(ch)
if curx + width + char_w >= self.width:
self.chars(text[written:pos], curx,cury)
curx = 0 ; written = pos; width = char_h
cury = self.next_line(cury,char_h)
else:
width += char_w
if written<len(text):
curx = self.chars(text[written:], curx,cury)
self._x = curx; self._y = cury
def print(self, text): #does word wrap, leaves self._x unchanged
cury = self._y; curx = self._x
char_h = self._font.height()
char_w = self._font.max_width()
lines = text.split('\n')
for line in lines:
words = line.split(' ')
for word in words:
if curx + self._font.get_width(word) >= self.width:
curx = self._x; cury = self.next_line(cury,char_h)
while self._font.get_width(word) > self.width:
self.chars(word[:self.width//char_w],curx,cury)
word = word[self.width//char_w:]
cury = self.next_line(cury,char_h)
if len(word)>0:
curx = self.chars(word+' ', curx,cury)
curx = self._x; cury = self.next_line(cury,char_h)
self._y = cury
复制代码
最后是测试代码:
import time
from easydisplay import EasyDisplay
from machine import Pin,SPI
from ILI9341 import ILI9341
spi = SPI(1, baudrate=40000000, phase=0, polarity=0, sck=Pin(12), mosi=Pin(21))
lcd = ILI9341(spi, cs=Pin(14), dc=Pin(13), rst=Pin(0), w=320, h=240, r=2)
ed = EasyDisplay(lcd, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=True)
ed.text("你好,世界!\nHello World!\nこんにちは、世界!", 0, 0)
复制代码
import time
from easydisplay import EasyDisplay
from machine import Pin,SPI
from ILI9341 import ILI9341
spi = SPI(1, baudrate=40000000, phase=0, polarity=0, sck=Pin(12), mosi=Pin(21))
lcd = ILI9341(spi, cs=Pin(14), dc=Pin(13), rst=Pin(0), w=320, h=240, r=2)
ed = EasyDisplay(lcd, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=False,auto_wrap=True)
ed.clear()
ed.text("行空板K10简介", 40, 30,size=24,color=0x7E0)
text_k10 = "行空板K10是一款专为快速体验物联网和学习人工智能而设计的开发学习板,100%采用国产芯片,知识产权自主可控,符合信息科技课程中编程学习、物联网及人工智能等教学需求。该板集成2.8寸LCD彩屏、WiFi蓝牙、摄像头、麦克风、扬声器、RGB指示灯、多种传感器及丰富的扩展接口。凭借高度集成的板载资源,教学过程中无需额外连接其他设备,便可轻松实现传感器控制、物联网应用以及人脸识别、语音识别、语音合成等AI人工智能项目。"
ed.text(text_k10, 0, 80,0x0000FF,None,16)
复制代码
import time
from easydisplay import EasyDisplay
from machine import Pin,SPI
from ILI9341 import ILI9341
spi = SPI(1, baudrate=40000000, phase=0, polarity=0, sck=Pin(12), mosi=Pin(21))
lcd = ILI9341(spi, cs=Pin(14), dc=Pin(13), rst=Pin(0), w=320, h=240, r=2)
ed = EasyDisplay(lcd, "RGB565", font="/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=False,auto_wrap=True)
ed.clear()
ed.text("PY学习笔记简介", 40, 30,size=24,color=0x7E0)
text_pyxxbj = "微信公众号PY学习笔记:\n\n专注于python和Micropython的学习与分享。\n\n敬请关注!"
ed.text(text_pyxxbj, 0, 80,0x0000FF,None,16)
复制代码
效果: