adds rotary encoder+SPI based Display: ili9341

This commit is contained in:
Robert Schauklies 2025-08-23 18:55:01 +02:00
parent dd2a5bf637
commit a8f8586756
11 changed files with 2184 additions and 269 deletions

327
src/ili934xnew.py Normal file
View file

@ -0,0 +1,327 @@
# 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