Files
V2_micropython/epaper_diver/epaper4in2.py
jeremygan2021 efbe08f2cd
All checks were successful
Deploy WebSocket Server / deploy (push) Successful in 3s
action
2026-03-04 21:06:56 +08:00

312 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
MicroPython Good Display GDEQ042T81 (GDEY042T81)
Based on MicroPython Waveshare 4.2" Black/White GDEW042T2 e-paper display driver
https://github.com/mcauser/micropython-waveshare-epaper
licensed under the MIT License
Copyright (c) 2017 Waveshare
Copyright (c) 2018 Mike Causer
"""
"""
MicroPython Good Display GDEQ042T81 (GDEY042T81) e-paper display driver
MIT License
Copyright (c) 2024 Martin Maly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from micropython import const
from time import sleep_ms
try:
from buzzer import system_buzzer
except ImportError:
system_buzzer = None
# Display resolution
EPD_WIDTH = const(400)
EPD_HEIGHT = const(300)
BUSY = const(0) # 0=busy, 1=idle
class EPD:
def __init__(self, spi, cs, dc, rst, busy):
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
self.busy = busy
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.busy.init(self.busy.IN)
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
self.powered = False
self.init_done = False
self.hibernate = True
self.use_fast_update = True
# 添加刷新计数器,用于控制全屏刷新频率
self.refresh_count = 0
# 每N次局部刷新后执行一次全屏刷新防止残影积累
self.partial_refresh_limit = 5
# 强制全屏刷新标志
self.force_full_refresh = False
def _command(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 _ndata(self, data):
self._data(bytearray([data]))
def pwr_on(self):
if self.powered == False:
self._command(0x22, b'\xe0')
self._command(0x20)
self.wait_until_idle()
self.powered = True
def pwr_off(self):
if self.powered == True:
self._command(0x22, b'\x83')
self._command(0x20)
self.wait_until_idle()
self.powered = False
#set partial
def set_partial(self, x, y, w, h):
self._command(0x11,b'\x03')
self._command(0x44)
self._ndata(x // 8)
self._ndata((x + w - 1) // 8)
self._command(0x45)
self._ndata(y % 256)
self._ndata(y // 256)
self._ndata((y+h-1) % 256)
self._ndata((y+h-1) // 256)
self._command(0x4E)
self._ndata(x // 8)
self._command(0x4F)
self._ndata(y % 256)
self._ndata(y // 256)
def init(self):
if self.hibernate==True:
self.reset()
sleep_ms(100)
#self.wait_until_idle()
self._command(const(0x12)) #SWRESET
self.wait_until_idle()
# 优化驱动初始化参数确保与GDEY042T81规格匹配
self._command(0x01,b'\x2B\x01\x00') #MUX 设置
self._command(0x21,b'\x40\x00') # 显示更新控制
self._command(0x3C,b'\x05') # 边界波形控制,减少残影
self._command(0x18,b'\x80') # 读取内部温度传感器
self._command(0x0C,b'\x8B\x00\x00') # 设置开始和结束阶段,优化刷新
self.set_partial(0, 0, self.width, self.height)
self.init_done = True
def wait_until_idle(self):
while self.busy.value() == BUSY:
sleep_ms(100)
print("等待墨水屏空闲")
def reset(self):
self.rst(0)
sleep_ms(200)
self.rst(1)
sleep_ms(200)
def update_full(self):
#update Full
print("执行全屏更新")
self._command(0x21,b'\x40\x00')
if self.use_fast_update == False:
self._command(0x22,b'\xf7')
else:
self._command(0x1A, b'\x64') # 快速刷新设置
self._command(0x22,b'\xd7')
self._command(0x20)
self.wait_until_idle()
print("更新完成", self.busy.value())
# 添加专门的全屏刷新方法,用于清除残影
def clear_screen(self, double_refresh=True):
"""执行全屏刷新以清除残影
参数:
double_refresh: 是否执行两次刷新以彻底清除残影默认为True
"""
if double_refresh:
print("执行双次全屏刷新以彻底清除残影")
else:
print("执行单次全屏刷新以清除残影")
# 创建全白缓冲区
white_buffer = bytearray(self.width * self.height // 8)
for i in range(len(white_buffer)):
white_buffer[i] = 0xFF # 全白
# 先写入全白数据
self.set_partial(0, 0, self.width, self.height)
self.write_image(0x24, white_buffer, True, True)
# 执行第一次全屏刷新
self._command(0x21,b'\x40\x00')
self._command(0x22,b'\xf7') # 使用完整刷新模式
self._command(0x20)
self.wait_until_idle()
# 如果启用双次刷新,再执行一次
if double_refresh:
print("执行第二次全屏刷新")
self._command(0x21,b'\x40\x00')
self._command(0x22,b'\xf7') # 使用完整刷新模式
self._command(0x20)
self.wait_until_idle()
# 重置刷新计数器
self.refresh_count = 0
self.force_full_refresh = False
print("全屏刷新完成")
def write_image(self, command, bitmap, mirror_x, mirror_y):
sleep_ms(1)
h = self.height
w = self.width
bpl = w // 8 # bytes per line
self._command(command)
for i in range(0, h):
for j in range(0, bpl):
idx = ((bpl-j-1) if mirror_x else j) + ((h-i-1) if mirror_y else i) * bpl
self._ndata(bitmap[idx])
def write_value(self, command, value):
sleep_ms(1)
h = self.height
w = self.width
bpl = w // 8 # bytes per line
self._command(command)
for i in range(0, h):
for j in range(0, bpl):
self._ndata(value)
# 修改显示方法,添加刷新控制逻辑
def display_frame(self, frame_buffer, partial=False, x=0, y=0, w=None, h=None, global_refresh=False):
"""显示帧缓冲区内容
参数:
frame_buffer: 要显示的帧缓冲区数据
partial: 是否使用局部刷新模式默认为False
x: 局部刷新的起始X坐标默认为0
y: 局部刷新的起始Y坐标默认为0
w: 局部刷新的宽度,默认为全屏宽度
h: 局部刷新的高度,默认为全屏高度
global_refresh: 是否使用全局刷新模式默认为False
"""
print("显示帧缓冲区")
if self.init_done==False:
self.init()
# 如果未指定宽高,则使用全屏
if w is None:
w = self.width
if h is None:
h = self.height
# 检查是否需要强制全屏刷新
need_clear_screen = self.force_full_refresh or self.refresh_count >= self.partial_refresh_limit
# 如果使用全局刷新模式,则设置全屏刷新区域
if global_refresh:
self.set_partial(0, 0, self.width, self.height)
elif need_clear_screen:
self.clear_screen()
# 设置局部刷新区域
self.set_partial(x, y, w, h)
else:
# 设置局部刷新区域
self.set_partial(x, y, w, h)
# 写入图像数据
self.write_image(0x24, frame_buffer, True, True)
# 播放等待音效 (异步)
if system_buzzer:
system_buzzer.play_process_async()
# 执行刷新
if global_refresh:
# 全局刷新模式
print("执行全局刷新模式")
self._command(0x21,b'\x40\x00')
self._command(0x22,b'\xf7') # 使用完整刷新模式
self._command(0x20)
self.wait_until_idle()
self.refresh_count = 0
print("全局刷新完成,重置计数器")
elif need_clear_screen:
# 已经通过clear_screen()执行了刷新,这里不需要再次刷新
print("已通过clear_screen完成全屏刷新跳过重复刷新")
self.refresh_count = 0
elif partial and not self.force_full_refresh and self.refresh_count < self.partial_refresh_limit:
# 局部刷新模式
print("执行局部刷新模式")
self._command(0x21,b'\x40\x00')
self._command(0x1A, b'\x64') # 快速刷新设置
self._command(0x22,b'\xd7') # 局部刷新命令
self._command(0x20)
self.wait_until_idle()
self.refresh_count += 1
print(f"局部刷新完成,刷新计数: {self.refresh_count}/{self.partial_refresh_limit}")
else:
# 全屏刷新模式
print("执行全屏刷新模式")
self.update_full()
self.refresh_count = 0
print("全屏刷新完成,重置计数器")
# 添加强制全屏刷新的方法
def force_refresh(self):
"""强制执行下一次全屏刷新,清除所有残影"""
self.force_full_refresh = True
print("已设置强制全屏刷新标志")
# to wake call reset() or init()
def sleep(self):
self.pwr_off()
self._command(0x10, b'\x01')
self.init_done = False
self.hibernate = True