# lcd **Repository Path**: lrlbh/lcd ## Basic Information - **Project Name**: lcd - **Description**: micropython的lcd通用驱动 - **Primary Language**: Unknown - **License**: MPL-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-05 - **Last Updated**: 2025-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: MicroPython, TFT, lcd, st7796, ST7789 ## README # 来源 我总是喜欢在网上买,最有性价比,最便宜的屏幕。 然而每次找驱动都要很多时间,micropython使用者较少,一些驱动根本找不到,而且换屏幕都要换驱动,接口不同,改代码也是非常恶心,就算找到了,一些驱动速度非常慢,基本也就是看个亮。 我打算自己写一个驱动看看。 当我写到第二个的时候,我发现所有屏幕操作逻辑都是相同的。和过去学系统编程、网络编程一样,这些操作是有规范的,只需要重写_init即可 当我写到第三个的时候发现,大部分屏幕,都不需要校准参数,所以_init也不需要重写,而且大部分商家发送资料根本无法对应屏幕,所谓校准参数有多大用,也是存疑。所以我写了这个通用驱动。 当然我只学了一两个星期,也没有找到合适的教程,结论都是写代码过程中的归纳总结,所以我的结论肯定有大量的错误,清楚来龙去脉即可,不要相信,请指正。 # 使用示例 ## 初始化 ~~~python # 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() ~~~ ## 字符显示 ~~~python 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的说法是德国" }) ~~~ ## 小工具 ~~~python # 清屏,这个屏幕一次清除的,如果内存小,里面发送数据循环一下 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) ~~~ ## 波形显示 ~~~python # 波形测试 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 ~~~ ## 可直接运行的示例 ~~~python # 也许可以在 示例.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 ~~~ # 可以更新功能 - 有一个硬件波形,试试看 - 使用新的刷新逻辑,先上后下,在左在右 - 当初为了实现列刷新,我简单看了一下4个角度显示内容,发现只要两个简单操作就可以 - 内容右旋90度然后补偿一个逻辑高度就可以 - 内容左旋转90度,补偿一个逻辑宽度就可以 - 当时随便选了一个补偿高度的,随着时间发展,我发现了一个很大弊端,使用gpt实现代码时,它完全无法理解这种刷新逻辑 先(下 -- >上) 后(左-->右),或许是我语言表达能力有问题,我总是得多次纠正它,总之与gpt冲突的代码无疑是非常愚蠢的代码 - 快速一次性加载所有字体的函数,很早就被误删了,需要重新实现,不过先得重新查看一下自制bmf的格式 - 3线spi,还可以省略 DC 引脚 - ~~使用示例~~ - ~~TE引脚播放视频测试~~ - ~~用杜邦线连接,cs使能时,dc切换时,需要加点延迟~~ - ~~减少颜色~~ - ~~预设色彩为类属性,多屏幕使用不同bit会冲突~~ - ~~某些数据,会占用大量内存,多个实例总体维护一份即可~~ - ~~单个字符缓存大小,由加载字符时最大尺寸字符决定,算了有时也会手动添加几个~~ - ~~已经是列刷新了,字符显示可以加快,不使用多次 _set_window~~ - ~~波形更新,添加min数据、多波形单独像素 和 自适应模式,没有管自适应,每次变化都要生成1帧数据应该快不了~~ - ~~4线SPI下,理论上非常简单就可以节省引脚,测试无误后支持它~~ - ~~1个SPI设备,3个引脚,SCL,SDA,DC~~ - ~~2个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~~ # 速度问题记录 (测试ESP32-S3,SPI=80M) ~~~python # 刷新大量数据时 (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频率只能使用一些固定值,是因为什么 ~~~ # ESP32S3 N16R8模组 硬盘和网络访问速度 ~~~python # 官方模组硬盘访问速度 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.14697265625 ~~~ # 几个速度记录 ~~~shell MPY: 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 ~~~