diff --git a/config.py b/config.py index 4e43501..7ae042c 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,49 @@ from micropython import const + +from machine import Pin, SPI +from time import sleep_ms + +# ----------------------------epaper配置------------------------------------------------- + +# SPI引脚配置 +sck = Pin(47) # SCK pin47 +miso = Pin(46) # MISO pin46 +mosi = Pin(21) # SDI/MOSI pin21 + +# 控制引脚配置 +dc = Pin(40) # D/C pin40 +cs = Pin(45) # CS pin45 +rst = Pin(41) # RES pin41 +busy = Pin(42) # BUSY pin42 + +# 按钮引脚配置 +btn1 = Pin(46, Pin.IN, Pin.PULL_UP) # 按钮1连接到引脚46 +btn2 = Pin(20, Pin.IN, Pin.PULL_UP) # 按钮2连接到引脚20 +btn3 = Pin(12, Pin.IN, Pin.PULL_UP) # 按钮3连接到引脚12 +btn4 = Pin(11, Pin.IN, Pin.PULL_UP) # 按钮4连接到引脚11 + +# 蜂鸣器引脚配置 +buzzer_pin = 14 # 蜂鸣器连接到引脚14 + +# epaper屏幕尺寸 +WIDTH = 400 +HEIGHT = 300 + +# 初始化 SPI2(HSPI/VSPI 视固件而定) +spi = SPI(2, baudrate=2_000_000, polarity=0, phase=0, + sck=sck, miso=miso, mosi=mosi) + +# 如果你板子上真有单独的 EPD 电源控制 FET,就按实际 IO 改; +# 若只是直接 3.3V 供电,可以把下面这一段去掉。 +epd_power = Pin(2, Pin.OUT) +epd_power.on() +sleep_ms(10) + +# ----------------------------epaper配置------------------------------------------------- + + + class BoardConfig: def __init__(self, name): self.name = name diff --git a/epaper_diver/epaper4in2.py b/epaper_diver/epaper4in2.py new file mode 100644 index 0000000..675b324 --- /dev/null +++ b/epaper_diver/epaper4in2.py @@ -0,0 +1,312 @@ +""" +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 \ No newline at end of file