200 lines
7.1 KiB
Python
200 lines
7.1 KiB
Python
"""CircuitPython ST7305/RLCD driver for Waveshare ESP32-S3-RLCD-4.2.
|
|
|
|
This is a direct migration attempt from the project's MicroPython rlcd.py.
|
|
It intentionally does not use displayio's display bus because the panel uses a
|
|
non-standard 2x4 packed 1-bit memory layout.
|
|
"""
|
|
|
|
import time
|
|
|
|
try:
|
|
import adafruit_framebuf
|
|
except ImportError: # CircuitPython bundle dependency.
|
|
adafruit_framebuf = None
|
|
|
|
|
|
class RLCD:
|
|
WIDTH = 400
|
|
HEIGHT = 300
|
|
BUFFER_SIZE = (WIDTH * HEIGHT) // 8
|
|
|
|
def __init__(self, spi, cs, dc, rst, width=WIDTH, height=HEIGHT):
|
|
if adafruit_framebuf is None:
|
|
raise RuntimeError("adafruit_framebuf is required in CIRCUITPY/lib")
|
|
self.spi = spi
|
|
self.cs = cs
|
|
self.dc = dc
|
|
self.rst = rst
|
|
self.width = width
|
|
self.height = height
|
|
self.hw_buffer = bytearray((width * height) // 8)
|
|
self.canvas_buffer = bytearray((width * height) // 8)
|
|
self.canvas = adafruit_framebuf.FrameBuffer(
|
|
self.canvas_buffer,
|
|
width,
|
|
height,
|
|
adafruit_framebuf.MHMSB,
|
|
)
|
|
|
|
self.cs.switch_to_output(value=True)
|
|
self.dc.switch_to_output(value=False)
|
|
self.rst.switch_to_output(value=True)
|
|
|
|
self.reset()
|
|
self.init_display()
|
|
|
|
def reset(self):
|
|
self.rst.value = True
|
|
time.sleep(0.05)
|
|
self.rst.value = False
|
|
time.sleep(0.02)
|
|
self.rst.value = True
|
|
time.sleep(0.05)
|
|
|
|
def _lock_spi(self):
|
|
while not self.spi.try_lock():
|
|
pass
|
|
# CircuitPython SPI settings are only guaranteed while the bus is
|
|
# locked, so configure after every lock instead of only at startup.
|
|
self.spi.configure(baudrate=20000000, phase=0, polarity=0)
|
|
|
|
def _unlock_spi(self):
|
|
self.spi.unlock()
|
|
|
|
def write_cmd(self, cmd):
|
|
self._lock_spi()
|
|
try:
|
|
self.cs.value = False
|
|
self.dc.value = False
|
|
self.spi.write(bytes([cmd & 0xFF]))
|
|
self.cs.value = True
|
|
finally:
|
|
self._unlock_spi()
|
|
|
|
def write_data(self, data):
|
|
if isinstance(data, int):
|
|
payload = bytes([data & 0xFF])
|
|
elif isinstance(data, (bytes, bytearray, memoryview)):
|
|
payload = data
|
|
else:
|
|
payload = bytes(data)
|
|
self._lock_spi()
|
|
try:
|
|
self.cs.value = False
|
|
self.dc.value = True
|
|
self.spi.write(payload)
|
|
self.cs.value = True
|
|
finally:
|
|
self._unlock_spi()
|
|
|
|
def init_display(self):
|
|
# Ported from MicroPython rlcd.py. The old file had one decimal 19 in
|
|
# the 0xC5 payload; this port uses 0x19 consistently.
|
|
self.write_cmd(0xD6); self.write_data([0x17, 0x02])
|
|
self.write_cmd(0xD1); self.write_data(0x01)
|
|
self.write_cmd(0xC0); self.write_data([0x11, 0x04])
|
|
self.write_cmd(0xC1); self.write_data([0x41, 0x41, 0x41, 0x41])
|
|
self.write_cmd(0xC2); self.write_data([0x19, 0x19, 0x19, 0x19])
|
|
self.write_cmd(0xC4); self.write_data([0x41, 0x41, 0x41, 0x41])
|
|
self.write_cmd(0xC5); self.write_data([0x19, 0x19, 0x19, 0x19])
|
|
self.write_cmd(0xD8); self.write_data([0xA6, 0xE9])
|
|
self.write_cmd(0xB2); self.write_data(0x05)
|
|
self.write_cmd(0xB3); self.write_data([0xE5, 0xF6, 0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45])
|
|
self.write_cmd(0xB4); self.write_data([0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45])
|
|
self.write_cmd(0x62); self.write_data([0x32, 0x03, 0x1F])
|
|
self.write_cmd(0xB7); self.write_data(0x13)
|
|
self.write_cmd(0xB0); self.write_data(0x64)
|
|
self.write_cmd(0x11)
|
|
time.sleep(0.2)
|
|
self.write_cmd(0xC9); self.write_data(0x00)
|
|
self.write_cmd(0x36); self.write_data(0x48)
|
|
self.write_cmd(0x3A); self.write_data(0x11)
|
|
self.write_cmd(0xB9); self.write_data(0x20)
|
|
self.write_cmd(0xB8); self.write_data(0x29)
|
|
self.write_cmd(0x21)
|
|
self.write_cmd(0x2A); self.write_data([0x12, 0x2A])
|
|
self.write_cmd(0x2B); self.write_data([0x00, 0xC7])
|
|
self.write_cmd(0x35); self.write_data(0x00)
|
|
self.write_cmd(0xD0); self.write_data(0xFF)
|
|
self.write_cmd(0x38)
|
|
self.write_cmd(0x29)
|
|
|
|
def clear(self, color=0):
|
|
self.canvas.fill(1 if color else 0)
|
|
|
|
def pixel(self, x, y, color):
|
|
self.canvas.pixel(x, y, 1 if color else 0)
|
|
|
|
def line(self, x0, y0, x1, y1, color):
|
|
self.canvas.line(x0, y0, x1, y1, 1 if color else 0)
|
|
|
|
def rect(self, x, y, width, height, color):
|
|
self.canvas.rect(x, y, width, height, 1 if color else 0)
|
|
|
|
def fill_rect(self, x, y, width, height, color):
|
|
self.canvas.fill_rect(x, y, width, height, 1 if color else 0)
|
|
|
|
def text(self, text, x, y, color=1):
|
|
self.canvas.text(str(text), x, y, 1 if color else 0)
|
|
|
|
def text_large(self, text, x, y, scale=2, color=1):
|
|
# Simple nearest-neighbor scaled 8x8 font rendering using a temp buffer.
|
|
tmp = bytearray(8)
|
|
fb = adafruit_framebuf.FrameBuffer(tmp, 8, 8, adafruit_framebuf.MHMSB)
|
|
color = 1 if color else 0
|
|
for ch in str(text):
|
|
fb.fill(0)
|
|
fb.text(ch, 0, 0, 1)
|
|
for py in range(8):
|
|
for px in range(8):
|
|
if fb.pixel(px, py):
|
|
self.canvas.fill_rect(x + px * scale, y + py * scale, scale, scale, color)
|
|
x += 8 * scale
|
|
|
|
def draw_bitmap_threshold(self, bitmap, x=0, y=0, threshold=1):
|
|
"""Copy any indexable bitmap-like object to the 1-bit canvas.
|
|
|
|
gifio/displayio bitmaps return palette indexes. Treat index 0 as white
|
|
and anything >= threshold as black by default.
|
|
"""
|
|
width = min(getattr(bitmap, "width", self.width), self.width - x)
|
|
height = min(getattr(bitmap, "height", self.height), self.height - y)
|
|
for yy in range(height):
|
|
for xx in range(width):
|
|
self.canvas.pixel(x + xx, y + yy, 1 if bitmap[xx, yy] >= threshold else 0)
|
|
|
|
def pack(self):
|
|
"""Convert standard horizontal 1-bit canvas into the RLCD 2x4 layout."""
|
|
buf = self.hw_buffer
|
|
for i in range(len(buf)):
|
|
buf[i] = 0
|
|
|
|
# Correctness-first port of MicroPython native loop.
|
|
height = self.height
|
|
for y in range(height):
|
|
inv_y = height - 1 - y
|
|
block_y = inv_y // 4
|
|
local_y = inv_y & 0x03
|
|
local_y_shift = local_y * 2
|
|
row_offset = block_y
|
|
for x in range(self.width):
|
|
if self.canvas.pixel(x, y):
|
|
byte_x = x >> 1
|
|
index = byte_x * 75 + row_offset
|
|
local_x = x & 0x01
|
|
bit = 7 - (local_y_shift + local_x)
|
|
buf[index] |= 1 << bit
|
|
return buf
|
|
|
|
def show(self):
|
|
self.pack()
|
|
self.write_cmd(0x2A)
|
|
self.write_data([0x12, 0x2A])
|
|
self.write_cmd(0x2B)
|
|
self.write_data([0x00, 0xC7])
|
|
self.write_cmd(0x2C)
|
|
self.write_data(self.hw_buffer)
|
|
|
|
def invert(self, enable=True):
|
|
self.write_cmd(0x21 if enable else 0x20)
|