# This is an adapted version of the ILI934X driver as below. # It works with multiple fonts and also works with the esp32 H/W SPI implementation # Also includes a word wrap print function # Proportional fonts are generated by Peter Hinch's Font-to-py # MIT License; Copyright (c) 2017 Jeffrey N. Magee # This file is part of MicroPython ILI934X driver # Copyright (c) 2016 - 2017 Radomir Dopieralski, Mika Tuupola # # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php # # Project home: # https://github.com/tuupola/micropython-ili934x import time import ustruct import glcdfont import framebuf from micropython import const _RDDSDR = const(0x0f) # Read Display Self-Diagnostic Result _SLPOUT = const(0x11) # Sleep Out _GAMSET = const(0x26) # Gamma Set _DISPOFF = const(0x28) # Display Off _DISPON = const(0x29) # Display On _CASET = const(0x2a) # Column Address Set _PASET = const(0x2b) # Page Address Set _RAMWR = const(0x2c) # Memory Write _RAMRD = const(0x2e) # Memory Read _MADCTL = const(0x36) # Memory Access Control _VSCRSADD = const(0x37) # Vertical Scrolling Start Address _PIXSET = const(0x3a) # Pixel Format Set _PWCTRLA = const(0xcb) # Power Control A _PWCRTLB = const(0xcf) # Power Control B _DTCTRLA = const(0xe8) # Driver Timing Control A _DTCTRLB = const(0xea) # Driver Timing Control B _PWRONCTRL = const(0xed) # Power on Sequence Control _PRCTRL = const(0xf7) # Pump Ratio Control _PWCTRL1 = const(0xc0) # Power Control 1 _PWCTRL2 = const(0xc1) # Power Control 2 _VMCTRL1 = const(0xc5) # VCOM Control 1 _VMCTRL2 = const(0xc7) # VCOM Control 2 _FRMCTR1 = const(0xb1) # Frame Rate Control 1 _DISCTRL = const(0xb6) # Display Function Control _ENA3G = const(0xf2) # Enable 3G _PGAMCTRL = const(0xe0) # Positive Gamma Control _NGAMCTRL = const(0xe1) # Negative Gamma Control _CHUNK = const(1024) #maximum number of pixels per spi write def color565(r, g, b): return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 class ILI9341: def __init__(self, spi, cs, dc, rst, w, h, r): self.spi = spi self.cs = cs self.dc = dc self.rst = rst self._init_width = w self._init_height = h self.width = w self.height = h self.rotation = r self.cs.init(self.cs.OUT, value=1) self.dc.init(self.dc.OUT, value=0) self.rst.init(self.rst.OUT, value=0) self.reset() self.init() self._scroll = 0 self._buf = bytearray(_CHUNK * 2) self._colormap = bytearray(b'\x00\x00\xFF\xFF') #default white foregraound, black background self._x = 0 self._y = 0 self._font = glcdfont self.scrolling = False def set_color(self,fg,bg): self._colormap[0] = bg>>8 self._colormap[1] = bg & 255 self._colormap[2] = fg>>8 self._colormap[3] = fg & 255 def set_pos(self,x,y): self._x = x self._y = y def reset_scroll(self): self.scrolling = False self._scroll = 0 self.scroll(0) def set_font(self, font): self._font = font def init(self): for command, data in ( (_RDDSDR, b"\x03\x80\x02"), (_PWCRTLB, b"\x00\xc1\x30"), (_PWRONCTRL, b"\x64\x03\x12\x81"), (_DTCTRLA, b"\x85\x00\x78"), (_PWCTRLA, b"\x39\x2c\x00\x34\x02"), (_PRCTRL, b"\x20"), (_DTCTRLB, b"\x00\x00"), (_PWCTRL1, b"\x23"), (_PWCTRL2, b"\x10"), (_VMCTRL1, b"\x3e\x28"), (_VMCTRL2, b"\x86")): self._write(command, data) if self.rotation == 0: # 0 deg self._write(_MADCTL, b"\x48") self.width = self._init_height self.height = self._init_width elif self.rotation == 1: # 90 deg self._write(_MADCTL, b"\x28") self.width = self._init_width self.height = self._init_height elif self.rotation == 2: # 180 deg self._write(_MADCTL, b"\x88") self.width = self._init_height self.height = self._init_width elif self.rotation == 3: # 270 deg self._write(_MADCTL, b"\xE8") self.width = self._init_width self.height = self._init_height elif self.rotation == 4: # Mirrored + 0 deg self._write(_MADCTL, b"\xC8") self.width = self._init_height self.height = self._init_width elif self.rotation == 5: # Mirrored + 90 deg self._write(_MADCTL, b"\x68") self.width = self._init_width self.height = self._init_height elif self.rotation == 6: # Mirrored + 180 deg self._write(_MADCTL, b"\x08") self.width = self._init_height self.height = self._init_width elif self.rotation == 7: # Mirrored + 270 deg self._write(_MADCTL, b"\xA8") self.width = self._init_width self.height = self._init_height else: self._write(_MADCTL, b"\x08") for command, data in ( (_PIXSET, b"\x55"), (_FRMCTR1, b"\x00\x18"), (_DISCTRL, b"\x08\x82\x27"), (_ENA3G, b"\x00"), (_GAMSET, b"\x01"), (_PGAMCTRL, b"\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"), (_NGAMCTRL, b"\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f")): self._write(command, data) self._write(_SLPOUT) time.sleep_ms(120) self._write(_DISPON) def reset(self): self.rst(0) time.sleep_ms(50) self.rst(1) time.sleep_ms(50) def _write(self, command, data=None): self.dc(0) self.cs(0) self.spi.write(bytearray([command])) self.cs(1) if data is not None: self._data(data) def _data(self, data): self.dc(1) self.cs(0) self.spi.write(data) self.cs(1) def _writeblock(self, x0, y0, x1, y1, data=None): self._write(_CASET, ustruct.pack(">HH", x0, x1)) self._write(_PASET, ustruct.pack(">HH", y0, y1)) self._write(_RAMWR, data) def _readblock(self, x0, y0, x1, y1, data=None): self._write(_CASET, ustruct.pack(">HH", x0, x1)) self._write(_PASET, ustruct.pack(">HH", y0, y1)) if data is None: return self._read(_RAMRD, (x1 - x0 + 1) * (y1 - y0 + 1) * 3) def _read(self, command, count): self.dc(0) self.cs(0) self.spi.write(bytearray([command])) data = self.spi.read(count) self.cs(1) return data def pixel(self, x, y, color=None): if color is None: r, b, g = self._readblock(x, y, x, y) return color565(r, g, b) if not 0 <= x < self.width or not 0 <= y < self.height: return self._writeblock(x, y, x, y, ustruct.pack(">H", color)) def fill_rectangle(self, x, y, w, h, color=None): x = min(self.width - 1, max(0, x)) y = min(self.height - 1, max(0, y)) w = min(self.width - x, max(1, w)) h = min(self.height - y, max(1, h)) if color: color = ustruct.pack(">H", color) else: color = self._colormap[0:2] #background for i in range(_CHUNK): self._buf[2*i]=color[0]; self._buf[2*i+1]=color[1] chunks, rest = divmod(w * h, _CHUNK) self._writeblock(x, y, x + w - 1, y + h - 1, None) if chunks: for count in range(chunks): self._data(self._buf) if rest != 0: mv = memoryview(self._buf) self._data(mv[:rest*2]) def erase(self): self.fill_rectangle(0, 0, self.width, self.height) def blit(self, bitbuff, x, y, w, h): x = min(self.width - 1, max(0, x)) y = min(self.height - 1, max(0, y)) w = min(self.width - x, max(1, w)) h = min(self.height - y, max(1, h)) chunks, rest = divmod(w * h, _CHUNK) self._writeblock(x, y, x + w - 1, y + h - 1, None) written = 0 for iy in range(h): for ix in range(w): index = ix+iy*w - written if index >=_CHUNK: self._data(self._buf) written += _CHUNK index -= _CHUNK c = bitbuff.pixel(ix,iy) self._buf[index*2] = self._colormap[c*2] self._buf[index*2+1] = self._colormap[c*2+1] rest = w*h - written if rest != 0: mv = memoryview(self._buf) self._data(mv[:rest*2]) def chars(self, str, x, y): str_w = self._font.get_width(str) div, rem = divmod(self._font.height(),8) nbytes = div+1 if rem else div buf = bytearray(str_w * nbytes) pos = 0 for ch in str: glyph, char_w = self._font.get_ch(ch) for row in range(nbytes): index = row*str_w + pos for i in range(char_w): buf[index+i] = glyph[nbytes*i+row] pos += char_w fb = framebuf.FrameBuffer(buf,str_w, self._font.height(), framebuf.MONO_VLSB) self.blit(fb,x,y,str_w,self._font.height()) return x+str_w def scroll(self, dy): self._scroll = (self._scroll + dy) % self.height self._write(_VSCRSADD, ustruct.pack(">H", self._scroll)) def next_line(self, cury, char_h): global scrolling if not self.scrolling: res = cury + char_h self.scrolling = (res >= self.height) if self.scrolling: self.scroll(char_h) res = (self.height - char_h + self._scroll)%self.height self.fill_rectangle(0, res, self.width, self._font.height()) return res def write(self, text): #does character wrap, compatible with stream output curx = self._x; cury = self._y char_h = self._font.height() width = 0 written = 0 for pos, ch in enumerate(text): if ch == '\n': if pos>0: self.chars(text[written:pos],curx,cury) curx = 0; written = pos+1; width = 0 cury = self.next_line(cury,char_h) else: char_w = self._font.get_width(ch) if curx + width + char_w >= self.width: self.chars(text[written:pos], curx,cury) curx = 0 ; written = pos; width = char_h cury = self.next_line(cury,char_h) else: width += char_w if written<len(text): curx = self.chars(text[written:], curx,cury) self._x = curx; self._y = cury def print(self, text): #does word wrap, leaves self._x unchanged cury = self._y; curx = self._x char_h = self._font.height() char_w = self._font.max_width() lines = text.split('\n') for line in lines: words = line.split(' ') for word in words: if curx + self._font.get_width(word) >= self.width: curx = self._x; cury = self.next_line(cury,char_h) while self._font.get_width(word) > self.width: self.chars(word[:self.width//char_w],curx,cury) word = word[self.width//char_w:] cury = self.next_line(cury,char_h) if len(word)>0: curx = self.chars(word+' ', curx,cury) curx = self._x; cury = self.next_line(cury,char_h) self._y = cury