dc77fdb7d4
The intention of oflush() is to flush the "fast SPI" command itself so that the SPI object is ready to use when the function returns.
475 lines
14 KiB
Python
475 lines
14 KiB
Python
# Driver for official MicroPython LCD160CR display
|
|
# MIT license; Copyright (c) 2017 Damien P. George
|
|
|
|
from micropython import const
|
|
from utime import sleep_ms
|
|
from ustruct import calcsize, pack_into
|
|
import uerrno, machine
|
|
|
|
# for set_orient
|
|
PORTRAIT = const(0)
|
|
LANDSCAPE = const(1)
|
|
PORTRAIT_UPSIDEDOWN = const(2)
|
|
LANDSCAPE_UPSIDEDOWN = const(3)
|
|
|
|
# for set_startup_deco; can be or'd
|
|
STARTUP_DECO_NONE = const(0)
|
|
STARTUP_DECO_MLOGO = const(1)
|
|
STARTUP_DECO_INFO = const(2)
|
|
|
|
_uart_baud_table = {
|
|
2400: 0,
|
|
4800: 1,
|
|
9600: 2,
|
|
19200: 3,
|
|
38400: 4,
|
|
57600: 5,
|
|
115200: 6,
|
|
230400: 7,
|
|
460800: 8,
|
|
}
|
|
|
|
class LCD160CR:
|
|
def __init__(self, connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98):
|
|
if connect in ('X', 'Y', 'XY', 'YX'):
|
|
i = connect[-1]
|
|
j = connect[0]
|
|
y = j + '4'
|
|
elif connect == 'C':
|
|
i = 2
|
|
j = 2
|
|
y = 'A7'
|
|
else:
|
|
if pwr is None or i2c is None or spi is None:
|
|
raise ValueError('must specify valid "connect" or all of "pwr", "i2c" and "spi"')
|
|
|
|
if pwr is None:
|
|
pwr = machine.Pin(y, machine.Pin.OUT)
|
|
if i2c is None:
|
|
i2c = machine.I2C(i, freq=1000000)
|
|
if spi is None:
|
|
spi = machine.SPI(j, baudrate=13500000, polarity=0, phase=0)
|
|
|
|
if not pwr.value():
|
|
pwr(1)
|
|
sleep_ms(10)
|
|
# else:
|
|
# alread have power
|
|
# lets be optimistic...
|
|
|
|
# set connections
|
|
self.pwr = pwr
|
|
self.i2c = i2c
|
|
self.spi = spi
|
|
self.i2c_addr = i2c_addr
|
|
|
|
# create temp buffers and memoryviews
|
|
self.buf16 = bytearray(16)
|
|
self.buf19 = bytearray(19)
|
|
self.buf = [None] * 10
|
|
for i in range(1, 10):
|
|
self.buf[i] = memoryview(self.buf16)[0:i]
|
|
self.buf1 = self.buf[1]
|
|
self.array4 = [0, 0, 0, 0]
|
|
|
|
# set default orientation and window
|
|
self.set_orient(PORTRAIT)
|
|
self._fcmd2b('<BBBBBB', 0x76, 0, 0, self.w, self.h) # viewport 'v'
|
|
self._fcmd2b('<BBBBBB', 0x79, 0, 0, self.w, self.h) # window 'y'
|
|
|
|
def _send(self, cmd):
|
|
i = self.i2c.writeto(self.i2c_addr, cmd)
|
|
if i == len(cmd):
|
|
return
|
|
cmd = memoryview(cmd)
|
|
n = len(cmd)
|
|
while True:
|
|
i += self.i2c.writeto(self.i2c_addr, cmd[i:])
|
|
if i == n:
|
|
return
|
|
sleep_ms(10)
|
|
|
|
def _fcmd2(self, fmt, a0, a1=0, a2=0):
|
|
buf = self.buf[calcsize(fmt)]
|
|
pack_into(fmt, buf, 0, 2, a0, a1, a2)
|
|
self._send(buf)
|
|
|
|
def _fcmd2b(self, fmt, a0, a1, a2, a3, a4=0):
|
|
buf = self.buf[calcsize(fmt)]
|
|
pack_into(fmt, buf, 0, 2, a0, a1, a2, a3, a4)
|
|
self._send(buf)
|
|
|
|
def _waitfor(self, n, buf):
|
|
t = 5000
|
|
while t:
|
|
self.i2c.readfrom_into(self.i2c_addr, self.buf1)
|
|
if self.buf1[0] >= n:
|
|
self.i2c.readfrom_into(self.i2c_addr, buf)
|
|
return
|
|
t -= 1
|
|
sleep_ms(1)
|
|
raise OSError(uerrno.ETIMEDOUT)
|
|
|
|
def oflush(self, n=255):
|
|
t = 5000
|
|
while t:
|
|
self.i2c.readfrom_into(self.i2c_addr + 1, self.buf1)
|
|
r = self.buf1[0]
|
|
if r >= n:
|
|
return
|
|
t -= 1
|
|
machine.idle()
|
|
raise OSError(uerrno.ETIMEDOUT)
|
|
|
|
def iflush(self):
|
|
t = 5000
|
|
while t:
|
|
self.i2c.readfrom_into(self.i2c_addr, self.buf16)
|
|
if self.buf16[0] == 0:
|
|
return
|
|
t -= 1
|
|
sleep_ms(1)
|
|
raise OSError(uerrno.ETIMEDOUT)
|
|
|
|
#### MISC METHODS ####
|
|
|
|
@staticmethod
|
|
def rgb(r, g, b):
|
|
return ((b & 0xf8) << 8) | ((g & 0xfc) << 3) | (r >> 3)
|
|
|
|
@staticmethod
|
|
def clip_line(c, w, h):
|
|
while True:
|
|
ca = ce = 0
|
|
if c[1] < 0:
|
|
ca |= 8
|
|
elif c[1] > h:
|
|
ca |= 4
|
|
if c[0] < 0:
|
|
ca |= 1
|
|
elif c[0] > w:
|
|
ca |= 2
|
|
if c[3] < 0:
|
|
ce |= 8
|
|
elif c[3] > h:
|
|
ce |= 4
|
|
if c[2] < 0:
|
|
ce |= 1
|
|
elif c[2] > w:
|
|
ce |= 2
|
|
if ca & ce:
|
|
return False
|
|
elif ca | ce:
|
|
ca |= ce
|
|
if ca & 1:
|
|
if c[2] < c[0]:
|
|
c[0], c[2] = c[2], c[0]
|
|
c[1], c[3] = c[3], c[1]
|
|
c[1] += ((-c[0]) * (c[3] - c[1])) // (c[2] - c[0])
|
|
c[0] = 0
|
|
elif ca & 2:
|
|
if c[2] < c[0]:
|
|
c[0], c[2] = c[2], c[0]
|
|
c[1], c[3] = c[3], c[1]
|
|
c[3] += ((w - 1 - c[2]) * (c[3] - c[1])) // (c[2] - c[0])
|
|
c[2] = w - 1
|
|
elif ca & 4:
|
|
if c[0] == c[2]:
|
|
if c[1] >= h:
|
|
c[1] = h - 1
|
|
if c[3] >= h:
|
|
c[3] = h - 1
|
|
else:
|
|
if c[3] < c[1]:
|
|
c[0], c[2] = c[2], c[0]
|
|
c[1], c[3] = c[3], c[1]
|
|
c[2] += ((h - 1 - c[3]) * (c[2] - c[0])) // (c[3] - c[1])
|
|
c[3] = h - 1
|
|
else:
|
|
if c[0] == c[2]:
|
|
if c[1] < 0:
|
|
c[1] = 0
|
|
if c[3] < 0:
|
|
c[3] = 0
|
|
else:
|
|
if c[3] < c[1]:
|
|
c[0], c[2] = c[2], c[0]
|
|
c[1], c[3] = c[3], c[1]
|
|
c[0] += ((-c[1]) * (c[2] - c[0])) // (c[3] - c[1])
|
|
c[1] = 0
|
|
else:
|
|
return True
|
|
|
|
#### SETUP COMMANDS ####
|
|
|
|
def set_power(self, on):
|
|
self.pwr(on)
|
|
sleep_ms(15)
|
|
|
|
def set_orient(self, orient):
|
|
self._fcmd2('<BBB', 0x14, (orient & 3) + 4)
|
|
# update width and height variables
|
|
self.iflush()
|
|
self._send(b'\x02g0')
|
|
self._waitfor(4, self.buf[5])
|
|
self.w = self.buf[5][1]
|
|
self.h = self.buf[5][2]
|
|
|
|
def set_brightness(self, value):
|
|
self._fcmd2('<BBB', 0x16, value)
|
|
|
|
def set_i2c_addr(self, addr):
|
|
# 0x0e set i2c addr
|
|
if addr & 3:
|
|
raise ValueError('must specify mod 4 aligned address')
|
|
self._fcmd2('<BBW', 0x0e, 0x433249 | (addr << 24))
|
|
|
|
def set_uart_baudrate(self, baudrate):
|
|
try:
|
|
baudrate = _uart_baud_table[baudrate]
|
|
except KeyError:
|
|
raise ValueError('invalid baudrate')
|
|
self._fcmd2('<BBB', 0x18, baudrate)
|
|
|
|
def set_startup_deco(self, value):
|
|
self._fcmd2('<BBB', 0x19, value)
|
|
|
|
def save_to_flash(self):
|
|
self._send(b'\x02fn')
|
|
|
|
#### PIXEL ACCESS ####
|
|
|
|
def set_pixel(self, x, y, c):
|
|
self._fcmd2b('<BBBBH', 0x41, x, y, c)
|
|
|
|
def get_pixel(self, x, y):
|
|
self._fcmd2('<BBBB', 0x61, x, y)
|
|
t = 1000
|
|
while t:
|
|
self.i2c.readfrom_into(self.i2c_addr, self.buf1)
|
|
if self.buf1[0] >= 2:
|
|
self.i2c.readfrom_into(self.i2c_addr, self.buf[3])
|
|
return self.buf[3][1] | self.buf[3][2] << 8
|
|
t -= 1
|
|
sleep_ms(1)
|
|
raise OSError(uerrno.ETIMEDOUT)
|
|
|
|
def get_line(self, x, y, buf):
|
|
l = len(buf) // 2
|
|
self._fcmd2b('<BBBBB', 0x10, l, x, y)
|
|
l *= 2
|
|
t = 1000
|
|
while t:
|
|
self.i2c.readfrom_into(self.i2c_addr, self.buf1)
|
|
if self.buf1[0] >= l:
|
|
self.i2c.readfrom_into(self.i2c_addr, buf)
|
|
return
|
|
t -= 1
|
|
sleep_ms(1)
|
|
raise OSError(uerrno.ETIMEDOUT)
|
|
|
|
def screen_dump(self, buf, x=0, y=0, w=None, h=None):
|
|
if w is None:
|
|
w = self.w - x
|
|
if h is None:
|
|
h = self.h - y
|
|
if w <= 127:
|
|
line = bytearray(2 * w + 1)
|
|
line2 = None
|
|
else:
|
|
# split line if more than 254 bytes needed
|
|
buflen = (w + 1) // 2
|
|
line = bytearray(2 * buflen + 1)
|
|
line2 = memoryview(line)[:2 * (w - buflen) + 1]
|
|
for i in range(min(len(buf) // (2 * w), h)):
|
|
ix = i * w * 2
|
|
self.get_line(x, y + i, line)
|
|
buf[ix:ix + len(line) - 1] = memoryview(line)[1:]
|
|
ix += len(line) - 1
|
|
if line2:
|
|
self.get_line(x + buflen, y + i, line2)
|
|
buf[ix:ix + len(line2) - 1] = memoryview(line2)[1:]
|
|
ix += len(line2) - 1
|
|
|
|
def screen_load(self, buf):
|
|
l = self.w * self.h * 2+2
|
|
self._fcmd2b('<BBHBBB', 0x70, l, 16, self.w, self.h)
|
|
n = 0
|
|
ar = memoryview(buf)
|
|
while n < len(buf):
|
|
if len(buf) - n >= 0x200:
|
|
self._send(ar[n:n + 0x200])
|
|
n += 0x200
|
|
else:
|
|
self._send(ar[n:])
|
|
while n < self.w * self.h * 2:
|
|
self._send(b'\x00')
|
|
n += 1
|
|
|
|
#### TEXT COMMANDS ####
|
|
|
|
def set_pos(self, x, y):
|
|
self._fcmd2('<BBBB', 0x58, x, y)
|
|
|
|
def set_text_color(self, fg, bg):
|
|
self._fcmd2('<BBHH', 0x63, fg, bg)
|
|
|
|
def set_font(self, font, scale=0, bold=0, trans=0, scroll=0):
|
|
self._fcmd2('<BBBB', 0x46, (scroll << 7) | (trans << 6) | ((font & 3) << 4) | (bold & 0xf), scale & 0xff)
|
|
|
|
def write(self, s):
|
|
# TODO: eventually check for room in LCD input queue
|
|
self._send(s)
|
|
|
|
#### PRIMITIVE DRAWING COMMANDS ####
|
|
|
|
def set_pen(self, line, fill):
|
|
self._fcmd2('<BBHH', 0x50, line, fill)
|
|
|
|
def erase(self):
|
|
self._send(b'\x02\x45')
|
|
|
|
def dot(self, x, y):
|
|
if 0 <= x < self.w and 0 <= y < self.h:
|
|
self._fcmd2('<BBBB', 0x4b, x, y)
|
|
|
|
def rect(self, x, y, w, h, cmd=0x72):
|
|
if x + w <= 0 or y + h <= 0 or x >= self.w or y >= self.h:
|
|
return
|
|
elif x < 0 or y < 0:
|
|
left = top = True
|
|
if x < 0:
|
|
left = False
|
|
w += x
|
|
x = 0
|
|
if y < 0:
|
|
top = False
|
|
h += y
|
|
y = 0
|
|
if cmd == 0x51 or cmd == 0x72:
|
|
# draw interior
|
|
self._fcmd2b('<BBBBBB', 0x51, x, y, min(w, 255), min(h, 255))
|
|
if cmd == 0x57 or cmd == 0x72:
|
|
# draw outline
|
|
if left:
|
|
self._fcmd2b('<BBBBBB', 0x57, x, y, 1, min(h, 255))
|
|
if top:
|
|
self._fcmd2b('<BBBBBB', 0x57, x, y, min(w, 255), 1)
|
|
if x + w < self.w:
|
|
self._fcmd2b('<BBBBBB', 0x57, x + w, y, 1, min(h, 255))
|
|
if y + h < self.h:
|
|
self._fcmd2b('<BBBBBB', 0x57, x, y + h, min(w, 255), 1)
|
|
else:
|
|
self._fcmd2b('<BBBBBB', cmd, x, y, min(w, 255), min(h, 255))
|
|
|
|
def rect_outline(self, x, y, w, h):
|
|
self.rect(x, y, w, h, 0x57)
|
|
|
|
def rect_interior(self, x, y, w, h):
|
|
self.rect(x, y, w, h, 0x51)
|
|
|
|
def line(self, x1, y1, x2, y2):
|
|
ar4 = self.array4
|
|
ar4[0] = x1
|
|
ar4[1] = y1
|
|
ar4[2] = x2
|
|
ar4[3] = y2
|
|
if self.clip_line(ar4, self.w, self.h):
|
|
self._fcmd2b('<BBBBBB', 0x4c, ar4[0], ar4[1], ar4[2], ar4[3])
|
|
|
|
def dot_no_clip(self, x, y):
|
|
self._fcmd2('<BBBB', 0x4b, x, y)
|
|
|
|
def rect_no_clip(self, x, y, w, h):
|
|
self._fcmd2b('<BBBBBB', 0x72, x, y, w, h)
|
|
|
|
def rect_outline_no_clip(self, x, y, w, h):
|
|
self._fcmd2b('<BBBBBB', 0x57, x, y, w, h)
|
|
|
|
def rect_interior_no_clip(self, x, y, w, h):
|
|
self._fcmd2b('<BBBBBB', 0x51, x, y, w, h)
|
|
|
|
def line_no_clip(self, x1, y1, x2, y2):
|
|
self._fcmd2b('<BBBBBB', 0x4c, x1, y1, x2, y2)
|
|
|
|
def poly_dot(self, data):
|
|
if len(data) & 1:
|
|
raise ValueError('must specify even number of bytes')
|
|
self._fcmd2('<BBB', 0x71, len(data) // 2)
|
|
self._send(data)
|
|
|
|
def poly_line(self, data):
|
|
if len(data) & 1:
|
|
raise ValueError('must specify even number of bytes')
|
|
self._fcmd2('<BBB', 0x78, len(data) // 2)
|
|
self._send(data)
|
|
|
|
#### TOUCH COMMANDS ####
|
|
|
|
def touch_config(self, calib=False, save=False, irq=None):
|
|
self._fcmd2('<BBBB', 0x7a, (irq is not None) << 2 | save << 1 | calib, bool(irq) << 7)
|
|
|
|
def is_touched(self):
|
|
self._send(b'\x02T')
|
|
b = self.buf[4]
|
|
self._waitfor(3, b)
|
|
return b[1] >> 7 != 0
|
|
|
|
def get_touch(self):
|
|
self._send(b'\x02T') # implicit LCD output flush
|
|
b = self.buf[4]
|
|
self._waitfor(3, b)
|
|
return b[1] >> 7, b[2], b[3]
|
|
|
|
#### ADVANCED COMMANDS ####
|
|
|
|
def set_spi_win(self, x, y, w, h):
|
|
pack_into('<BBBHHHHHHHH', self.buf19, 0, 2, 0x55, 10, x, y, x + w - 1, y + h - 1, 0, 0, 0, 0xffff)
|
|
self._send(self.buf19)
|
|
|
|
def fast_spi(self, flush=True):
|
|
self._send(b'\x02\x12')
|
|
if flush:
|
|
self.oflush()
|
|
return self.spi
|
|
|
|
def show_framebuf(self, buf):
|
|
self.fast_spi().write(buf)
|
|
|
|
def set_scroll(self, on):
|
|
self._fcmd2('<BBB', 0x15, on)
|
|
|
|
def set_scroll_win(self, win, x=-1, y=0, w=0, h=0, vec=0, pat=0, fill=0x07e0, color=0):
|
|
pack_into('<BBBHHHHHHHH', self.buf19, 0, 2, 0x55, win, x, y, w, h, vec, pat, fill, color)
|
|
self._send(self.buf19)
|
|
|
|
def set_scroll_win_param(self, win, param, value):
|
|
self._fcmd2b('<BBBBH', 0x75, win, param, value)
|
|
|
|
def set_scroll_buf(self, s):
|
|
l = len(s)
|
|
if l > 32:
|
|
raise ValueError('length must be 32 or less')
|
|
self._fcmd2('<BBB', 0x11, l)
|
|
self._send(s)
|
|
|
|
def jpeg_start(self, l):
|
|
if l > 0xffff:
|
|
raise ValueError('length must be 65535 or less')
|
|
self.oflush()
|
|
self._fcmd2('<BBH', 0x6a, l)
|
|
|
|
def jpeg_data(self, buf):
|
|
self._send(buf)
|
|
|
|
def jpeg(self, buf):
|
|
self.jpeg_start(len(buf))
|
|
self.jpeg_data(buf)
|
|
|
|
def feed_wdt(self):
|
|
self._send(b'\x02\x17')
|
|
|
|
def reset(self):
|
|
self._send(b'\x02Y\xef\xbe\xad\xde')
|
|
sleep_ms(15)
|