# 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) 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): line = bytearray(self.w + 1) h = len(buf) // (2 * self.w) if h > self.h: h = self.h for i in range(h): ix = i * self.w * 2 self.get_line(0, i, line) for j in range(1, len(line)): buf[ix] = line[j] ix += 1 self.get_line(self.w // 2, i, line) for j in range(1, len(line)): buf[ix] = line[j] ix += 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): if flush: self.oflush() self._send(b'\x02\x12') 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): 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)