lcd160cr.py 14 KB

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