Add gc9a01 driver.

pull/48/head
Peter Hinch 2024-05-13 10:18:32 +01:00
rodzic 637e48ea26
commit 9e9f076d1b
9 zmienionych plików z 443 dodań i 204 usunięć

Wyświetl plik

@ -3,7 +3,7 @@
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Optimisations to reduce allocations and RAM use. # 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 # Released under the MIT license see LICENSE
# Based on the following sources: # 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") # https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py ("official")
import framebuf import framebuf
import uasyncio as asyncio import asyncio
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff 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): class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures # 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): def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127)) 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): def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
self._spi = spi self._spi = spi
self._cs = cs # Pins self._cs = cs # Pins
@ -30,9 +41,9 @@ class EPD(framebuf.FrameBuffer):
self._rst = rst self._rst = rst
self._busy = busy self._busy = busy
self._lsc = landscape 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._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. # Dimensions in pixels. Waveshare code is portrait mode.
# Public bound variables required by nanogui. # Public bound variables required by nanogui.
self.width = 264 if landscape else 176 self.width = 264 if landscape else 176
@ -41,6 +52,7 @@ class EPD(framebuf.FrameBuffer):
self._buffer = bytearray(self.height * self.width // 8) self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer) self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
self.palette = BoolPalette(mode)
super().__init__(self._buffer, self.width, self.height, mode) super().__init__(self._buffer, self.width, self.height, mode)
self.init() self.init()
@ -65,62 +77,67 @@ class EPD(framebuf.FrameBuffer):
sleep_ms(200) sleep_ms(200)
# Initialisation # Initialisation
cmd = self._command 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(
cmd(b'\x06', b'\x07\x07\x17') # BOOSTER_SOFT_START b"\x01", b"\x03\x00\x2B\x2B\x09"
cmd(b'\xf8', b'\x60\xA5') # POWER_OPTIMIZATION ) # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR
cmd(b'\xf8', b'\x89\xA5') cmd(b"\x06", b"\x07\x07\x17") # BOOSTER_SOFT_START
cmd(b'\xf8', b'\x90\x00') cmd(b"\xf8", b"\x60\xA5") # POWER_OPTIMIZATION
cmd(b'\xf8', b'\x93\x2A') cmd(b"\xf8", b"\x89\xA5")
cmd(b'\xf8', b'\xA0\xA5') cmd(b"\xf8", b"\x90\x00")
cmd(b'\xf8', b'\xA1\x00') cmd(b"\xf8", b"\x93\x2A")
cmd(b'\xf8', b'\x73\x41') cmd(b"\xf8", b"\xA0\xA5")
cmd(b'\x16', b'\x00') # PARTIAL_DISPLAY_REFRESH cmd(b"\xf8", b"\xA1\x00")
cmd(b'\x04') # POWER_ON cmd(b"\xf8", b"\x73\x41")
cmd(b"\x16", b"\x00") # PARTIAL_DISPLAY_REFRESH
cmd(b"\x04") # POWER_ON
self.wait_until_ready() self.wait_until_ready()
cmd(b'\x00', b'\xAF') # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f 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"\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"\x50", b"\x57") # Vcom and data interval setting (PGH)
cmd(b'\x82', b'\x12') # VCM_DC_SETTING_REGISTER cmd(b"\x82", b"\x12") # VCM_DC_SETTING_REGISTER
sleep_ms(2) # No delay in official code sleep_ms(2) # No delay in official code
# Set LUT. Local bytes objects reduce RAM usage. # Set LUT. Local bytes objects reduce RAM usage.
# Values used by mcauser # Values used by mcauser
#lut_vcom_dc =\ # lut_vcom_dc =\
#b'\x00\x00\x00\x0F\x0F\x00\x00\x05\x00\x32\x32\x00\x00\x02\x00'\ # 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'\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' # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#lut_ww =\ # lut_ww =\
#b'\x50\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\xA0\x0F\x0F'\ # 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\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 # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R21H
#lut_bb =\ # lut_bb =\
#b'\xA0\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\x50\x0F\x0F'\ # 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\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 # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # R24H b
# Values from official code: # Values from official code:
lut_vcom_dc =\ lut_vcom_dc = (
b'\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00'\ 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"\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' 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'\ lut_ww = (
b'\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\ b"\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00"
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b"\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00"
lut_bb =\ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
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'\ lut_bb = (
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 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: # Both agree on this:
lut_bw = lut_ww # R22H r lut_bw = lut_ww # R22H r
lut_wb = lut_bb # R23H w lut_wb = lut_bb # R23H w
cmd(b'\x20', lut_vcom_dc) # LUT_FOR_VCOM vcom cmd(b"\x20", lut_vcom_dc) # LUT_FOR_VCOM vcom
cmd(b'\x21', lut_ww) # LUT_WHITE_TO_WHITE ww -- cmd(b"\x21", lut_ww) # LUT_WHITE_TO_WHITE ww --
cmd(b'\x22', lut_bw) # LUT_BLACK_TO_WHITE bw r cmd(b"\x22", lut_bw) # LUT_BLACK_TO_WHITE bw r
cmd(b'\x23', lut_bb) # LUT_WHITE_TO_BLACK wb w cmd(b"\x23", lut_bb) # LUT_WHITE_TO_BLACK wb w
cmd(b'\x24', lut_wb) # LUT_BLACK_TO_BLACK bb b cmd(b"\x24", lut_wb) # LUT_BLACK_TO_BLACK bb b
print('Init Done.') print("Init Done.")
def wait_until_ready(self): def wait_until_ready(self):
sleep_ms(50) sleep_ms(50)
@ -128,38 +145,29 @@ class EPD(framebuf.FrameBuffer):
while not self.ready(): while not self.ready():
sleep_ms(100) sleep_ms(100)
dt = ticks_diff(ticks_ms(), t) dt = ticks_diff(ticks_ms(), t)
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000)) 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()
# For polling in asynchronous code. Just checks pin state. # For polling in asynchronous code. Just checks pin state.
# 0 == busy. Comment in official code is wrong. Code is correct. # 0 == busy. Comment in official code is wrong. Code is correct.
def ready(self): 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)): async def _as_show(self, buf1=bytearray(1)):
mvb = self._mvb mvb = self._mvb
send = self._spi.write send = self._spi.write
cmd = self._command 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 self._dc(1) # For some reason don't need to deassert CS here
buf1[0] = 0xff buf1[0] = 0xFF
t = ticks_ms() t = ticks_ms()
for i in range(len(mvb)): for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf self._cs(0) # but do when copying the framebuf
send(buf1) 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) await asyncio.sleep_ms(0)
t = ticks_ms() t = ticks_ms()
self._cs(1) 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) self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not # Necessary to deassert CS after each byte otherwise display does not
@ -183,7 +191,7 @@ class EPD(framebuf.FrameBuffer):
if not vbc: if not vbc:
hpc += 1 hpc += 1
idx = iidx + hpc 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) await asyncio.sleep_ms(0)
t = ticks_ms() t = ticks_ms()
else: else:
@ -192,38 +200,40 @@ class EPD(framebuf.FrameBuffer):
buf1[0] = b # INVERSION HACK ~data buf1[0] = b # INVERSION HACK ~data
send(buf1) send(buf1)
self._cs(1) 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) await asyncio.sleep_ms(0)
t = ticks_ms() t = ticks_ms()
self._updated.set() # framebuf has now been copied to the device self.updated.set() # framebuf has now been copied to the device
self._updated.clear() cmd(b"\x12") # DISPLAY_REFRESH
cmd(b'\x12') # DISPLAY_REFRESH
await asyncio.sleep(1) await asyncio.sleep(1)
while self._busy() == 0: while self._busy() == 0:
await asyncio.sleep_ms(200) # Don't release lock until update is complete await asyncio.sleep_ms(200) # Don't release lock until update is complete
self._as_busy = False self._as_busy = False
self.complete.set()
# draw the current frame memory. Blocking time ~180ms # draw the current frame memory. Blocking time ~180ms
def show(self, buf1=bytearray(1)): def show(self, buf1=bytearray(1)):
if self._asyn: if asyncio_running():
if self._as_busy: if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.') raise RuntimeError("Cannot refresh: display is busy.")
self._as_busy = True self._as_busy = True
self.updated.clear()
self.complete.clear()
asyncio.create_task(self._as_show()) asyncio.create_task(self._as_show())
return return
t = ticks_us() t = ticks_us()
mvb = self._mvb mvb = self._mvb
send = self._spi.write send = self._spi.write
cmd = self._command 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 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)): for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf self._cs(0) # but do when copying the framebuf
send(buf1) send(buf1)
self._cs(1) 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) self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not # Necessary to deassert CS after each byte otherwise display does not
@ -253,9 +263,9 @@ class EPD(framebuf.FrameBuffer):
send(buf1) send(buf1)
self._cs(1) self._cs(1)
cmd(b'\x12') # DISPLAY_REFRESH cmd(b"\x12") # DISPLAY_REFRESH
te = ticks_us() 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: if not self.demo_mode:
# Immediate return to avoid blocking the whole application. # Immediate return to avoid blocking the whole application.
# User should wait for ready before calling refresh() # User should wait for ready before calling refresh()
@ -263,41 +273,41 @@ class EPD(framebuf.FrameBuffer):
self.wait_until_ready() self.wait_until_ready()
sleep_ms(2000) # Give time for user to see result sleep_ms(2000) # Give time for user to see result
# to wake call init() # to wake call init()
def sleep(self): def sleep(self):
self._as_busy = False self._as_busy = False
self.wait_until_ready() self.wait_until_ready()
cmd = self._command cmd = self._command
cmd(b'\x50', b'\xf7') # From Waveshare code cmd(b"\x50", b"\xf7") # From Waveshare code
cmd(b'\x02') # POWER_OFF cmd(b"\x02") # POWER_OFF
cmd(b'\x07', b'\xA5') # DEEP_SLEEP (Waveshare and mcauser) cmd(b"\x07", b"\xA5") # DEEP_SLEEP (Waveshare and mcauser)
self._rst(0) # According to schematic this turns off the power 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 # 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. # 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, # 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. # ordered a second unit from Amazon.
#import machine # import machine
#import gc # import gc
#pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0) # pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
#pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1) # pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
#prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1) # prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
#pbusy = machine.Pin('Y4', machine.Pin.IN) # pbusy = machine.Pin('Y4', machine.Pin.IN)
## baudrate ## baudrate
## From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 2MHz ## 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 ## 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) # spi = machine.SPI(2, baudrate=2_000_000)
#gc.collect() # Precaution before instantiating framebuf # gc.collect() # Precaution before instantiating framebuf
#epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance # epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance
#sleep_ms(100) # sleep_ms(100)
#epd.init() # epd.init()
#print('Initialised') # print('Initialised')
#epd.fill(1) # 1 seems to be white # epd.fill(1) # 1 seems to be white
#epd.show() # epd.show()
#sleep_ms(1000) # sleep_ms(1000)
#epd.fill(0) # epd.fill(0)
#epd.show() # epd.show()
#epd._rst(0) # epd._rst(0)
#epd._dc(0) # Turn off power according to RPI code # epd._dc(0) # Turn off power according to RPI code

Wyświetl plik

@ -4,7 +4,7 @@
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui. # 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 # Released under the MIT license see LICENSE
# Based on the following sources: # Based on the following sources:
@ -18,11 +18,22 @@
# Adfruit code transposing the axes. # Adfruit code transposing the axes.
import framebuf import framebuf
import uasyncio as asyncio import asyncio
from micropython import const from micropython import const
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff 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): class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures # 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): def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127)) 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): def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False):
self._spi = spi self._spi = spi
self._cs = cs # Pins self._cs = cs # Pins
@ -38,11 +50,11 @@ class EPD(framebuf.FrameBuffer):
self._rst = rst # Active low. self._rst = rst # Active low.
self._busy = busy # Active low on IL0373 self._busy = busy # Active low on IL0373
self._lsc = landscape self._lsc = landscape
self._asyn = asyn
# ._as_busy is set immediately on start of task. Cleared # ._as_busy is set immediately on start of task. Cleared
# when busy pin is logically false (physically 1). # when busy pin is logically false (physically 1).
self._as_busy = False self._as_busy = False
self._updated = asyncio.Event() self.updated = asyncio.Event()
self.complete = asyncio.Event()
# Public bound variables required by nanogui. # Public bound variables required by nanogui.
# Dimensions in pixels as seen by nanogui (landscape mode). # Dimensions in pixels as seen by nanogui (landscape mode).
self.width = 296 if landscape else 128 self.width = 296 if landscape else 128
@ -54,6 +66,7 @@ class EPD(framebuf.FrameBuffer):
self._buffer = bytearray(self.height * self.width // 8) self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer) self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
self.palette = BoolPalette(mode)
super().__init__(self._buffer, self.width, self.height, mode) super().__init__(self._buffer, self.width, self.height, mode)
self.init() self.init()
@ -86,27 +99,27 @@ class EPD(framebuf.FrameBuffer):
cmd = self._command cmd = self._command
# Power setting. Data from Adafruit. # Power setting. Data from Adafruit.
# Datasheet default \x03\x00\x26\x26\x03 - slightly different voltages. # 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. # Booster soft start. Matches datasheet.
cmd(b'\x06', b'\x17\x17\x17') cmd(b"\x06", b"\x17\x17\x17")
cmd(b'\x04') # Power on cmd(b"\x04") # Power on
sleep_ms(200) sleep_ms(200)
# Iss https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/16 # 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. # CDI: As used by Adafruit. Datasheet is confusing on this.
# See https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11 # See https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11
# With 0x37 got white border on flexible display, black on FeatherWing # With 0x37 got white border on flexible display, black on FeatherWing
# 0xf7 still produced black border 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 # 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 # 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. # Set VCM_DC. Now clarified with Adafruit.
# https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17 # 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) sleep_ms(50)
print('Init Done.') print("Init Done.")
# For use in synchronous code: blocking wait on ready state. # For use in synchronous code: blocking wait on ready state.
def wait_until_ready(self): def wait_until_ready(self):
@ -114,25 +127,15 @@ class EPD(framebuf.FrameBuffer):
while not self.ready(): while not self.ready():
sleep_ms(100) 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()
# Return immediate status. Pin state: 0 == busy. # Return immediate status. Pin state: 0 == busy.
def ready(self): 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)): async def _as_show(self, buf1=bytearray(1)):
mvb = self._mvb mvb = self._mvb
cmd = self._command cmd = self._command
dat = self._data dat = self._data
cmd(b'\x13') cmd(b"\x13")
t = ticks_ms() t = ticks_ms()
if self._lsc: # Landscape mode if self._lsc: # Landscape mode
wid = self.width wid = self.width
@ -150,34 +153,36 @@ class EPD(framebuf.FrameBuffer):
if not vbc: if not vbc:
hpc += 1 hpc += 1
idx = iidx + hpc 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) await asyncio.sleep_ms(0)
t = ticks_ms() t = ticks_ms()
else: else:
for i, b in enumerate(mvb): for i, b in enumerate(mvb):
buf1[0] = ~b buf1[0] = ~b
dat(buf1) 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) await asyncio.sleep_ms(0)
t = ticks_ms() t = ticks_ms()
cmd(b'\x11') # Data stop cmd(b"\x11") # Data stop
self._updated.set() self.updated.set()
self._updated.clear()
sleep_us(20) # Allow for data coming back: currently ignore this 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. # busy goes low now, for ~4.9 seconds.
await asyncio.sleep(1) await asyncio.sleep(1)
while self._busy() == 0: while self._busy() == 0:
await asyncio.sleep_ms(200) await asyncio.sleep_ms(200)
self._as_busy = False self._as_busy = False
self.complete.set()
# draw the current frame memory. # draw the current frame memory.
def show(self, buf1=bytearray(1)): def show(self, buf1=bytearray(1)):
if self._asyn: if asyncio_running():
if self._as_busy: 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._as_busy = True # Immediate busy flag. Pin goes low much later.
self.updated.clear()
self.complete.clear()
asyncio.create_task(self._as_show()) asyncio.create_task(self._as_show())
return return
@ -187,7 +192,7 @@ class EPD(framebuf.FrameBuffer):
# DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets # DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets
# busy pin low (True) and that it stays logically True until # busy pin low (True) and that it stays logically True until
# refresh is complete. In my testing this doesn't happen. # refresh is complete. In my testing this doesn't happen.
cmd(b'\x13') cmd(b"\x13")
if self._lsc: # Landscape mode if self._lsc: # Landscape mode
wid = self.width wid = self.width
tbc = self.height // 8 # Vertical bytes per column tbc = self.height // 8 # Vertical bytes per column
@ -209,9 +214,9 @@ class EPD(framebuf.FrameBuffer):
buf1[0] = ~b buf1[0] = ~b
dat(buf1) dat(buf1)
cmd(b'\x11') # Data stop cmd(b"\x11") # Data stop
sleep_us(20) # Allow for data coming back: currently ignore this 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 # 258ms to get here on Pyboard D
# Checking with scope, busy goes low now. For 4.9s. # Checking with scope, busy goes low now. For 4.9s.
if not self.demo_mode: if not self.demo_mode:
@ -227,8 +232,8 @@ class EPD(framebuf.FrameBuffer):
self.wait_until_ready() self.wait_until_ready()
cmd = self._command cmd = self._command
# CDI: not sure about value or why we set this here. Copying Adafruit. # 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. # 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. # POWER_OFF. User code should pull ENA low to power down the display.
cmd(b'\x02') cmd(b"\x02")

Wyświetl plik

@ -7,6 +7,7 @@
# UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf # UC8176 manual https://www.waveshare.com/w/upload/8/88/UC8176.pdf
# Waveshare's copy of this driver. # 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/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 # * | File : Pico_ePaper-3.7.py
@ -42,7 +43,7 @@
from machine import Pin, SPI from machine import Pin, SPI
import framebuf import framebuf
import time import time
import uasyncio as asyncio import asyncio
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
@ -67,7 +68,8 @@ _BUSY_PIN = const(13)
# LUT elements vcom, ww, bw, wb, bb # LUT elements vcom, ww, bw, wb, bb
# ****************************** full screen update LUT********************************* # # ****************************** 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\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", \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\ b"\x50\x08\x08\x00\x00\x02\x90\x0F\x0F\x00\x00\x01\xA0\x08\x08\x00\x00\x02\

Wyświetl plik

@ -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)

Wyświetl plik

@ -1,5 +1,4 @@
# ILI9341 nano-gui driver for ili9341 displays # ILI9341 nano-gui driver for ili9341 displays
# As with all nano-gui displays, touch is not supported.
# Copyright (c) Peter Hinch 2020 # Copyright (c) Peter Hinch 2020
# Released under the MIT license see LICENSE # Released under the MIT license see LICENSE
@ -12,19 +11,19 @@
from time import sleep_ms from time import sleep_ms
import gc import gc
import framebuf import framebuf
import uasyncio as asyncio import asyncio
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
# 74μs on RP2 @250MHz
@micropython.viper @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 # rgb565 - 16bit/pixel
n = 0 n = 0
for x in range(length): for x in range(length):
c = source[x] c = source[x]
dest[n] = lut[c >> 4] # current pixel dest[n] = lut[c >> 4] # current pixel
n += 1 n += 1
dest[n] = lut[c & 0x0f] # next pixel dest[n] = lut[c & 0x0F] # next pixel
n += 1 n += 1
@ -38,11 +37,10 @@ class ILI9341(framebuf.FrameBuffer):
# ILI9341 expects RGB order # ILI9341 expects RGB order
@staticmethod @staticmethod
def rgb(r, g, b): 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 # Transpose width & height for landscape mode
def __init__(self, spi, cs, dc, rst, height=240, width=320, def __init__(self, spi, cs, dc, rst, height=240, width=320, usd=False, init_spi=False):
usd=False, init_spi=False):
self._spi = spi self._spi = spi
self._cs = cs self._cs = cs
self._dc = dc self._dc = dc
@ -66,34 +64,36 @@ class ILI9341(framebuf.FrameBuffer):
self._spi_init(spi) # Bus may be shared self._spi_init(spi) # Bus may be shared
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
# Send initialization commands # Send initialization commands
self._wcmd(b'\x01') # SWRESET Software reset self._wcmd(b"\x01") # SWRESET Software reset
sleep_ms(100) sleep_ms(100)
self._wcd(b'\xcf', b'\x00\xC1\x30') # PWCTRB Pwr ctrl B 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"\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"\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"\xcb", b"\x39\x2C\x00\x34\x02") # PWCTRA Pwr ctrl A
self._wcd(b'\xf7', b'\x20') # PUMPRC Pump ratio control 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"\xea", b"\x00\x00") # DTCB Driver timing ctrl B
self._wcd(b'\xc0', b'\x23') # PWCTR1 Pwr ctrl 1 self._wcd(b"\xc0", b"\x23") # PWCTR1 Pwr ctrl 1
self._wcd(b'\xc1', b'\x10') # PWCTR2 Pwr ctrl 2 self._wcd(b"\xc1", b"\x10") # PWCTR2 Pwr ctrl 2
self._wcd(b'\xc5', b'\x3E\x28') # VMCTR1 VCOM ctrl 1 self._wcd(b"\xc5", b"\x3E\x28") # VMCTR1 VCOM ctrl 1
self._wcd(b'\xc7', b'\x86') # VMCTR2 VCOM ctrl 2 self._wcd(b"\xc7", b"\x86") # VMCTR2 VCOM ctrl 2
# (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90] # (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90]
if self.height > self.width: 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: else:
self._wcd(b'\x36', b'\x28' if usd else b'\xe8') # MADCTL: RGB landscape mode 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"\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"\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"\xb1", b"\x00\x18") # FRMCTR1 Frame rate ctrl
self._wcd(b'\xb6', b'\x08\x82\x27') # DFUNCTR self._wcd(b"\xb6", b"\x08\x82\x27") # DFUNCTR
self._wcd(b'\xf2', b'\x00') # ENABLE3G Enable 3 gamma ctrl self._wcd(b"\xf2", b"\x00") # ENABLE3G Enable 3 gamma ctrl
self._wcd(b'\x26', b'\x01') # GAMMASET Gamma curve selected 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 # GMCTRP1
self._wcd(b'\xe1', b'\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F') # GMCTRN1 self._wcd(b"\xe0", b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00")
self._wcmd(b'\x11') # SLPOUT Exit sleep # 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) sleep_ms(100)
self._wcmd(b'\x29') # DISPLAY_ON self._wcmd(b"\x29") # DISPLAY_ON
sleep_ms(100) sleep_ms(100)
# Write a command. # Write a command.
@ -114,8 +114,8 @@ class ILI9341(framebuf.FrameBuffer):
self._spi.write(data) self._spi.write(data)
self._cs(1) self._cs(1)
# Time (ESP32 stock freq) 196ms portrait, 185ms landscape. # Time (ESP32 stock freq) 196ms portrait, 185ms landscape.
# mem free on ESP32 43472 bytes (vs 110192) # mem free on ESP32 43472 bytes (vs 110192)
@micropython.native @micropython.native
def show(self): def show(self):
clut = ILI9341.lut clut = ILI9341.lut
@ -126,13 +126,13 @@ class ILI9341(framebuf.FrameBuffer):
if self._spi_init: # A callback was passed if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared self._spi_init(self._spi) # Bus may be shared
# Commands needed to start data write # Commands needed to start data write
self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN 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._wcd(b"\x2b", int.to_bytes(ht, 4, "big")) # SET_PAGE
self._wcmd(b'\x2c') # WRITE_RAM self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1) self._dc(1)
self._cs(0) self._cs(0)
for start in range(0, wd*ht, wd): # For each line for start in range(0, wd * ht, 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) self._spi.write(lb)
self._cs(1) self._cs(1)
@ -140,16 +140,16 @@ class ILI9341(framebuf.FrameBuffer):
async with self._lock: async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment lines, mod = divmod(self.height, split) # Lines per segment
if mod: if mod:
raise ValueError('Invalid do_refresh arg.') raise ValueError("Invalid do_refresh arg.")
clut = ILI9341.lut clut = ILI9341.lut
wd = self.width // 2 wd = self.width // 2
ht = self.height ht = self.height
lb = self._linebuf lb = self._linebuf
buf = self._mvb buf = self._mvb
# Commands needed to start data write # Commands needed to start data write
self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN 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._wcd(b"\x2b", int.to_bytes(ht, 4, "big")) # SET_PAGE
self._wcmd(b'\x2c') # WRITE_RAM self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1) self._dc(1)
line = 0 line = 0
for _ in range(split): # For each segment 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._spi_init(self._spi) # Bus may be shared
self._cs(0) self._cs(0)
for start in range(wd * line, wd * (line + lines), wd): # For each line 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) self._spi.write(lb)
line += lines line += lines
self._cs(1) # Allow other tasks to use bus self._cs(1) # Allow other tasks to use bus

Wyświetl plik

@ -1,5 +1,4 @@
# ILI9486 nano-gui driver for ili9486 displays # ILI9486 nano-gui driver for ili9486 displays
# As with all nano-gui displays, touch is not supported.
# Copyright (c) Peter Hinch 2022 # Copyright (c) Peter Hinch 2022
# Released under the MIT license see LICENSE # Released under the MIT license see LICENSE
@ -16,7 +15,7 @@
from time import sleep_ms from time import sleep_ms
import gc import gc
import framebuf import framebuf
import uasyncio as asyncio import asyncio
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
# Portrait mode # Portrait mode
@ -63,10 +62,14 @@ class ILI9486(framebuf.FrameBuffer):
# ILI9486 expects RGB order. 8 bit register writes require padding # ILI9486 expects RGB order. 8 bit register writes require padding
@classmethod @classmethod
def rgb(cls, r, g, b): 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 # 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._spi = spi
self._cs = cs self._cs = cs
self._dc = dc self._dc = dc
@ -107,11 +110,11 @@ class ILI9486(framebuf.FrameBuffer):
# Default page address start == 0 end == 0x1DF (479) # Default page address start == 0 end == 0x1DF (479)
if self._long != 480: if self._long != 480:
self._wcd(b"\x2b", int.to_bytes(self._long - 1, 4, "big")) # SET_PAGE ht 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 madctl = 0x48 if usd else 0x88
if mirror: if mirror:
madctl ^= 0x80 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"\x11") # sleep out
self._wcmd(b"\x29") # display on self._wcmd(b"\x29") # display on

Wyświetl plik

@ -2,7 +2,7 @@
from micropython import const from micropython import const
import framebuf import framebuf
from drivers.boolpalette import BoolPalette
# register definitions # register definitions
SET_CONTRAST = const(0x81) SET_CONTRAST = const(0x81)
@ -27,7 +27,7 @@ SET_CHARGE_PUMP = const(0x8D)
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer): class SSD1306(framebuf.FrameBuffer):
@staticmethod @staticmethod
def rgb(r, g, b): def rgb(r, g, b): # Color compatibility
return int((r > 127) or (g > 127) or (b > 127)) return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, width, height, external_vcc): def __init__(self, width, height, external_vcc):
@ -36,7 +36,9 @@ class SSD1306(framebuf.FrameBuffer):
self.external_vcc = external_vcc self.external_vcc = external_vcc
self.pages = self.height // 8 self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width) 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() self.init_display()
def init_display(self): def init_display(self):

Wyświetl plik

@ -9,7 +9,7 @@
# This driver is based on the Adafruit C++ library for Arduino # This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git # 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 # Released under the MIT license see LICENSE
import framebuf import framebuf
@ -18,7 +18,7 @@ import gc
import micropython import micropython
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
#import sys import sys
# https://github.com/peterhinch/micropython-nano-gui/issues/2 # https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct. # The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Now using 0,0 on STM and ESP32 # Now using 0,0 on STM and ESP32

Wyświetl plik

@ -1,7 +1,7 @@
# st7789.py Driver for ST7789 LCD displays for nano-gui # st7789.py Driver for ST7789 LCD displays for nano-gui
# Released under the MIT License (MIT). See LICENSE. # 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: # Tested displays:
# Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789 # 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 framebuf
import gc import gc
import micropython import micropython
import uasyncio as asyncio import asyncio
from drivers.boolpalette import BoolPalette from drivers.boolpalette import BoolPalette
# User orientation constants # User orientation constants
@ -39,13 +39,16 @@ WAVESHARE_13 = (0, 0, 16) # Waveshare 1.3" 240x240 LCD contributed by Aaron Mit
@micropython.viper @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 # rgb565 - 16bit/pixel
n = 0 n: int = 0
for x in range(length): x: int = 0
while length:
c = source[x] c = source[x]
dest[n] = lut[c >> 4] # current pixel dest[n] = lut[c >> 4] # current pixel
n += 1 n += 1
dest[n] = lut[c & 0x0F] # next pixel dest[n] = lut[c & 0x0F] # next pixel
n += 1 n += 1
x += 1
length -= 1
class ST7789(framebuf.FrameBuffer): class ST7789(framebuf.FrameBuffer):