From 9e9f076d1b68166470540436d5dc4203bbcfafcd Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Mon, 13 May 2024 10:18:32 +0100 Subject: [PATCH] Add gc9a01 driver. --- drivers/epaper/epaper2in7_fb.py | 212 ++++++++++++++-------------- drivers/epaper/epd29.py | 87 ++++++------ drivers/epaper/pico_epaper_42.py | 6 +- drivers/gc9a01/gc9a01.py | 214 +++++++++++++++++++++++++++++ drivers/ili93xx/ili9341.py | 90 ++++++------ drivers/ili94xx/ili9486.py | 15 +- drivers/ssd1306/ssd1306.py | 8 +- drivers/ssd1351/ssd1351_generic.py | 4 +- drivers/st7789/st7789_4bit.py | 11 +- 9 files changed, 443 insertions(+), 204 deletions(-) create mode 100644 drivers/gc9a01/gc9a01.py diff --git a/drivers/epaper/epaper2in7_fb.py b/drivers/epaper/epaper2in7_fb.py index 69b7484..7cf40dd 100644 --- a/drivers/epaper/epaper2in7_fb.py +++ b/drivers/epaper/epaper2in7_fb.py @@ -3,7 +3,7 @@ # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # Optimisations to reduce allocations and RAM use. -# Copyright (c) Peter Hinch 2020 +# Copyright (c) Peter Hinch 2020-2023 # Released under the MIT license see LICENSE # Based on the following sources: @@ -13,8 +13,18 @@ # https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py ("official") import framebuf -import uasyncio as asyncio +import asyncio from time import sleep_ms, ticks_ms, ticks_us, ticks_diff +from drivers.boolpalette import BoolPalette + + +def asyncio_running(): + try: + _ = asyncio.current_task() + except: + return False + return True + class EPD(framebuf.FrameBuffer): # A monochrome approach should be used for coding this. The rgb method ensures @@ -23,6 +33,7 @@ class EPD(framebuf.FrameBuffer): def rgb(r, g, b): return int((r > 127) or (g > 127) or (b > 127)) + # Discard asyn: autodetect def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False): self._spi = spi self._cs = cs # Pins @@ -30,17 +41,18 @@ class EPD(framebuf.FrameBuffer): self._rst = rst self._busy = busy self._lsc = landscape - self._asyn = asyn self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1). - self._updated = asyncio.Event() + self.updated = asyncio.Event() + self.complete = asyncio.Event() # Dimensions in pixels. Waveshare code is portrait mode. # Public bound variables required by nanogui. - self.width = 264 if landscape else 176 + self.width = 264 if landscape else 176 self.height = 176 if landscape else 264 self.demo_mode = False # Special mode enables demos to run self._buffer = bytearray(self.height * self.width // 8) self._mvb = memoryview(self._buffer) mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB + self.palette = BoolPalette(mode) super().__init__(self._buffer, self.width, self.height, mode) self.init() @@ -65,101 +77,97 @@ class EPD(framebuf.FrameBuffer): sleep_ms(200) # Initialisation cmd = self._command - cmd(b'\x01', b'\x03\x00\x2B\x2B\x09') # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR - cmd(b'\x06', b'\x07\x07\x17') # BOOSTER_SOFT_START - cmd(b'\xf8', b'\x60\xA5') # POWER_OPTIMIZATION - cmd(b'\xf8', b'\x89\xA5') - cmd(b'\xf8', b'\x90\x00') - cmd(b'\xf8', b'\x93\x2A') - cmd(b'\xf8', b'\xA0\xA5') - cmd(b'\xf8', b'\xA1\x00') - cmd(b'\xf8', b'\x73\x41') - cmd(b'\x16', b'\x00') # PARTIAL_DISPLAY_REFRESH - cmd(b'\x04') # POWER_ON + cmd( + b"\x01", b"\x03\x00\x2B\x2B\x09" + ) # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR + cmd(b"\x06", b"\x07\x07\x17") # BOOSTER_SOFT_START + cmd(b"\xf8", b"\x60\xA5") # POWER_OPTIMIZATION + cmd(b"\xf8", b"\x89\xA5") + cmd(b"\xf8", b"\x90\x00") + cmd(b"\xf8", b"\x93\x2A") + cmd(b"\xf8", b"\xA0\xA5") + cmd(b"\xf8", b"\xA1\x00") + cmd(b"\xf8", b"\x73\x41") + cmd(b"\x16", b"\x00") # PARTIAL_DISPLAY_REFRESH + cmd(b"\x04") # POWER_ON self.wait_until_ready() - cmd(b'\x00', b'\xAF') # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f - cmd(b'\x30', b'\x3A') # PLL_CONTROL: 3A 100HZ, 29 150Hz, 39 200HZ 31 171HZ - cmd(b'\x50', b'\x57') # Vcom and data interval setting (PGH) - cmd(b'\x82', b'\x12') # VCM_DC_SETTING_REGISTER + cmd(b"\x00", b"\xAF") # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f + cmd(b"\x30", b"\x3A") # PLL_CONTROL: 3A 100HZ, 29 150Hz, 39 200HZ 31 171HZ + cmd(b"\x50", b"\x57") # Vcom and data interval setting (PGH) + cmd(b"\x82", b"\x12") # VCM_DC_SETTING_REGISTER sleep_ms(2) # No delay in official code # Set LUT. Local bytes objects reduce RAM usage. # Values used by mcauser - #lut_vcom_dc =\ - #b'\x00\x00\x00\x0F\x0F\x00\x00\x05\x00\x32\x32\x00\x00\x02\x00'\ - #b'\x0F\x0F\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - #b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - #lut_ww =\ - #b'\x50\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\xA0\x0F\x0F'\ - #b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - #b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R21H - #lut_bb =\ - #b'\xA0\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\x50\x0F\x0F'\ - #b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - #b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R24H b + # lut_vcom_dc =\ + # b'\x00\x00\x00\x0F\x0F\x00\x00\x05\x00\x32\x32\x00\x00\x02\x00'\ + # b'\x0F\x0F\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + # lut_ww =\ + # b'\x50\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\xA0\x0F\x0F'\ + # b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R21H + # lut_bb =\ + # b'\xA0\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\x50\x0F\x0F'\ + # b'\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R24H b # Values from official code: - lut_vcom_dc =\ - b'\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00'\ - b'\x14\x00\x00\x00\x01\x00\x12\x12\x00\x00\x01\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - lut_ww =\ - b'\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00'\ - b'\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - lut_bb =\ - b'\x80\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x80\x14\x00'\ - b'\x00\x00\x01\x50\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + lut_vcom_dc = ( + b"\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00" + b"\x14\x00\x00\x00\x01\x00\x12\x12\x00\x00\x01\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + lut_ww = ( + b"\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00" + b"\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + lut_bb = ( + b"\x80\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x80\x14\x00" + b"\x00\x00\x01\x50\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) # Both agree on this: lut_bw = lut_ww # R22H r lut_wb = lut_bb # R23H w - cmd(b'\x20', lut_vcom_dc) # LUT_FOR_VCOM vcom - cmd(b'\x21', lut_ww) # LUT_WHITE_TO_WHITE ww -- - cmd(b'\x22', lut_bw) # LUT_BLACK_TO_WHITE bw r - cmd(b'\x23', lut_bb) # LUT_WHITE_TO_BLACK wb w - cmd(b'\x24', lut_wb) # LUT_BLACK_TO_BLACK bb b - print('Init Done.') + cmd(b"\x20", lut_vcom_dc) # LUT_FOR_VCOM vcom + cmd(b"\x21", lut_ww) # LUT_WHITE_TO_WHITE ww -- + cmd(b"\x22", lut_bw) # LUT_BLACK_TO_WHITE bw r + cmd(b"\x23", lut_bb) # LUT_WHITE_TO_BLACK wb w + cmd(b"\x24", lut_wb) # LUT_BLACK_TO_BLACK bb b + print("Init Done.") def wait_until_ready(self): sleep_ms(50) t = ticks_ms() - while not self.ready(): + while not self.ready(): sleep_ms(100) dt = ticks_diff(ticks_ms(), t) - print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000)) - - async def wait(self): - await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready - while not self.ready(): - await asyncio.sleep_ms(100) - - # Pause until framebuf has been copied to device. - async def updated(self): - await self._updated.wait() + print("wait_until_ready {}ms {:5.1f}mins".format(dt, dt / 60_000)) # For polling in asynchronous code. Just checks pin state. # 0 == busy. Comment in official code is wrong. Code is correct. def ready(self): - return not(self._as_busy or (self._busy() == 0)) # 0 == busy + return not (self._as_busy or (self._busy() == 0)) # 0 == busy async def _as_show(self, buf1=bytearray(1)): mvb = self._mvb send = self._spi.write cmd = self._command - cmd(b'\x10') # DATA_START_TRANSMISSION_1 + cmd(b"\x10") # DATA_START_TRANSMISSION_1 self._dc(1) # For some reason don't need to deassert CS here - buf1[0] = 0xff + buf1[0] = 0xFF t = ticks_ms() for i in range(len(mvb)): self._cs(0) # but do when copying the framebuf send(buf1) - if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20): + if not (i & 0x1F) and (ticks_diff(ticks_ms(), t) > 20): await asyncio.sleep_ms(0) t = ticks_ms() self._cs(1) - cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet + cmd(b"\x13") # DATA_START_TRANSMISSION_2 not in datasheet self._dc(1) # Necessary to deassert CS after each byte otherwise display does not @@ -183,7 +191,7 @@ class EPD(framebuf.FrameBuffer): if not vbc: hpc += 1 idx = iidx + hpc - if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20): + if not (i & 0x1F) and (ticks_diff(ticks_ms(), t) > 20): await asyncio.sleep_ms(0) t = ticks_ms() else: @@ -192,38 +200,40 @@ class EPD(framebuf.FrameBuffer): buf1[0] = b # INVERSION HACK ~data send(buf1) self._cs(1) - if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20): + if not (i & 0x1F) and (ticks_diff(ticks_ms(), t) > 20): await asyncio.sleep_ms(0) t = ticks_ms() - self._updated.set() # framebuf has now been copied to the device - self._updated.clear() - cmd(b'\x12') # DISPLAY_REFRESH + self.updated.set() # framebuf has now been copied to the device + cmd(b"\x12") # DISPLAY_REFRESH await asyncio.sleep(1) while self._busy() == 0: await asyncio.sleep_ms(200) # Don't release lock until update is complete self._as_busy = False + self.complete.set() # draw the current frame memory. Blocking time ~180ms def show(self, buf1=bytearray(1)): - if self._asyn: + if asyncio_running(): if self._as_busy: - raise RuntimeError('Cannot refresh: display is busy.') + raise RuntimeError("Cannot refresh: display is busy.") self._as_busy = True + self.updated.clear() + self.complete.clear() asyncio.create_task(self._as_show()) return t = ticks_us() mvb = self._mvb send = self._spi.write cmd = self._command - cmd(b'\x10') # DATA_START_TRANSMISSION_1 + cmd(b"\x10") # DATA_START_TRANSMISSION_1 self._dc(1) # For some reason don't need to deassert CS here - buf1[0] = 0xff + buf1[0] = 0xFF for i in range(len(mvb)): self._cs(0) # but do when copying the framebuf send(buf1) self._cs(1) - cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet + cmd(b"\x13") # DATA_START_TRANSMISSION_2 not in datasheet self._dc(1) # Necessary to deassert CS after each byte otherwise display does not @@ -253,51 +263,51 @@ class EPD(framebuf.FrameBuffer): send(buf1) self._cs(1) - cmd(b'\x12') # DISPLAY_REFRESH + cmd(b"\x12") # DISPLAY_REFRESH te = ticks_us() - print('show time', ticks_diff(te, t)//1000, 'ms') + print("show time", ticks_diff(te, t) // 1000, "ms") if not self.demo_mode: # Immediate return to avoid blocking the whole application. # User should wait for ready before calling refresh() return self.wait_until_ready() sleep_ms(2000) # Give time for user to see result - # to wake call init() def sleep(self): self._as_busy = False self.wait_until_ready() cmd = self._command - cmd(b'\x50', b'\xf7') # From Waveshare code - cmd(b'\x02') # POWER_OFF - cmd(b'\x07', b'\xA5') # DEEP_SLEEP (Waveshare and mcauser) + cmd(b"\x50", b"\xf7") # From Waveshare code + cmd(b"\x02") # POWER_OFF + cmd(b"\x07", b"\xA5") # DEEP_SLEEP (Waveshare and mcauser) self._rst(0) # According to schematic this turns off the power + # Testing connections by toggling pins connected to 40-way connector and checking volts on small connector # All OK except rst: a 1 level produced only about 1.6V as against 3.3V for all other I/O. # Further the level on the 40-way connector read 2.9V as agains 3.3V for others. Suspect hardware problem, # ordered a second unit from Amazon. -#import machine -#import gc +# import machine +# import gc -#pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) -#pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) -#prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) -#pbusy = machine.Pin('Y4', machine.Pin.IN) +# pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) +# pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) +# prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) +# pbusy = machine.Pin('Y4', machine.Pin.IN) ## baudrate ## From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 2MHz ## From https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py 4MHz -#spi = machine.SPI(2, baudrate=2_000_000) -#gc.collect() # Precaution before instantiating framebuf -#epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance -#sleep_ms(100) -#epd.init() -#print('Initialised') -#epd.fill(1) # 1 seems to be white -#epd.show() -#sleep_ms(1000) -#epd.fill(0) -#epd.show() -#epd._rst(0) -#epd._dc(0) # Turn off power according to RPI code +# spi = machine.SPI(2, baudrate=2_000_000) +# gc.collect() # Precaution before instantiating framebuf +# epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance +# sleep_ms(100) +# epd.init() +# print('Initialised') +# epd.fill(1) # 1 seems to be white +# epd.show() +# sleep_ms(1000) +# epd.fill(0) +# epd.show() +# epd._rst(0) +# epd._dc(0) # Turn off power according to RPI code diff --git a/drivers/epaper/epd29.py b/drivers/epaper/epd29.py index 8241fa8..ed26bbc 100644 --- a/drivers/epaper/epd29.py +++ b/drivers/epaper/epd29.py @@ -4,7 +4,7 @@ # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. -# Copyright (c) Peter Hinch 2020 +# Copyright (c) Peter Hinch 2020-2023 # Released under the MIT license see LICENSE # Based on the following sources: @@ -18,11 +18,22 @@ # Adfruit code transposing the axes. import framebuf -import uasyncio as asyncio +import asyncio from micropython import const from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff +from drivers.boolpalette import BoolPalette + + +def asyncio_running(): + try: + _ = asyncio.current_task() + except: + return False + return True + + +MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show. -_MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show. class EPD(framebuf.FrameBuffer): # A monochrome approach should be used for coding this. The rgb method ensures @@ -31,6 +42,7 @@ class EPD(framebuf.FrameBuffer): def rgb(r, g, b): return int((r > 127) or (g > 127) or (b > 127)) + # Discard asyn: autodetect def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False): self._spi = spi self._cs = cs # Pins @@ -38,11 +50,11 @@ class EPD(framebuf.FrameBuffer): self._rst = rst # Active low. self._busy = busy # Active low on IL0373 self._lsc = landscape - self._asyn = asyn # ._as_busy is set immediately on start of task. Cleared # when busy pin is logically false (physically 1). self._as_busy = False - self._updated = asyncio.Event() + self.updated = asyncio.Event() + self.complete = asyncio.Event() # Public bound variables required by nanogui. # Dimensions in pixels as seen by nanogui (landscape mode). self.width = 296 if landscape else 128 @@ -54,6 +66,7 @@ class EPD(framebuf.FrameBuffer): self._buffer = bytearray(self.height * self.width // 8) self._mvb = memoryview(self._buffer) mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB + self.palette = BoolPalette(mode) super().__init__(self._buffer, self.width, self.height, mode) self.init() @@ -86,53 +99,43 @@ class EPD(framebuf.FrameBuffer): cmd = self._command # Power setting. Data from Adafruit. # Datasheet default \x03\x00\x26\x26\x03 - slightly different voltages. - cmd(b'\x01', b'\x03\x00\x2b\x2b\x09') + cmd(b"\x01", b"\x03\x00\x2b\x2b\x09") # Booster soft start. Matches datasheet. - cmd(b'\x06', b'\x17\x17\x17') - cmd(b'\x04') # Power on + cmd(b"\x06", b"\x17\x17\x17") + cmd(b"\x04") # Power on sleep_ms(200) # Iss https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/16 - cmd(b'\x00', b'\x9f') + cmd(b"\x00", b"\x9f") # CDI: As used by Adafruit. Datasheet is confusing on this. # See https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11 # With 0x37 got white border on flexible display, black on FeatherWing # 0xf7 still produced black border on FeatherWing - cmd(b'\x50', b'\x37') + cmd(b"\x50", b"\x37") # PLL: correct for 150Hz as specified in Adafruit code - cmd(b'\x30', b'\x29') + cmd(b"\x30", b"\x29") # Resolution 128w * 296h as required by IL0373 - cmd(b'\x61', b'\x80\x01\x28') # Note hex(296) == 0x128 + cmd(b"\x61", b"\x80\x01\x28") # Note hex(296) == 0x128 # Set VCM_DC. Now clarified with Adafruit. # https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17 - cmd(b'\x82', b'\x12') # Set Vcom to -1.0V + cmd(b"\x82", b"\x12") # Set Vcom to -1.0V sleep_ms(50) - print('Init Done.') + print("Init Done.") # For use in synchronous code: blocking wait on ready state. def wait_until_ready(self): sleep_ms(50) - while not self.ready(): - sleep_ms(100) - - # Asynchronous wait on ready state. Pause (4.9s) for physical refresh. - async def wait(self): - await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready while not self.ready(): - await asyncio.sleep_ms(100) - - # Pause until framebuf has been copied to device. - async def updated(self): - await self._updated.wait() + sleep_ms(100) # Return immediate status. Pin state: 0 == busy. def ready(self): - return not(self._as_busy or (self._busy() == 0)) + return not (self._as_busy or (self._busy() == 0)) async def _as_show(self, buf1=bytearray(1)): mvb = self._mvb cmd = self._command dat = self._data - cmd(b'\x13') + cmd(b"\x13") t = ticks_ms() if self._lsc: # Landscape mode wid = self.width @@ -150,34 +153,36 @@ class EPD(framebuf.FrameBuffer): if not vbc: hpc += 1 idx = iidx + hpc - if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK): + if not (i & 0x0F) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK): await asyncio.sleep_ms(0) t = ticks_ms() else: for i, b in enumerate(mvb): buf1[0] = ~b dat(buf1) - if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK): + if not (i & 0x0F) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK): await asyncio.sleep_ms(0) t = ticks_ms() - cmd(b'\x11') # Data stop - self._updated.set() - self._updated.clear() + cmd(b"\x11") # Data stop + self.updated.set() sleep_us(20) # Allow for data coming back: currently ignore this - cmd(b'\x12') # DISPLAY_REFRESH + cmd(b"\x12") # DISPLAY_REFRESH # busy goes low now, for ~4.9 seconds. await asyncio.sleep(1) while self._busy() == 0: await asyncio.sleep_ms(200) self._as_busy = False + self.complete.set() # draw the current frame memory. def show(self, buf1=bytearray(1)): - if self._asyn: + if asyncio_running(): if self._as_busy: - raise RuntimeError('Cannot refresh: display is busy.') + raise RuntimeError("Cannot refresh: display is busy.") self._as_busy = True # Immediate busy flag. Pin goes low much later. + self.updated.clear() + self.complete.clear() asyncio.create_task(self._as_show()) return @@ -187,7 +192,7 @@ class EPD(framebuf.FrameBuffer): # DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets # busy pin low (True) and that it stays logically True until # refresh is complete. In my testing this doesn't happen. - cmd(b'\x13') + cmd(b"\x13") if self._lsc: # Landscape mode wid = self.width tbc = self.height // 8 # Vertical bytes per column @@ -209,9 +214,9 @@ class EPD(framebuf.FrameBuffer): buf1[0] = ~b dat(buf1) - cmd(b'\x11') # Data stop + cmd(b"\x11") # Data stop sleep_us(20) # Allow for data coming back: currently ignore this - cmd(b'\x12') # DISPLAY_REFRESH + cmd(b"\x12") # DISPLAY_REFRESH # 258ms to get here on Pyboard D # Checking with scope, busy goes low now. For 4.9s. if not self.demo_mode: @@ -227,8 +232,8 @@ class EPD(framebuf.FrameBuffer): self.wait_until_ready() cmd = self._command # CDI: not sure about value or why we set this here. Copying Adafruit. - cmd(b'\x50', b'\x17') + cmd(b"\x50", b"\x17") # Set VCM_DC. 0 is datasheet default. - cmd(b'\x82', b'\x00') + cmd(b"\x82", b"\x00") # POWER_OFF. User code should pull ENA low to power down the display. - cmd(b'\x02') + cmd(b"\x02") diff --git a/drivers/epaper/pico_epaper_42.py b/drivers/epaper/pico_epaper_42.py index 26aaff4..0dfba61 100644 --- a/drivers/epaper/pico_epaper_42.py +++ b/drivers/epaper/pico_epaper_42.py @@ -7,6 +7,7 @@ # UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf # Waveshare's copy of this driver. # https://github.com/waveshare/Pico_ePaper_Code/blob/main/pythonNanoGui/drivers/ePaper4in2.py +# https://github.com/waveshare/Pico_ePaper_Code/blob/main/python/Pico-ePaper-4.2.py # ***************************************************************************** # * | File : Pico_ePaper-3.7.py @@ -42,7 +43,7 @@ from machine import Pin, SPI import framebuf import time -import uasyncio as asyncio +import asyncio from drivers.boolpalette import BoolPalette @@ -67,7 +68,8 @@ _BUSY_PIN = const(13) # LUT elements vcom, ww, bw, wb, bb # ****************************** full screen update LUT********************************* # -lut_full = (b"\x00\x08\x08\x00\x00\x02\x00\x0F\x0F\x00\x00\x01\x00\x08\x08\x00\ +lut_full = ( + b"\x00\x08\x08\x00\x00\x02\x00\x0F\x0F\x00\x00\x01\x00\x08\x08\x00\ \x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00", b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\ diff --git a/drivers/gc9a01/gc9a01.py b/drivers/gc9a01/gc9a01.py new file mode 100644 index 0000000..8df05db --- /dev/null +++ b/drivers/gc9a01/gc9a01.py @@ -0,0 +1,214 @@ +# gc9a01 nano-gui driver for gc9a01 displays +# Default args are for a 240*240 (typically circular) display + +# Copyright (c) Peter Hinch 2024 +# Released under the MIT license see LICENSE + +from time import sleep_ms +import gc +import framebuf +import asyncio +from drivers.boolpalette import BoolPalette + +# Initialisation ported from Russ Hughes' C driver +# https://github.com/russhughes/gc9a01_mpy/blob/main/src/gc9a01.h +# https://github.com/russhughes/gc9a01_mpy/blob/main/src/gc9a01.c +# Based on a ST7789 C driver: https://github.com/devbis/st7789_mpy +# Many registers are undocumented. Lines initialising them are commented "?" +# in cases where initialising them seems to have no effect. + +# Datasheet 7.3.4 allows scl <= 100MHz +# Waveshare touch board https://www.waveshare.com/wiki/1.28inch_Touch_LCD has CST816S touch controller +# Touch controller uses I2C + +# Portrait mode +@micropython.viper +def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): + # rgb565 - 16bit/pixel + n: int = 0 + x: int = 0 + while length: + c = source[x] # Source byte holds two 4-bit colors + dest[n] = lut[c >> 4] # current pixel + n += 1 + dest[n] = lut[c & 0x0F] # next pixel + n += 1 + x += 1 + length -= 1 + + +class GC9A01(framebuf.FrameBuffer): + + lut = bytearray(32) # Color LUT holds all possible 16-bit colors + + # Convert r, g, b in range 0-255 to a 16 bit colour value + # LS byte goes into LUT offset 0, MS byte into offset 1 + # Same mapping in linebuf so LS byte is shifted out 1st + # GC9A01 expects RGB order. 8 bit register writes require padding + @classmethod + def rgb(cls, r, g, b): + return (r & 0xF8) | ((g & 0xE0) >> 5) | ((g & 0x1C) << 11) | ((b & 0xF8) << 5) + + def __init__( + self, + spi, + cs, + dc, + rst, + height=240, + width=240, + lscape=False, + usd=False, + mirror=False, + init_spi=False, + ): + self._spi = spi + self._cs = cs + self._dc = dc + self._rst = rst + self.height = height # Logical dimensions for GUIs + self.width = width + self._spi_init = init_spi + mode = framebuf.GS4_HMSB + self.palette = BoolPalette(mode) + gc.collect() + buf = bytearray(height * width // 2) # Frame buffer + self._mvb = memoryview(buf) + super().__init__(buf, width, height, mode) + self._linebuf = bytearray(width * 2) # Line buffer (16-bit colors) + + # Hardware reset + self._rst(0) + sleep_ms(50) + self._rst(1) + sleep_ms(50) + if self._spi_init: # A callback was passed + self._spi_init(spi) # Bus may be shared + self._lock = asyncio.Lock() + sleep_ms(100) + self._wcd(b"\x2a", int.to_bytes(width - 1, 4, "big")) + # Default page address start == 0 end == 0xEF (239) + self._wcd(b"\x2b", int.to_bytes(height - 1, 4, "big")) # SET_PAGE ht + # **** Start of opaque chip setup **** + self._wcmd(b"\xEF") # Inter register enable 2 + self._wcd(b"\xEB", b"\x14") # ? + self._wcmd(b"\xFE") # Inter register enable 1 + self._wcmd(b"\xEF") # Inter register enable 2 + self._wcd(b"\xEB", b"\x14") # ? + self._wcd(b"\x84", b"\x40") # ? + self._wcd(b"\x85", b"\xFF") # ? + self._wcd(b"\x87", b"\xFF") # ? + self._wcd(b"\x86", b"\xFF") # ? + self._wcd(b"\x88", b"\x0A") # ? + self._wcd(b"\x89", b"\x21") # ? + self._wcd(b"\x8A", b"\x00") # ? + self._wcd(b"\x8B", b"\x80") # ? + self._wcd(b"\x8C", b"\x01") # ? + self._wcd(b"\x8D", b"\x01") # ? + self._wcd(b"\x8E", b"\xFF") # ? + self._wcd(b"\x8F", b"\xFF") # ? + self._wcd(b"\xB6", b"\x00\x00") # Display function control + self._wcd(b"\x3A", b"\x55") # COLMOD + self._wcd(b"\x90", b"\x08\x08\x08\x08") # ? + self._wcd(b"\xBD", b"\x06") # ? + self._wcd(b"\xBC", b"\x00") # ? + self._wcd(b"\xFF", b"\x60\x01\x04") # ? + self._wcd(b"\xC3", b"\x13") # Vreg1a voltage Control + self._wcd(b"\xC4", b"\x13") # Vreg1b voltage Control + self._wcd(b"\xC9", b"\x22") # Vreg2a voltage Control + self._wcd(b"\xBE", b"\x11") # ? + self._wcd(b"\xE1", b"\x10\x0E") # ? + self._wcd(b"\xDF", b"\x21\x0c\x02") # ? + self._wcd(b"\xF0", b"\x45\x09\x08\x08\x26\x2A") # Gamma + self._wcd(b"\xF1", b"\x43\x70\x72\x36\x37\x6F") # Gamma + self._wcd(b"\xF2", b"\x45\x09\x08\x08\x26\x2A") # Gamma + self._wcd(b"\xF3", b"\x43\x70\x72\x36\x37\x6F") # Gamma + self._wcd(b"\xED", b"\x1B\x0B") # ? + self._wcd(b"\xAE", b"\x77") # ? + self._wcd(b"\xCD", b"\x63") # ? + self._wcd(b"\x70", b"\x07\x07\x04\x0E\x0F\x09\x07\x08\x03") # ? + self._wcd(b"\xE8", b"\x34") # Frame rate / dot inversion + self._wcd(b"\x62", b"\x18\x0D\x71\xED\x70\x70\x18\x0F\x71\xEF\x70\x70") # ? + self._wcd(b"\x63", b"\x18\x11\x71\xF1\x70\x70\x18\x13\x71\xF3\x70\x70") # ? + self._wcd(b"\x64", b"\x28\x29\xF1\x01\xF1\x00\x07") # ? + self._wcd(b"\x66", b"\x3C\x00\xCD\x67\x45\x45\x10\x00\x00\x00") # Undoc but needed + self._wcd(b"\x67", b"\x00\x3C\x00\x00\x00\x01\x54\x10\x32\x98") # Undoc but needed + self._wcd(b"\x74", b"\x10\x85\x80\x00\x00\x4E\x00") # ? + self._wcd(b"\x98", b"\x3e\x07") # ? + self._wcmd(b"\x35") # Tearing effect line on + self._wcmd(b"\x21") # Display inversion on ??? + self._wcmd(b"\x11") + sleep_ms(120) + # ************************* + + # madctl reg 0x36 p127 6.2.18. b0-2 == 0. b3: color output BGR RGB/ + # b4 == 0 + # d5 row/col exchange + # d6 col address order + # d7 row address order + if lscape: + madctl = 0x28 if usd else 0xE8 # RGB landscape mode + else: + madctl = 0x48 if usd else 0x88 # RGB portrait mode + if mirror: + madctl ^= 0x80 + self._wcd(b"\x36", madctl.to_bytes(1, "big")) # MADCTL: RGB portrait mode + self._wcmd(b"\x29") # display on + + # Write a command. + def _wcmd(self, command): + self._dc(0) + self._cs(0) + self._spi.write(command) + self._cs(1) + + # Write a command followed by a data arg. + def _wcd(self, command, data): + self._dc(0) + self._cs(0) + self._spi.write(command) + self._cs(1) + self._dc(1) + self._cs(0) + self._spi.write(data) + self._cs(1) + + # @micropython.native # Made almost no difference to timing + def show(self): # Physical display is in portrait mode + clut = GC9A01.lut + lb = self._linebuf + buf = self._mvb + if self._spi_init: # A callback was passed + self._spi_init(self._spi) # Bus may be shared + self._wcmd(b"\x2c") # WRITE_RAM + self._dc(1) + self._cs(0) + wd = self.width // 2 + ht = self.height + for start in range(0, wd * ht, wd): # For each line + _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + self._spi.write(lb) + self._cs(1) + + async def do_refresh(self, split=4): + async with self._lock: + lines, mod = divmod(self.height, split) # Lines per segment + if mod: + raise ValueError("Invalid do_refresh arg.") + clut = GC9A01.lut + lb = self._linebuf + buf = self._mvb + self._wcmd(b"\x2c") # WRITE_RAM + self._dc(1) + wd = self.width // 2 + line = 0 + for _ in range(split): # For each segment + if self._spi_init: # A callback was passed + self._spi_init(self._spi) # Bus may be shared + self._cs(0) + for start in range(wd * line, wd * (line + lines), wd): # For each line + _lcopy(lb, buf[start:], clut, wd) # Copy and map colors + self._spi.write(lb) + line += lines + self._cs(1) # Allow other tasks to use bus + await asyncio.sleep_ms(0) diff --git a/drivers/ili93xx/ili9341.py b/drivers/ili93xx/ili9341.py index 8a44781..def8194 100644 --- a/drivers/ili93xx/ili9341.py +++ b/drivers/ili93xx/ili9341.py @@ -1,5 +1,4 @@ # ILI9341 nano-gui driver for ili9341 displays -# As with all nano-gui displays, touch is not supported. # Copyright (c) Peter Hinch 2020 # Released under the MIT license see LICENSE @@ -12,19 +11,19 @@ from time import sleep_ms import gc import framebuf -import uasyncio as asyncio +import asyncio from drivers.boolpalette import BoolPalette -# 74μs on RP2 @250MHz + @micropython.viper -def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int): +def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): # rgb565 - 16bit/pixel n = 0 for x in range(length): c = source[x] dest[n] = lut[c >> 4] # current pixel n += 1 - dest[n] = lut[c & 0x0f] # next pixel + dest[n] = lut[c & 0x0F] # next pixel n += 1 @@ -38,11 +37,10 @@ class ILI9341(framebuf.FrameBuffer): # ILI9341 expects RGB order @staticmethod def rgb(r, g, b): - return (r & 0xf8) | (g & 0xe0) >> 5 | (g & 0x1c) << 11 | (b & 0xf8) << 5 + return (r & 0xF8) | (g & 0xE0) >> 5 | (g & 0x1C) << 11 | (b & 0xF8) << 5 # Transpose width & height for landscape mode - def __init__(self, spi, cs, dc, rst, height=240, width=320, - usd=False, init_spi=False): + def __init__(self, spi, cs, dc, rst, height=240, width=320, usd=False, init_spi=False): self._spi = spi self._cs = cs self._dc = dc @@ -66,34 +64,36 @@ class ILI9341(framebuf.FrameBuffer): self._spi_init(spi) # Bus may be shared self._lock = asyncio.Lock() # Send initialization commands - self._wcmd(b'\x01') # SWRESET Software reset + self._wcmd(b"\x01") # SWRESET Software reset sleep_ms(100) - self._wcd(b'\xcf', b'\x00\xC1\x30') # PWCTRB Pwr ctrl B - self._wcd(b'\xed', b'\x64\x03\x12\x81') # POSC Pwr on seq. ctrl - self._wcd(b'\xe8', b'\x85\x00\x78') # DTCA Driver timing ctrl A - self._wcd(b'\xcb', b'\x39\x2C\x00\x34\x02') # PWCTRA Pwr ctrl A - self._wcd(b'\xf7', b'\x20') # PUMPRC Pump ratio control - self._wcd(b'\xea', b'\x00\x00') # DTCB Driver timing ctrl B - self._wcd(b'\xc0', b'\x23') # PWCTR1 Pwr ctrl 1 - self._wcd(b'\xc1', b'\x10') # PWCTR2 Pwr ctrl 2 - self._wcd(b'\xc5', b'\x3E\x28') # VMCTR1 VCOM ctrl 1 - self._wcd(b'\xc7', b'\x86') # VMCTR2 VCOM ctrl 2 + self._wcd(b"\xcf", b"\x00\xC1\x30") # PWCTRB Pwr ctrl B + self._wcd(b"\xed", b"\x64\x03\x12\x81") # POSC Pwr on seq. ctrl + self._wcd(b"\xe8", b"\x85\x00\x78") # DTCA Driver timing ctrl A + self._wcd(b"\xcb", b"\x39\x2C\x00\x34\x02") # PWCTRA Pwr ctrl A + self._wcd(b"\xf7", b"\x20") # PUMPRC Pump ratio control + self._wcd(b"\xea", b"\x00\x00") # DTCB Driver timing ctrl B + self._wcd(b"\xc0", b"\x23") # PWCTR1 Pwr ctrl 1 + self._wcd(b"\xc1", b"\x10") # PWCTR2 Pwr ctrl 2 + self._wcd(b"\xc5", b"\x3E\x28") # VMCTR1 VCOM ctrl 1 + self._wcd(b"\xc7", b"\x86") # VMCTR2 VCOM ctrl 2 # (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90] if self.height > self.width: - self._wcd(b'\x36', b'\x48' if usd else b'\x88') # MADCTL: RGB portrait mode + self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode else: - self._wcd(b'\x36', b'\x28' if usd else b'\xe8') # MADCTL: RGB landscape mode - self._wcd(b'\x37', b'\x00') # VSCRSADD Vertical scrolling start address - self._wcd(b'\x3a', b'\x55') # PIXFMT COLMOD: Pixel format 16 bits (MCU & interface) - self._wcd(b'\xb1', b'\x00\x18') # FRMCTR1 Frame rate ctrl - self._wcd(b'\xb6', b'\x08\x82\x27') # DFUNCTR - self._wcd(b'\xf2', b'\x00') # ENABLE3G Enable 3 gamma ctrl - self._wcd(b'\x26', b'\x01') # GAMMASET Gamma curve selected - self._wcd(b'\xe0', b'\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00') # GMCTRP1 - self._wcd(b'\xe1', b'\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F') # GMCTRN1 - self._wcmd(b'\x11') # SLPOUT Exit sleep + self._wcd(b"\x36", b"\x28" if usd else b"\xe8") # MADCTL: RGB landscape mode + self._wcd(b"\x37", b"\x00") # VSCRSADD Vertical scrolling start address + self._wcd(b"\x3a", b"\x55") # PIXFMT COLMOD: Pixel format 16 bits (MCU & interface) + self._wcd(b"\xb1", b"\x00\x18") # FRMCTR1 Frame rate ctrl + self._wcd(b"\xb6", b"\x08\x82\x27") # DFUNCTR + self._wcd(b"\xf2", b"\x00") # ENABLE3G Enable 3 gamma ctrl + self._wcd(b"\x26", b"\x01") # GAMMASET Gamma curve selected + # GMCTRP1 + self._wcd(b"\xe0", b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00") + # GMCTRN1 + self._wcd(b"\xe1", b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F") + self._wcmd(b"\x11") # SLPOUT Exit sleep sleep_ms(100) - self._wcmd(b'\x29') # DISPLAY_ON + self._wcmd(b"\x29") # DISPLAY_ON sleep_ms(100) # Write a command. @@ -114,8 +114,8 @@ class ILI9341(framebuf.FrameBuffer): self._spi.write(data) self._cs(1) -# Time (ESP32 stock freq) 196ms portrait, 185ms landscape. -# mem free on ESP32 43472 bytes (vs 110192) + # Time (ESP32 stock freq) 196ms portrait, 185ms landscape. + # mem free on ESP32 43472 bytes (vs 110192) @micropython.native def show(self): clut = ILI9341.lut @@ -125,14 +125,14 @@ class ILI9341(framebuf.FrameBuffer): buf = self._mvb if self._spi_init: # A callback was passed self._spi_init(self._spi) # Bus may be shared - # Commands needed to start data write - self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN - self._wcd(b'\x2b', int.to_bytes(ht, 4, 'big')) # SET_PAGE - self._wcmd(b'\x2c') # WRITE_RAM + # Commands needed to start data write + self._wcd(b"\x2a", int.to_bytes(self.width, 4, "big")) # SET_COLUMN + self._wcd(b"\x2b", int.to_bytes(ht, 4, "big")) # SET_PAGE + self._wcmd(b"\x2c") # WRITE_RAM self._dc(1) self._cs(0) - for start in range(0, wd*ht, wd): # For each line - _lcopy(lb, buf[start :], clut, wd) # Copy and map colors + for start in range(0, wd * ht, wd): # For each line + _lcopy(lb, buf[start:], clut, wd) # Copy and map colors self._spi.write(lb) self._cs(1) @@ -140,16 +140,16 @@ class ILI9341(framebuf.FrameBuffer): async with self._lock: lines, mod = divmod(self.height, split) # Lines per segment if mod: - raise ValueError('Invalid do_refresh arg.') + raise ValueError("Invalid do_refresh arg.") clut = ILI9341.lut wd = self.width // 2 ht = self.height lb = self._linebuf buf = self._mvb - # Commands needed to start data write - self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN - self._wcd(b'\x2b', int.to_bytes(ht, 4, 'big')) # SET_PAGE - self._wcmd(b'\x2c') # WRITE_RAM + # Commands needed to start data write + self._wcd(b"\x2a", int.to_bytes(self.width, 4, "big")) # SET_COLUMN + self._wcd(b"\x2b", int.to_bytes(ht, 4, "big")) # SET_PAGE + self._wcmd(b"\x2c") # WRITE_RAM self._dc(1) line = 0 for _ in range(split): # For each segment @@ -157,7 +157,7 @@ class ILI9341(framebuf.FrameBuffer): self._spi_init(self._spi) # Bus may be shared self._cs(0) for start in range(wd * line, wd * (line + lines), wd): # For each line - _lcopy(lb, buf[start :], clut, wd) # Copy and map colors + _lcopy(lb, buf[start:], clut, wd) # Copy and map colors self._spi.write(lb) line += lines self._cs(1) # Allow other tasks to use bus diff --git a/drivers/ili94xx/ili9486.py b/drivers/ili94xx/ili9486.py index ac52cf9..6f537da 100644 --- a/drivers/ili94xx/ili9486.py +++ b/drivers/ili94xx/ili9486.py @@ -1,5 +1,4 @@ # ILI9486 nano-gui driver for ili9486 displays -# As with all nano-gui displays, touch is not supported. # Copyright (c) Peter Hinch 2022 # Released under the MIT license see LICENSE @@ -16,7 +15,7 @@ from time import sleep_ms import gc import framebuf -import uasyncio as asyncio +import asyncio from drivers.boolpalette import BoolPalette # Portrait mode @@ -63,10 +62,14 @@ class ILI9486(framebuf.FrameBuffer): # ILI9486 expects RGB order. 8 bit register writes require padding @classmethod def rgb(cls, r, g, b): - return cls.COLOR_INVERT ^ ((r & 0xF8) | (g & 0xE0) >> 5 | (g & 0x1C) << 11 | (b & 0xF8) << 5) + return cls.COLOR_INVERT ^ ( + (r & 0xF8) | (g & 0xE0) >> 5 | (g & 0x1C) << 11 | (b & 0xF8) << 5 + ) # Transpose width & height for landscape mode - def __init__(self, spi, cs, dc, rst, height=320, width=480, usd=False, mirror=False, init_spi=False): + def __init__( + self, spi, cs, dc, rst, height=320, width=480, usd=False, mirror=False, init_spi=False + ): self._spi = spi self._cs = cs self._dc = dc @@ -107,11 +110,11 @@ class ILI9486(framebuf.FrameBuffer): # Default page address start == 0 end == 0x1DF (479) if self._long != 480: self._wcd(b"\x2b", int.to_bytes(self._long - 1, 4, "big")) # SET_PAGE ht -# self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode + # self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode madctl = 0x48 if usd else 0x88 if mirror: madctl ^= 0x80 - self._wcd(b"\x36", madctl.to_bytes(1, 'big')) # MADCTL: RGB portrait mode + self._wcd(b"\x36", madctl.to_bytes(1, "big")) # MADCTL: RGB portrait mode self._wcmd(b"\x11") # sleep out self._wcmd(b"\x29") # display on diff --git a/drivers/ssd1306/ssd1306.py b/drivers/ssd1306/ssd1306.py index 1364bd3..e26efa8 100644 --- a/drivers/ssd1306/ssd1306.py +++ b/drivers/ssd1306/ssd1306.py @@ -2,7 +2,7 @@ from micropython import const import framebuf - +from drivers.boolpalette import BoolPalette # register definitions SET_CONTRAST = const(0x81) @@ -27,7 +27,7 @@ SET_CHARGE_PUMP = const(0x8D) # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): @staticmethod - def rgb(r, g, b): + def rgb(r, g, b): # Color compatibility return int((r > 127) or (g > 127) or (b > 127)) def __init__(self, width, height, external_vcc): @@ -36,7 +36,9 @@ class SSD1306(framebuf.FrameBuffer): self.external_vcc = external_vcc self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) - super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) + mode = framebuf.MONO_VLSB + self.palette = BoolPalette(mode) # Ensure color compatibility + super().__init__(self.buffer, self.width, self.height, mode) self.init_display() def init_display(self): diff --git a/drivers/ssd1351/ssd1351_generic.py b/drivers/ssd1351/ssd1351_generic.py index 243a612..e2e284b 100644 --- a/drivers/ssd1351/ssd1351_generic.py +++ b/drivers/ssd1351/ssd1351_generic.py @@ -9,7 +9,7 @@ # This driver is based on the Adafruit C++ library for Arduino # https://github.com/adafruit/Adafruit-SSD1351-library.git -# Copyright (c) Peter Hinch 2018-2022 +# Copyright (c) Peter Hinch 2018-2020 # Released under the MIT license see LICENSE import framebuf @@ -18,7 +18,7 @@ import gc import micropython from drivers.boolpalette import BoolPalette -#import sys +import sys # https://github.com/peterhinch/micropython-nano-gui/issues/2 # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. # Now using 0,0 on STM and ESP32 diff --git a/drivers/st7789/st7789_4bit.py b/drivers/st7789/st7789_4bit.py index 4221f5b..13756f2 100644 --- a/drivers/st7789/st7789_4bit.py +++ b/drivers/st7789/st7789_4bit.py @@ -1,7 +1,7 @@ # st7789.py Driver for ST7789 LCD displays for nano-gui # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa +# Copyright (c) 2021-2024 Peter Hinch, Ihor Nehrutsa # Tested displays: # Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789 @@ -20,7 +20,7 @@ from time import sleep_ms # , ticks_us, ticks_diff import framebuf import gc import micropython -import uasyncio as asyncio +import asyncio from drivers.boolpalette import BoolPalette # User orientation constants @@ -39,13 +39,16 @@ WAVESHARE_13 = (0, 0, 16) # Waveshare 1.3" 240x240 LCD contributed by Aaron Mit @micropython.viper def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int): # rgb565 - 16bit/pixel - n = 0 - for x in range(length): + n: int = 0 + x: int = 0 + while length: c = source[x] dest[n] = lut[c >> 4] # current pixel n += 1 dest[n] = lut[c & 0x0F] # next pixel n += 1 + x += 1 + length -= 1 class ST7789(framebuf.FrameBuffer):