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.
# 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,9 +41,9 @@ 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
@ -41,6 +52,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()
@ -65,22 +77,24 @@ 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.
@ -99,28 +113,31 @@ class EPD(framebuf.FrameBuffer):
# 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)
@ -128,16 +145,7 @@ class EPD(framebuf.FrameBuffer):
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.
@ -148,18 +156,18 @@ class EPD(framebuf.FrameBuffer):
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,9 +263,9 @@ 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()
@ -263,17 +273,17 @@ class EPD(framebuf.FrameBuffer):
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,

Wyświetl plik

@ -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,27 +99,27 @@ 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):
@ -114,16 +127,6 @@ class EPD(framebuf.FrameBuffer):
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()
# Return immediate status. Pin state: 0 == busy.
def ready(self):
return not (self._as_busy or (self._busy() == 0))
@ -132,7 +135,7 @@ class EPD(framebuf.FrameBuffer):
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")

Wyświetl plik

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

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
# As with all nano-gui displays, touch is not supported.
# Copyright (c) Peter Hinch 2020
# Released under the MIT license see LICENSE
@ -12,10 +11,10 @@
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):
# rgb565 - 16bit/pixel
@ -24,7 +23,7 @@ def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int):
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.
@ -126,9 +126,9 @@ class ILI9341(framebuf.FrameBuffer):
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
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
@ -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
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

Wyświetl plik

@ -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
@ -111,7 +114,7 @@ class ILI9486(framebuf.FrameBuffer):
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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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