ILI9486 update.

encoder_driver
peterhinch 2023-01-13 16:41:33 +00:00
rodzic e2fb78818a
commit 30ffd8b23b
2 zmienionych plików z 51 dodań i 55 usunięć

Wyświetl plik

@ -4,11 +4,14 @@
# Copyright (c) Peter Hinch 2022
# Released under the MIT license see LICENSE
# Inspired by @brave-ulysses https://github.com/micropython/micropython/discussions/10404
# Much help provided by @brave-ulysses in this thread
# https://github.com/micropython/micropython/discussions/10404 for the special handling
# required by the Waveshare Pi HAT.
# Design note. I could not find a way to do landscape display at the chip level
# without a nasty hack. Consequently this driver uses portrait mode at chip level,
# with rotation performed in the driver.
# This driver configures the chip in portrait mode with rotation performed in the driver.
# This is done to enable default values to be used for the Column Address Set and Page
# Address Set registers. This avoids having to use commands with multi-byte data values,
# which would necessitate special code for the Waveshare Pi HAT (see DRIVERS.md).
from time import sleep_ms
import gc
@ -18,21 +21,22 @@ from drivers.boolpalette import BoolPalette
# Portrait mode
@micropython.viper
def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int):
def _lcopy(dest: ptr16, source: ptr8, lut: ptr16, length: int):
# rgb565 - 16bit/pixel
n = 0
for x in range(length):
c = source[x]
dest[n] = lut[c >> 4] # current pixel
n += 1
dest[n] = lut[c & 0x0f] # next pixel
dest[n] = lut[c & 0x0F] # next pixel
n += 1
# FB is in landscape mode, hence issue a column at a time to portrait mode hardware.
@micropython.viper
def _lscopy(dest:ptr16, source:ptr8, lut:ptr16, ch:int):
col = ch & 0x1ff # Unpack (viper 4 parameter limit)
height = (ch >> 9) & 0x1ff
def _lscopy(dest: ptr16, source: ptr8, lut: ptr16, ch: int):
col = ch & 0x1FF # Unpack (viper 4 parameter limit)
height = (ch >> 9) & 0x1FF
wbytes = ch >> 19 # Width in bytes is width // 2
# rgb565 - 16bit/pixel
n = 0
@ -40,7 +44,7 @@ def _lscopy(dest:ptr16, source:ptr8, lut:ptr16, ch:int):
idx = col >> 1 # 2 pixels per byte
for _ in range(height):
if clsb:
c = source[idx] & 0x0f
c = source[idx] & 0x0F
else:
c = source[idx] >> 4
dest[n] = lut[c] # 16 bit transfer of rightmost 4-bit pixel
@ -58,7 +62,7 @@ class ILI9486(framebuf.FrameBuffer):
# ILI9486 expects RGB order. 8 bit register writes require padding
@staticmethod
def rgb(r, g, b):
return (r & 0xf8) | (g & 0xe0) >> 5 | (g & 0x1c) << 11 | (b & 0xf8) << 5
return (r & 0xF8) | (g & 0xE0) >> 5 | (g & 0x1C) << 11 | (b & 0xF8) << 5
# Transpose width & height for landscape mode
def __init__(self, spi, cs, dc, rst, height=320, width=480, usd=False, init_spi=False):
@ -71,12 +75,12 @@ class ILI9486(framebuf.FrameBuffer):
self._long = max(height, width) # Physical dimensions of screen and aspect ratio
self._short = min(height, width)
self._spi_init = init_spi
pmode = framebuf.GS4_HMSB
self.palette = BoolPalette(pmode)
mode = framebuf.GS4_HMSB
self.palette = BoolPalette(mode)
gc.collect()
buf = bytearray(height * width // 2)
self._mvb = memoryview(buf)
super().__init__(buf, width, height, pmode) # Logical aspect ratio
super().__init__(buf, width, height, mode) # Logical aspect ratio
self._linebuf = bytearray(self._short * 2)
# Hardware reset
@ -89,64 +93,63 @@ class ILI9486(framebuf.FrameBuffer):
self._lock = asyncio.Lock()
# Send initialization commands
self._wcmd(b'\x01') # SWRESET Software reset
self._wcmd(b"\x01") # SWRESET Software reset
sleep_ms(100)
self._wcmd(b'\x11') # sleep out
self._wcmd(b"\x11") # sleep out
sleep_ms(20)
self._wcd(b'\x3a', b'\x55') # interface pixel format
self._wcd(b'\x36', b'\x48' if usd else b'\x88') # MADCTL: RGB portrait mode
self._wcmd(b'\x11') # sleep out
self._wcmd(b'\x29') # display on
# Write data.
def _wdata(self, data):
self._dc(1)
self._cs(0)
self._spi.write( data )
self._cs(1)
self._wcd(b"\x3a", b"\x55") # interface pixel format
# Normally use defaults. This allows it to work on the Waveshare board with a
# shift register. If size is not 320x480 assume no shift register.
# Default column address start == 0, end == 0x13F (319)
if self._short != 320: # Not the Waveshare board: no shift register
self._wcd(b"\x2a", int.to_bytes(self._short - 1, 4, "big"))
# Default page address start == 0 end == 0x1DF (479)
if self._long != 480:
self._wcd(b"\x2b", int.to_bytes(self._long - 1, 4, "big")) # SET_PAGE ht
self._wcd(b"\x36", b"\x48" if usd else b"\x88") # MADCTL: RGB portrait mode
self._wcmd(b"\x11") # sleep out
self._wcmd(b"\x29") # display on
# Write a command.
def _wcmd(self, command):
self._dc(0)
self._cs(0)
self._spi.write( command )
self._spi.write(command)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, command, data):
self._dc(0)
self._cs(0)
self._spi.write( command )
self._spi.write(command)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write( data )
self._spi.write(data)
self._cs(1)
@micropython.native
# @micropython.native # Made almost no difference to timing
def show(self): # Physical display is in portrait mode
clut = ILI9486.lut
lb = self._linebuf
buf = self._mvb
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
# Commands needed to start data write
self._wcd(b'\x2a', int.to_bytes(self._short -1, 4, 'big')) # SET_COLUMN works 0 .. width
self._wcd(b'\x2b', int.to_bytes(self._long -1, 4, 'big')) # SET_PAGE ht
self._wcmd(b'\x2c') # WRITE_RAM
self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1)
self._cs(0)
if self.width < self.height: # Portrait 214ms on RP2 120MHz, 30MHz SPI clock
wd = self.width // 2
ht = self.height
for start in range(0, wd*ht, wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
for start in range(0, wd * ht, wd): # For each line
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
else: # Landscpe 264ms on RP2 120MHz, 30MHz SPI clock
cargs = (self.height << 9) + (self.width << 18) # Viper 4-arg limit
for col in range(self.width -1, -1, -1): # For each column of landscape display
_lscopy(lb, buf, clut, col + cargs) # Copy and map colors
width = self.width
wd = width - 1
cargs = (self.height << 9) + (width << 18) # Viper 4-arg limit
for col in range(width): # For each column of landscape display
_lscopy(lb, buf, clut, wd - col + cargs) # Copy and map colors
self._spi.write(lb)
self._cs(1)
@ -154,31 +157,28 @@ class ILI9486(framebuf.FrameBuffer):
async with self._lock:
lines, mod = divmod(self._long, split) # Lines per segment
if mod:
raise ValueError('Invalid do_refresh arg.')
raise ValueError("Invalid do_refresh arg.")
clut = ILI9486.lut
lb = self._linebuf
buf = self._mvb
self._wcd(b'\x2a', int.to_bytes(self._short -1, 4, 'big')) # SET_COLUMN works 0 .. width
self._wcd(b'\x2b', int.to_bytes(self._long -1, 4, 'big')) # SET_PAGE ht
self._wcmd(b'\x2c') # WRITE_RAM
self._wcmd(b"\x2c") # WRITE_RAM
self._dc(1)
if self.width < self.height: # Portrait: write sets of rows
wd = self.width // 2
ht = self.height
line = 0
for _ in range(split): # For each segment
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._cs(0)
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)
else: # Landscape: write sets of cols. lines is no. of cols per segment.
cargs = (self.height << 9) + (self.width << 18) # Viper 4-arg limit
sc = self.width -1 # Start and end columns
sc = self.width - 1 # Start and end columns
ec = sc - lines # End column
for _ in range(split): # For each segment
if self._spi_init: # A callback was passed
@ -191,4 +191,3 @@ class ILI9486(framebuf.FrameBuffer):
ec -= lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)

Wyświetl plik

@ -7,13 +7,10 @@
# Also for suggesting abstracting the input device class.
import uasyncio as asyncio
from uasyncio import Event
from time import ticks_diff, ticks_ms
import gc
from gui.core.colors import *
from hardware_setup import ssd
from gui.primitives import Pushbutton
# Globally available singleton objects
@ -268,11 +265,11 @@ class Display(DisplayIP):
class Screen:
do_gc = True # Allow user to take control of GC
current_screen = None
is_shutdown = Event()
is_shutdown = asyncio.Event()
# These events enable user code to synchronise display refresh
# to a realtime process.
rfsh_start = Event() # Refresh pauses until set (set by default).
rfsh_done = Event() # Flag a user task that a refresh was done.
rfsh_start = asyncio.Event() # Refresh pauses until set (set by default).
rfsh_done = asyncio.Event() # Flag a user task that a refresh was done.
@classmethod # Called by Input when status change needs redraw of current obj
def redraw_co(cls):