我总是喜欢在网上买,最有性价比,最便宜的屏幕。
然而每次找驱动都要很多时间,micropython使用者较少,一些驱动根本找不到,而且换屏幕都要换驱动,接口不同,改代码也是非常恶心,就算找到了,一些驱动速度非常慢,基本也就是看个亮。
我打算自己写一个驱动看看。
当我写到第二个的时候,我发现所有屏幕操作逻辑都是相同的。和过去学系统编程、网络编程一样,这些操作是有规范的,只需要重写_init即可
当我写到第三个的时候发现,大部分屏幕,都不需要校准参数,所以_init也不需要重写,而且大部分商家发送资料根本无法对应屏幕,所谓校准参数有多大用,也是存疑。所以我写了这个通用驱动。
当然我只学了一两个星期,也没有找到合适的教程,结论都是写代码过程中的归纳总结,所以我的结论肯定有大量的错误,清楚来龙去脉即可,不要相信,请指正。
# SPI
spi = SPI(
1,
baudrate=100_000_000,
polarity=0,
phase=0,
sck=12,
mosi=13,
miso=None, # 10
)
# 在lib目录中找到lcd.py
# 大部分屏幕都可以使用lcd来初始化
# 无法工作的屏幕可以在lib目录中寻找 "驱动名称.py",里面重写了lcd.py中的_init函数
# 如果无法找到,只需要将商家提供的初始化代码发送给gpt,让它重写_init方法即可
# 已知需要校准的屏幕:
# gca9a01,需要使用商家提供的校准参数,否则工作怪异
# gc9107,需要使用商家提供的校准参数,否则有点细微问题
st = lcd.LCD(
spi, # spi 对象
cs=47, # cs 引脚,不使用传入None
dc=21, # dc 引脚
rst = 14, # 重启引脚,软件初始传入None
bl=48, # 背光引脚,不使用传入None
size=lcd.LCD.Size.st7796, # 驱动原始分辨率,用于被裁剪的屏幕,在4个旋转角度,自动生成对应偏移
# 在 LCD.Size 中定义了一些已知的分辨率,参数格式 st7796 = (320, 480)
旋转=0, # 4角度旋转 0~3
color_bit=16, # 16 18 24,实际上16bit,和其他看不出区别
逆CS=False, # True = 高电平使能,我没到遇到高电平使能的,主要考虑引脚复用
像素缺失=lcd.LCD.像素缺失.st7789_1_14, # 实际屏幕在0旋转时,4个角度丢失的像素
# 可用st._test_像素裁剪()查看丢失的像素,格式(0,0,0,0)代表 上 下 左 右
)._init(反色=1,左右镜像= 1,rgb=1) # 考虑到初始化需要加延迟,所以单独一个函数方便使用 asyncio
# 黑白颜色反了用 反色
# 左右倒了用 左右镜像
# 红蓝颜色反了用 rgb
# 实际上初始化大概率只需这三个参数: spi,dc,驱动分辨率
st = lcd.LCD(
spi,
dc=21,
size=lcd.LCD.Size.st7796
)._init()st.txt(
字符串="阿斯顿asd", # 要显示什么字符串
x=20, # 宽起点
y=20, # 高起点
size=32, # 字体大小
字体色=st.color.白, # 字体色
背景色=st.color.黑, # 背景色
缓存=True, # 本次显示的字符中,有未被缓存的,是否加入缓存。有缓存是包用的,不受影响。
)
################使用取模软件时,字体右旋转90度###############################
# 常用软件导入
# lcd里面有一个_char属性用于保存点阵字符,里面为了方便调试加了几个字符,可以查看加入点阵字符的方式
st._char[32] = {}
st._char[32]["阿"] = bytes([0x00,0x00...])
# 从字库中加载
# 字库生成软件在这里 https://github.com/lrlbh/lcd字库
# 某天不小心把加载完整字库的函数删了,现在只能用选择性加载
# 方式1: def 字符中内置了16,24,32,40,48,56,64,72的ascii和几个常用字符,你可以继续添加
st.def_字符.all = "的身份人格完善的法律就能很快就"
st.load_bmf("/字库.bmf")
# 方式2:
st.load_bmf("/字库.bmf",{
16:"caxzsdgfsdfgDADSZF撒法帝国",
32:"zxcgvsedfg的说法是德国"
})# 清屏,这个屏幕一次清除的,如果内存小,里面发送数据循环一下
st.fill(st.color.白)
# 颜色
st.color.XX # 可以访问内置颜色,不同实例指向不同bit,多屏幕多实例情况下,可以放心使用
st.color_fn(255,255,255) # RGB顺序创建一个颜色,不同bit指向不同函数,修改bit也没事
# 同一款驱动基本只有一个分辨率,然而有多种分辨率屏幕,实际上有些没有使用,遇到这种情况需要计算
# 运行后显示一个图案,四周有边框,白色表示左上方
# 通过初始化时的参数 像素缺失= (0,0,0,0) 丢弃四边的像素,知道边框出现即可
# 调整旋转0即可,其他3个角度会自动适配,前提是初始化时驱动分辨率参数 size=(240,320) 传入正确
st._test_像素裁剪()
# 旋转屏幕,查看偏移有没有问题
st._test()
# 4角度旋转测试
while True:
for i in range(0, 4):
st = lcd.LCD(
spi,
cs=47,
dc=21,
rst=14,
bl=48,
size=驱动,
旋转=i,
color_bit=16,
逆CS=False,
像素缺失=像素补偿,
)._init()#反色=True, RGB=True)
# udp.send(f"-----------------旋转{i}---------------------")
# udp.send(f"驱动分辨率w-h{st._width_驱动, st._height_驱动}")
# udp.send(f"逻辑分辨率w-h{st._width, st._height}")
# udp.send("----------------------------------------------")
st._test()
time.sleep(1)# 波形测试
st.fill(st.color.黑)
bx = st.new_波形(
w起点=20, # 宽起点
h起点=20, # 高起点
size_w=200, # 波形区域宽
size_h=50, # 波形区域高
波形像素=[3, 3, 3], # 波形性有多粗,列表是因为可以传入多个通道数据
多少格=998, # 加了网格不好看,已废弃,没用
data_min=[0, 0, 0], # 每个通道,显示范围
data_max=[33, 66, 99],# 每个通道,显示范围
波形色=[st.color.红, st.color.绿, st.color.蓝], # 每个通道的颜色
背景色=st.color.白, # 波形区域背景色
)
bx1 = st.new_波形(
w起点=20,
h起点=90,
size_w=200,
size_h=100,
波形像素=[6, 6, 6],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
bx2 = st.new_波形(
w起点=20,
h起点=210,
size_w=200,
size_h=50,
波形像素=[6, 6, 6],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
bx3 = st.new_波形(
w起点=20,
h起点=280,
size_w=200,
size_h=40,
波形像素=[4, 4, 4],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
t1, t2, t3 = 0, 0, 0
tt1, tt2, tt3 = 1, 1, 1
while True:
bx.append_data([t1, t2, t3]) # 添加一个点数据
bx.更新() # 里面有一份环形内存,保存了完整波形数据,不用每次都更新,有空了更新一下就行
bx1.append_data([t1, t2, t3])
bx1.更新()
bx2.append_data([t1, t2, t3])
bx2.更新()
bx3.append_data([t1, t2, t3])
bx3.更新()
# udp.send(t1)
# udp.send(t2)
# udp.send(t3)
if t1 >= 33:
tt1 = -1
if t2 >= 66:
tt2 = -1
if t3 >= 99:
tt3 = -1
if t1 <= 0:
tt1 = 1
if t2 <= 0:
tt2 = 1
if t3 <= 0:
tt3 = 1
t1 += tt1
t2 += tt2
t3 += tt3# 也许可以在 示例.py 中找到最新的
import time
from machine import SPI
try:
import lcd
except ImportError:
from lib import lcd
def get_st(旋转):
# 老板子引脚
spi = SPI(
1,
baudrate=100_000_000,
polarity=0,
phase=0,
sck=12,
mosi=13,
miso=None, # 10
)
# lcd.def_字符.all = "的身份人格完善的法律就能很快就"
return lcd.LCD(
spi,
cs=47,
dc=21,
# rst=None,
rst=14,
bl=48,
size=lcd.LCD.Size.st7789,
旋转=旋转,
color_bit=16,
逆CS=False,
像素缺失=(0, 0, 0, 0),
)._init(反色=1, 左右镜像=1, rgb=1) # .load_bmf("/字库.bmf")
# st.fill(st.color.白)
# st.load_bmf(
# "/字库.bmf",
# {
# 16: "caxzsdgfsdfgDADSZF撒法帝国",
# 32: "zxcgvsedfg的说法是德国",
# },
# )
# 显示字符
st = get_st(3)
st.txt(
字符串="阿斯顿asd",
x=20,
y=20,
size=32,
字体色=st.color.白,
背景色=st.color.黑,
缓存=True,
)
time.sleep(4)
# 丢弃像素
# st._test_像素裁剪()
# while True:
# pass
# 4角度旋转
for i in range(0, 4):
st = get_st(i)
st._test()
time.sleep(3)
# time.sleep(1000000000)
# 波形
st = get_st(3)
bx = st.new_波形(
w起点=20,
h起点=20,
size_w=200,
size_h=50,
波形像素=[3, 3, 3],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
bx1 = st.new_波形(
w起点=20,
h起点=90,
size_w=200,
size_h=100,
波形像素=[6, 6, 6],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
bx2 = st.new_波形(
w起点=20,
h起点=210,
size_w=200,
size_h=50,
波形像素=[6, 6, 6],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
bx3 = st.new_波形(
w起点=20,
h起点=280,
size_w=200,
size_h=40,
波形像素=[4, 4, 4],
多少格=998,
data_min=[0, 0, 0],
data_max=[33, 66, 99],
波形色=[st.color.红, st.color.绿, st.color.蓝],
背景色=st.color.白,
)
t1, t2, t3 = 0, 0, 0
tt1, tt2, tt3 = 1, 1, 1
while True:
bx.append_data([t1, t2, t3])
bx.更新()
bx1.append_data([t1, t2, t3])
bx1.更新()
bx2.append_data([t1, t2, t3])
bx2.更新()
bx3.append_data([t1, t2, t3])
bx3.更新()
# udp.send(t1)
# udp.send(t2)
# udp.send(t3)
if t1 >= 33:
tt1 = -1
if t2 >= 66:
tt2 = -1
if t3 >= 99:
tt3 = -1
if t1 <= 0:
tt1 = 1
if t2 <= 0:
tt2 = 1
if t3 <= 0:
tt3 = 1
t1 += tt1
t2 += tt2
t3 += tt3-
有一个硬件波形,试试看
-
快速一次性加载所有字体的函数,很早就被误删了,需要重新实现,不过先得重新查看一下自制bmf的格式
-
3线spi,还可以省略 DC 引脚
-
使用新的刷新逻辑,先上后下,在左在右
- 当初为了实现列刷新,我简单看了一下4个角度显示内容,发现只要两个简单操作就可以
- 内容右旋90度然后补偿一个逻辑高度就可以
- 内容左旋转90度,补偿一个逻辑宽度就可以
- 当时随便选了一个补偿高度的,随着时间发展,我发现了一个很大弊端,使用gpt实现代码时,它完全无法理解这种刷新逻辑 先(下 -- >上) 后(左-->右),或许是我语言表达能力有问题,我总是得多次纠正它,总之与gpt冲突的代码无疑是非常愚蠢的代码
-
创建一行显示区域,记录里面的内容,更新时只更新变化的字符。此区域还可以分段,每段自增坐标,自动产长度,每段可以分上中下,显示不同大小字符。-->部分尝试了下,目前尝试部分,还可以加一个更新时使用默认起点用第一个 : 记录 -
软件波形生成,多像素在往上下增长,目前是多像素单边增长,另外记录上一次波形坐标当变化激烈时也使用一个像素连接,算了随便搞下,有别的事了,上下增长没写,简单连接了下 -
使用示例 -
TE引脚播放视频测试 -
用杜邦线连接,cs使能时,dc切换时,需要加点延迟 -
减少颜色 -
预设色彩为类属性,多屏幕使用不同bit会冲突 -
某些数据,会占用大量内存,多个实例总体维护一份即可 -
单个字符缓存大小,由加载字符时最大尺寸字符决定,算了有时也会手动添加几个 -
已经是列刷新了,字符显示可以加快,不使用多次 _set_window -
波形更新,添加min数据、多波形单独像素 和 自适应模式,没有管自适应,每次变化都要生成1帧数据应该快不了 -
4线SPI下,理论上非常简单就可以节省引脚,测试无误后支持它1个SPI设备,3个引脚,SCL,SDA,DC2个SPI设备,4个引脚,SCL,SDA,DC,CS,CS反向- 3个及以上SPI设备
- 似乎在很多地方,听说一种二进制 转 数据位的芯片,2CS挂4个设备,3个CS挂8个设备
- 上面方法还是需要大于一个引脚,猜测应该有1扩N的数字芯片,就是不知道是否可以白嫖
-
更多驱动-
以前购买过的屏幕 -
7789 240 * 320 240 * 280 240 * 240 135 * 240 170 * 320 -
GC9A01 240 * 240 -
7735 80 * 160 128 * 160 128 * 128 -
GC9107 128 * 128 -
ili9488 320*240 -
st7796 320* 240
-
# 刷新大量数据时 (ESP32S3)
# 推算实际为 30MHZ
# 示波器查看 40MHZ
# 网上搜索 80MHZ
# 官网某个文档看到,默认引脚80MHZ,非默认引脚40MHZ,未验证
# 问题1:少10Mhz问题,单独记录每一次刷新屏幕耗时,刷新460800字节,出现了2种数据 (已解决)
# 1、速度 95 ~ 105ms, 基本稳定在100已内,推算SPI为40MHZ完全符合
# 2、速度 160 ~ 180ms
# 我在开发时,使用的是网络更新,有一个单独的线程频繁发送网络请求,也许是网络线程打断了
# 由于SPI.write(),传入的是memoryview,所以忘记查看是否会是GC导致了
# 问题2:少40Mhz问题(已解决)
# 问题也许来自分频(?),不过我不知道那是什么东西,不过我大约听说过SPI频率和时钟树(?)有关
# 基于问题,推测SPI无法设置指定频率,只能选择几个固定频率,所以指定的频率未曾生效
# 解决,给一个 >= 最高频率的频率
# 10MHZ、20MHZ、26.66MHZ、40MHZ、80MHZ,随便指定了几个频率返回的频率,示波器只验证了80MHZ
# 似乎没规律,有兴趣在搜一下看看,实际原因
# 80MHZ写入上面同样的数据返回时间,推算SPI为80MHZ完全符合
# 1、速度 49ms最多,50ms次之
# 2、速度 119 ~ 121ms
# 使用问题已经完全解决,剩下两个小问题
# 1、SPI写入时,偶尔耗时会变多60~70MS
# 判断网络线程工作的原因
# 因为无论频率如何,平均时间总是多了20MS左右。
# 如果是GC数据量都翻倍了。
# 2、SPI频率只能使用一些固定值,是因为什么# 官方模组硬盘访问速度
0.8MiB~1MiB
# 非官方模组硬盘访问速度
1.1MiB~1.3MiB
# 在github上看到过,有人通过修改固件硬盘读取速度达到了6M还是8M去了,修改前速度和我基本一致
# WIFI速度 N年前拉宽带自带的路由器,距离几十厘米
# Wi-Fi 已连接: 192.168.1.11
# TCP 已连接
1.6155909MiB/s
# Wi-Fi已连接: 192.168.1.2
# UDP监听中...
2.8595062MiB/s
# 1472,UDP单包计时 最快325us
>>> 1_000_000 / 325 * 1472 / 1024 / 1024
4.319411057692308
>>> 1_000_000 / 325 * 1500 / 1024 / 1024
4.401573768028847
>>> 1_000_000 / 300 * 1500 / 1024 / 1024
4.76837158203125
>>> 1_000_000 / 300 * 1500 / 1024 / 1024 * 8
38.14697265625MPY: soft reboot
空循环1W次耗时: 24773
空循环2W次耗时: 49393
空循环4W次耗时: 99071
循环1W次空函数耗时: 88534
循环2W次空函数耗时: 177037
循环4W次空函数耗时: 353925
循环1W次复杂类空成员函数数耗时: 110882
循环2W次复杂类空成员函数数耗时: 222157
循环4W次复杂类空成员函数数耗时: 444956
循环1W次简单类空成员函数耗时: 108202
循环2W次简单类空成员函数耗时: 216783
循环4W次简单类空成员函数耗时: 433752
循环1W次全局变量耗时: 37346
循环2W次全局变量耗时: 74733
循环4W次全局变量耗时: 149199
循环1W次简单类空成员变量耗时: 40254
循环2W次简单类空成员变量耗时: 80546
循环4W次简单类空成员变量耗时: 161271
循环1W次复杂类空成员变量耗时: 40491
循环2W次复杂类空成员变量耗时: 80717
循环4W次复杂类空成员变量耗时: 161248
循环1W次if耗时: 47888
循环2W次if耗时: 96006
循环4W次if耗时: 240192