1# Driver for official MicroPython LCD160CR display 2# MIT license; Copyright (c) 2017 Damien P. George 3 4from micropython import const 5from utime import sleep_ms 6from ustruct import calcsize, pack_into 7import uerrno, machine 8 9# for set_orient 10PORTRAIT = const(0) 11LANDSCAPE = const(1) 12PORTRAIT_UPSIDEDOWN = const(2) 13LANDSCAPE_UPSIDEDOWN = const(3) 14 15# for set_startup_deco; can be or'd 16STARTUP_DECO_NONE = const(0) 17STARTUP_DECO_MLOGO = const(1) 18STARTUP_DECO_INFO = const(2) 19 20_uart_baud_table = { 21 2400: 0, 22 4800: 1, 23 9600: 2, 24 19200: 3, 25 38400: 4, 26 57600: 5, 27 115200: 6, 28 230400: 7, 29 460800: 8, 30} 31 32 33class LCD160CR: 34 def __init__(self, connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98): 35 if connect in ("X", "Y", "XY", "YX"): 36 i = connect[-1] 37 j = connect[0] 38 y = j + "4" 39 elif connect == "C": 40 i = 2 41 j = 2 42 y = "A7" 43 else: 44 if pwr is None or i2c is None or spi is None: 45 raise ValueError('must specify valid "connect" or all of "pwr", "i2c" and "spi"') 46 47 if pwr is None: 48 pwr = machine.Pin(y, machine.Pin.OUT) 49 if i2c is None: 50 i2c = machine.I2C(i, freq=1000000) 51 if spi is None: 52 spi = machine.SPI(j, baudrate=13500000, polarity=0, phase=0) 53 54 if not pwr.value(): 55 pwr(1) 56 sleep_ms(10) 57 # else: 58 # alread have power 59 # lets be optimistic... 60 61 # set connections 62 self.pwr = pwr 63 self.i2c = i2c 64 self.spi = spi 65 self.i2c_addr = i2c_addr 66 67 # create temp buffers and memoryviews 68 self.buf16 = bytearray(16) 69 self.buf19 = bytearray(19) 70 self.buf = [None] * 10 71 for i in range(1, 10): 72 self.buf[i] = memoryview(self.buf16)[0:i] 73 self.buf1 = self.buf[1] 74 self.array4 = [0, 0, 0, 0] 75 76 # set default orientation and window 77 self.set_orient(PORTRAIT) 78 self._fcmd2b("<BBBBBB", 0x76, 0, 0, self.w, self.h) # viewport 'v' 79 self._fcmd2b("<BBBBBB", 0x79, 0, 0, self.w, self.h) # window 'y' 80 81 def _send(self, cmd): 82 i = self.i2c.writeto(self.i2c_addr, cmd) 83 if i == len(cmd): 84 return 85 cmd = memoryview(cmd) 86 n = len(cmd) 87 while True: 88 i += self.i2c.writeto(self.i2c_addr, cmd[i:]) 89 if i == n: 90 return 91 sleep_ms(10) 92 93 def _fcmd2(self, fmt, a0, a1=0, a2=0): 94 buf = self.buf[calcsize(fmt)] 95 pack_into(fmt, buf, 0, 2, a0, a1, a2) 96 self._send(buf) 97 98 def _fcmd2b(self, fmt, a0, a1, a2, a3, a4=0): 99 buf = self.buf[calcsize(fmt)] 100 pack_into(fmt, buf, 0, 2, a0, a1, a2, a3, a4) 101 self._send(buf) 102 103 def _waitfor(self, n, buf): 104 t = 5000 105 while t: 106 self.i2c.readfrom_into(self.i2c_addr, self.buf1) 107 if self.buf1[0] >= n: 108 self.i2c.readfrom_into(self.i2c_addr, buf) 109 return 110 t -= 1 111 sleep_ms(1) 112 raise OSError(uerrno.ETIMEDOUT) 113 114 def oflush(self, n=255): 115 t = 5000 116 while t: 117 self.i2c.readfrom_into(self.i2c_addr + 1, self.buf1) 118 r = self.buf1[0] 119 if r >= n: 120 return 121 t -= 1 122 machine.idle() 123 raise OSError(uerrno.ETIMEDOUT) 124 125 def iflush(self): 126 t = 5000 127 while t: 128 self.i2c.readfrom_into(self.i2c_addr, self.buf16) 129 if self.buf16[0] == 0: 130 return 131 t -= 1 132 sleep_ms(1) 133 raise OSError(uerrno.ETIMEDOUT) 134 135 #### MISC METHODS #### 136 137 @staticmethod 138 def rgb(r, g, b): 139 return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3) 140 141 @staticmethod 142 def clip_line(c, w, h): 143 while True: 144 ca = ce = 0 145 if c[1] < 0: 146 ca |= 8 147 elif c[1] > h: 148 ca |= 4 149 if c[0] < 0: 150 ca |= 1 151 elif c[0] > w: 152 ca |= 2 153 if c[3] < 0: 154 ce |= 8 155 elif c[3] > h: 156 ce |= 4 157 if c[2] < 0: 158 ce |= 1 159 elif c[2] > w: 160 ce |= 2 161 if ca & ce: 162 return False 163 elif ca | ce: 164 ca |= ce 165 if ca & 1: 166 if c[2] < c[0]: 167 c[0], c[2] = c[2], c[0] 168 c[1], c[3] = c[3], c[1] 169 c[1] += ((-c[0]) * (c[3] - c[1])) // (c[2] - c[0]) 170 c[0] = 0 171 elif ca & 2: 172 if c[2] < c[0]: 173 c[0], c[2] = c[2], c[0] 174 c[1], c[3] = c[3], c[1] 175 c[3] += ((w - 1 - c[2]) * (c[3] - c[1])) // (c[2] - c[0]) 176 c[2] = w - 1 177 elif ca & 4: 178 if c[0] == c[2]: 179 if c[1] >= h: 180 c[1] = h - 1 181 if c[3] >= h: 182 c[3] = h - 1 183 else: 184 if c[3] < c[1]: 185 c[0], c[2] = c[2], c[0] 186 c[1], c[3] = c[3], c[1] 187 c[2] += ((h - 1 - c[3]) * (c[2] - c[0])) // (c[3] - c[1]) 188 c[3] = h - 1 189 else: 190 if c[0] == c[2]: 191 if c[1] < 0: 192 c[1] = 0 193 if c[3] < 0: 194 c[3] = 0 195 else: 196 if c[3] < c[1]: 197 c[0], c[2] = c[2], c[0] 198 c[1], c[3] = c[3], c[1] 199 c[0] += ((-c[1]) * (c[2] - c[0])) // (c[3] - c[1]) 200 c[1] = 0 201 else: 202 return True 203 204 #### SETUP COMMANDS #### 205 206 def set_power(self, on): 207 self.pwr(on) 208 sleep_ms(15) 209 210 def set_orient(self, orient): 211 self._fcmd2("<BBB", 0x14, (orient & 3) + 4) 212 # update width and height variables 213 self.iflush() 214 self._send(b"\x02g0") 215 self._waitfor(4, self.buf[5]) 216 self.w = self.buf[5][1] 217 self.h = self.buf[5][2] 218 219 def set_brightness(self, value): 220 self._fcmd2("<BBB", 0x16, value) 221 222 def set_i2c_addr(self, addr): 223 # 0x0e set i2c addr 224 if addr & 3: 225 raise ValueError("must specify mod 4 aligned address") 226 self._fcmd2("<BBW", 0x0E, 0x433249 | (addr << 24)) 227 228 def set_uart_baudrate(self, baudrate): 229 try: 230 baudrate = _uart_baud_table[baudrate] 231 except KeyError: 232 raise ValueError("invalid baudrate") 233 self._fcmd2("<BBB", 0x18, baudrate) 234 235 def set_startup_deco(self, value): 236 self._fcmd2("<BBB", 0x19, value) 237 238 def save_to_flash(self): 239 self._send(b"\x02fn") 240 241 #### PIXEL ACCESS #### 242 243 def set_pixel(self, x, y, c): 244 self._fcmd2b("<BBBBH", 0x41, x, y, c) 245 246 def get_pixel(self, x, y): 247 self._fcmd2("<BBBB", 0x61, x, y) 248 t = 1000 249 while t: 250 self.i2c.readfrom_into(self.i2c_addr, self.buf1) 251 if self.buf1[0] >= 2: 252 self.i2c.readfrom_into(self.i2c_addr, self.buf[3]) 253 return self.buf[3][1] | self.buf[3][2] << 8 254 t -= 1 255 sleep_ms(1) 256 raise OSError(uerrno.ETIMEDOUT) 257 258 def get_line(self, x, y, buf): 259 l = len(buf) // 2 260 self._fcmd2b("<BBBBB", 0x10, l, x, y) 261 l *= 2 262 t = 1000 263 while t: 264 self.i2c.readfrom_into(self.i2c_addr, self.buf1) 265 if self.buf1[0] >= l: 266 self.i2c.readfrom_into(self.i2c_addr, buf) 267 return 268 t -= 1 269 sleep_ms(1) 270 raise OSError(uerrno.ETIMEDOUT) 271 272 def screen_dump(self, buf, x=0, y=0, w=None, h=None): 273 if w is None: 274 w = self.w - x 275 if h is None: 276 h = self.h - y 277 if w <= 127: 278 line = bytearray(2 * w + 1) 279 line2 = None 280 else: 281 # split line if more than 254 bytes needed 282 buflen = (w + 1) // 2 283 line = bytearray(2 * buflen + 1) 284 line2 = memoryview(line)[: 2 * (w - buflen) + 1] 285 for i in range(min(len(buf) // (2 * w), h)): 286 ix = i * w * 2 287 self.get_line(x, y + i, line) 288 buf[ix : ix + len(line) - 1] = memoryview(line)[1:] 289 ix += len(line) - 1 290 if line2: 291 self.get_line(x + buflen, y + i, line2) 292 buf[ix : ix + len(line2) - 1] = memoryview(line2)[1:] 293 ix += len(line2) - 1 294 295 def screen_load(self, buf): 296 l = self.w * self.h * 2 + 2 297 self._fcmd2b("<BBHBBB", 0x70, l, 16, self.w, self.h) 298 n = 0 299 ar = memoryview(buf) 300 while n < len(buf): 301 if len(buf) - n >= 0x200: 302 self._send(ar[n : n + 0x200]) 303 n += 0x200 304 else: 305 self._send(ar[n:]) 306 while n < self.w * self.h * 2: 307 self._send(b"\x00") 308 n += 1 309 310 #### TEXT COMMANDS #### 311 312 def set_pos(self, x, y): 313 self._fcmd2("<BBBB", 0x58, x, y) 314 315 def set_text_color(self, fg, bg): 316 self._fcmd2("<BBHH", 0x63, fg, bg) 317 318 def set_font(self, font, scale=0, bold=0, trans=0, scroll=0): 319 self._fcmd2( 320 "<BBBB", 321 0x46, 322 (scroll << 7) | (trans << 6) | ((font & 3) << 4) | (bold & 0xF), 323 scale & 0xFF, 324 ) 325 326 def write(self, s): 327 # TODO: eventually check for room in LCD input queue 328 self._send(s) 329 330 #### PRIMITIVE DRAWING COMMANDS #### 331 332 def set_pen(self, line, fill): 333 self._fcmd2("<BBHH", 0x50, line, fill) 334 335 def erase(self): 336 self._send(b"\x02\x45") 337 338 def dot(self, x, y): 339 if 0 <= x < self.w and 0 <= y < self.h: 340 self._fcmd2("<BBBB", 0x4B, x, y) 341 342 def rect(self, x, y, w, h, cmd=0x72): 343 if x + w <= 0 or y + h <= 0 or x >= self.w or y >= self.h: 344 return 345 elif x < 0 or y < 0: 346 left = top = True 347 if x < 0: 348 left = False 349 w += x 350 x = 0 351 if y < 0: 352 top = False 353 h += y 354 y = 0 355 if cmd == 0x51 or cmd == 0x72: 356 # draw interior 357 self._fcmd2b("<BBBBBB", 0x51, x, y, min(w, 255), min(h, 255)) 358 if cmd == 0x57 or cmd == 0x72: 359 # draw outline 360 if left: 361 self._fcmd2b("<BBBBBB", 0x57, x, y, 1, min(h, 255)) 362 if top: 363 self._fcmd2b("<BBBBBB", 0x57, x, y, min(w, 255), 1) 364 if x + w < self.w: 365 self._fcmd2b("<BBBBBB", 0x57, x + w, y, 1, min(h, 255)) 366 if y + h < self.h: 367 self._fcmd2b("<BBBBBB", 0x57, x, y + h, min(w, 255), 1) 368 else: 369 self._fcmd2b("<BBBBBB", cmd, x, y, min(w, 255), min(h, 255)) 370 371 def rect_outline(self, x, y, w, h): 372 self.rect(x, y, w, h, 0x57) 373 374 def rect_interior(self, x, y, w, h): 375 self.rect(x, y, w, h, 0x51) 376 377 def line(self, x1, y1, x2, y2): 378 ar4 = self.array4 379 ar4[0] = x1 380 ar4[1] = y1 381 ar4[2] = x2 382 ar4[3] = y2 383 if self.clip_line(ar4, self.w, self.h): 384 self._fcmd2b("<BBBBBB", 0x4C, ar4[0], ar4[1], ar4[2], ar4[3]) 385 386 def dot_no_clip(self, x, y): 387 self._fcmd2("<BBBB", 0x4B, x, y) 388 389 def rect_no_clip(self, x, y, w, h): 390 self._fcmd2b("<BBBBBB", 0x72, x, y, w, h) 391 392 def rect_outline_no_clip(self, x, y, w, h): 393 self._fcmd2b("<BBBBBB", 0x57, x, y, w, h) 394 395 def rect_interior_no_clip(self, x, y, w, h): 396 self._fcmd2b("<BBBBBB", 0x51, x, y, w, h) 397 398 def line_no_clip(self, x1, y1, x2, y2): 399 self._fcmd2b("<BBBBBB", 0x4C, x1, y1, x2, y2) 400 401 def poly_dot(self, data): 402 if len(data) & 1: 403 raise ValueError("must specify even number of bytes") 404 self._fcmd2("<BBB", 0x71, len(data) // 2) 405 self._send(data) 406 407 def poly_line(self, data): 408 if len(data) & 1: 409 raise ValueError("must specify even number of bytes") 410 self._fcmd2("<BBB", 0x78, len(data) // 2) 411 self._send(data) 412 413 #### TOUCH COMMANDS #### 414 415 def touch_config(self, calib=False, save=False, irq=None): 416 self._fcmd2("<BBBB", 0x7A, (irq is not None) << 2 | save << 1 | calib, bool(irq) << 7) 417 418 def is_touched(self): 419 self._send(b"\x02T") 420 b = self.buf[4] 421 self._waitfor(3, b) 422 return b[1] >> 7 != 0 423 424 def get_touch(self): 425 self._send(b"\x02T") # implicit LCD output flush 426 b = self.buf[4] 427 self._waitfor(3, b) 428 return b[1] >> 7, b[2], b[3] 429 430 #### ADVANCED COMMANDS #### 431 432 def set_spi_win(self, x, y, w, h): 433 pack_into( 434 "<BBBHHHHHHHH", self.buf19, 0, 2, 0x55, 10, x, y, x + w - 1, y + h - 1, 0, 0, 0, 0xFFFF 435 ) 436 self._send(self.buf19) 437 438 def fast_spi(self, flush=True): 439 self._send(b"\x02\x12") 440 if flush: 441 self.oflush() 442 return self.spi 443 444 def show_framebuf(self, buf): 445 self.fast_spi().write(buf) 446 447 def set_scroll(self, on): 448 self._fcmd2("<BBB", 0x15, on) 449 450 def set_scroll_win(self, win, x=-1, y=0, w=0, h=0, vec=0, pat=0, fill=0x07E0, color=0): 451 pack_into("<BBBHHHHHHHH", self.buf19, 0, 2, 0x55, win, x, y, w, h, vec, pat, fill, color) 452 self._send(self.buf19) 453 454 def set_scroll_win_param(self, win, param, value): 455 self._fcmd2b("<BBBBH", 0x75, win, param, value) 456 457 def set_scroll_buf(self, s): 458 l = len(s) 459 if l > 32: 460 raise ValueError("length must be 32 or less") 461 self._fcmd2("<BBB", 0x11, l) 462 self._send(s) 463 464 def jpeg_start(self, l): 465 if l > 0xFFFF: 466 raise ValueError("length must be 65535 or less") 467 self.oflush() 468 self._fcmd2("<BBH", 0x6A, l) 469 470 def jpeg_data(self, buf): 471 self._send(buf) 472 473 def jpeg(self, buf): 474 self.jpeg_start(len(buf)) 475 self.jpeg_data(buf) 476 477 def feed_wdt(self): 478 self._send(b"\x02\x17") 479 480 def reset(self): 481 self._send(b"\x02Y\xef\xbe\xad\xde") 482 sleep_ms(15) 483