DEKI/ENGINE docs
deki-display 0.20.2
Deki package reference
deki-display

Deki's own display library: panel controllers on SPI, quad SPI or 8- and 16-bit parallel buses, touch controllers, and frames that never tear

Repository: github.com/dekiengine/deki-display · Latest release: 0.20.0 · Needs engine 0.20.0+ · Requires: deki-spi, deki-gpio, deki-i2c, deki-input, deki-rendering

Install with DekiEditor --packages-add deki-display <project>, or from the Package Manager. The engine's own components and C++ API are in the Deki docs.

Shown in the editor as Display.

Components

Component Category Description
DisplayPanelComponent Display Drives a display panel (ST7789, ILI9341, GC9A01, ...) over SPI, quad SPI or an 8- or 16-bit parallel bus with Deki's own display library, and gives it to the engine.
TouchPanelComponent Display Reads a panel's touch controller (GT911, CST816, FT5x06 on I2C; XPT2046 on SPI) with Deki's own display library, as pointer input.

Feature flags

Parts you can leave out of a build. A feature with a define is switched there; the rest are stripped with their components.

Feature Define Default
Display Panel DEKI_FEATURE_DISPLAY_PANEL on
Touch Panel DEKI_FEATURE_DISPLAY_TOUCH on

Component reference

DisplayPanelComponent

Drives a display panel (ST7789, ILI9341, GC9A01, ...) over SPI, quad SPI or an 8- or 16-bit parallel bus with Deki's own display library, and gives it to the engine.

Inspector name: Display Panel Component · Category: Display · Type: DekiDisplay::DisplayPanelComponent

Property Type Default Constraints Description
controller enum "ST7789" ST7789, RM67162, ILI9341, ST7735, ST7735S, GC9A01, ST7789P3, ST7796, ILI9488, SSD1351, CO5300 The panel's controller IC
initSequence string "" The commands that start this panel module, replacing the controller's default. A panel's voltages, VCOM and gamma belong to the module, not the controller, so a board sets them here. One command per line or ';': its hex byte, then its parameter bytes, then optionally 'delay <ms>'. '#' starts a comment. Empty: the controller's default
panelWidth int32 240 1 to 1024 Visible width in pixels, before rotation
panelHeight int32 320 1 to 1024 Visible height in pixels, before rotation
rotation enum "Landscape" Portrait, Landscape, Portrait180, Landscape270, PortraitMirror, LandscapeMirror, Portrait180Mirror, Landscape270Mirror How the panel is turned
offsetX int32 0 Where the visible panel starts in the controller's memory, across (for panels with a non-zero origin)
offsetY int32 0 Where the visible panel starts in the controller's memory, down
invertColor bool false Invert the panel's colours (most IPS panels need it on)
rgbOrder bool false The panel takes red first. Off sets the controller's BGR order
busType enum "SPI" SPI, QSPI, Parallel8, Parallel16 How the panel is wired: SPI with a data/command pin (most LCDs), quad SPI (AMOLED panels such as the RM67162), which needs an SPI Bus with IO2 and IO3, or an 8- or 16-bit parallel bus (8080), which this component starts itself
spiHost int32 1 0 to 3; shown when busType allows The SPI host the panel is on, as the SPI Bus component that starts it numbers it (on an ESP32-S3: 1 = SPI2, 2 = SPI3)
spiWriteHz int32 40000000 shown when busType allows SPI clock for the panel. 80 MHz is the ESP32-S3's fastest, and most ST7789 modules take it
parallelWriteHz int32 20000000 shown when busType allows Write strobe frequency on the parallel bus. Most panels take 20 MHz; some, with short wires, more
dcPin int32 -1 -1 to 48; shown when busType allows Data/command pin (DC, also called RS)
wrPin int32 -1 -1 to 48; shown when busType allows Write strobe pin (WR)
rdPin int32 -1 -1 to 48; shown when busType allows Read strobe pin (RD), held high: the panel is only written (-1 = tied high on the board)
d0Pin int32 -1 -1 to 48; shown when busType allows Data line D0
d1Pin int32 -1 -1 to 48; shown when busType allows Data line D1
d2Pin int32 -1 -1 to 48; shown when busType allows Data line D2
d3Pin int32 -1 -1 to 48; shown when busType allows Data line D3
d4Pin int32 -1 -1 to 48; shown when busType allows Data line D4
d5Pin int32 -1 -1 to 48; shown when busType allows Data line D5
d6Pin int32 -1 -1 to 48; shown when busType allows Data line D6
d7Pin int32 -1 -1 to 48; shown when busType allows Data line D7
d8Pin int32 -1 -1 to 48; shown when busType allows Data line D8
d9Pin int32 -1 -1 to 48; shown when busType allows Data line D9
d10Pin int32 -1 -1 to 48; shown when busType allows Data line D10
d11Pin int32 -1 -1 to 48; shown when busType allows Data line D11
d12Pin int32 -1 -1 to 48; shown when busType allows Data line D12
d13Pin int32 -1 -1 to 48; shown when busType allows Data line D13
d14Pin int32 -1 -1 to 48; shown when busType allows Data line D14
d15Pin int32 -1 -1 to 48; shown when busType allows Data line D15
csPin int32 -1 -1 to 48 Chip select pin (-1 = tied low)
rstPin int32 -1 -1 to 48 Reset pin (-1 = not connected)
tePin int32 -1 -1 to 48 The panel's tearing-effect (TE) output (-1 = not connected). With it, frames never tear: each starts as the panel starts a refresh, and goes in the panel's own row order, a turned screen turned as it is sent and drawn in strips of columns
scanlineSync bool true shown when busType allows With no TE line wired, read the panel's scanline instead, so frames still never tear. A panel that cannot be read back is sent as frames are drawn, and the boot log says so
spiReadHz int32 10000000 shown when scanlineSync allows SPI clock for reading the panel back, slower than writing it (the ST7789 reads reliably at 10 MHz)
readsOnMosi bool false shown when scanlineSync allows The panel answers on its data-in line (SDA both ways, as the LilyGO T-Deck's does), not on MISO
blPin int32 -1 -1 to 48 Backlight pin (-1 = none). Held high for on: a backlight driver dimmed by counting pulses (the LilyGO T-Deck's) wants exactly that
blInvert bool false The backlight is on when its pin is low
memoryWidth int32 0 0 to 1024 The controller's memory width, which may be more than the visible width (0 = the controller's)
memoryHeight int32 0 0 to 1024 The controller's memory height (0 = the controller's)
usePsram bool false Keep the framebuffer in PSRAM instead of internal RAM: frees a screen's worth of internal RAM, drawing is slower
doubleBuffer bool false Two framebuffers, swapped after each frame
asyncPresent bool true Send each frame from the chip's other core while the next is drawn: a frame costs the longer of drawing and sending instead of both. Needs a dual-core chip and two frames of external memory (PSRAM); without them, frames are sent as they are drawn, and the boot log says so

TouchPanelComponent

Reads a panel's touch controller (GT911, CST816, FT5x06 on I2C; XPT2046 on SPI) with Deki's own display library, as pointer input.

Inspector name: Touch Panel Component · Category: Display · Type: DekiDisplay::TouchPanelComponent

Property Type Default Constraints Description
controller enum "GT911" GT911, CST816, FT5x06, XPT2046 The touch controller IC
i2cPort int32 0 0 to 3; shown when controller allows The I2C port it is on (an I2C Bus component must start that port before this one)
i2cAddress int32 0 0 to 127; shown when controller allows Its I2C address; 0 takes the controller's usual one (GT911: 0x5D, falling back to 0x14; CST816: 0x15; FT5x06: 0x38)
spiHost int32 1 0 to 3; shown when controller allows The SPI host it is on, as the SPI Bus component that starts it numbers it (often the display's)
csPin int32 -1 -1 to 48; shown when controller allows Its chip select pin
spiHz int32 1000000 shown when controller allows SPI clock for the touch controller. The XPT2046 reads well at 1 MHz and takes up to 2.5 MHz
intPin int32 -1 -1 to 48 Interrupt pin: the controller pulls it low when a touch report is waiting, or while pressed (the XPT2046's PENIRQ) (-1 = not wired, read every frame)
rstPin int32 -1 -1 to 48; shown when controller allows Reset pin (-1 = not connected)
xMin int32 0 Minimum raw X from the touch controller, along the panel's width before the display is rotated (capacitive: 0; an XPT2046 reads from about 300 and needs calibrating)
xMax int32 239 Maximum raw X from the touch controller, along the panel's width before the display is rotated (a 240x320 panel shown in landscape: 239)
yMin int32 0 Minimum raw Y from the touch controller, along the panel's height before the display is rotated
yMax int32 319 Maximum raw Y from the touch controller, along the panel's height before the display is rotated (a 240x320 panel shown in landscape: 319)
offsetRotation enum "None" None, CW_90, CW_180, CW_270, Mirror, Mirror_CW_90, Mirror_CW_180, Mirror_CW_270 How the touch controller is turned against the panel, added to the display rotation. None when the controller reports in the panel's own orientation

Changelog

0.20.0

Changed

  • minEngine 0.20.0: built against engine 0.20.0. A build of this package from before does not load and is rebuilt.

Added

  • The package: DisplayPanelComponent, which starts a panel on a deki-spi bus from the boot scene and gives the engine PanelDisplay, a Deki::IDisplay presenting whole frames and changed rectangles with the UI overlay blended over them; PanelController, a MIPI DCS controller driven by a table per controller (ST7789 first, its start-up and MADCTL ported from LovyanGFX); SpiPanelIO, 4-wire SPI with pixels queued for DMA; and initSequence, a board's own start-up for its panel module, as text.
  • Async present (asyncPresent): each frame is sent from the other core while the next is drawn. A whole frame is copied, swapped into the wire's byte order as it is copied, to one of two copies in external DMA memory, and sent from there by DMA in parts as long as the bus takes, with no bands in internal RAM; a strip is copied to one of two copies in internal DMA memory and swapped in place a chunk at a time. The worker holds the bus across back-to-back frames and strips, with each one's window commands queued behind the one before, so the wire does not wait between them; a copy goes back to the drawing core as soon as its last part is sent. The platform runs the worker through PanelDisplay::SetWorkerBackend (deki-esp32-integration: a FreeRTOS task pinned to the other core). On a LilyGO T-Deck Plus at 80 MHz with the frame rate uncapped: 63.2 fps in whole frames with 83.6 KB of internal RAM free, 61.0 in strips of 40 rows (LovyanGFX: 62.6 with 73.8 KB, and 60.2).
  • Touch: TouchPanelComponent starts a GT911 (GT911Touch, ported from LovyanGFX's driver) on a deki-i2c bus, shared with whatever else is on it, and gives deki-input PanelTouchInput: a press, moves and a release at screen pixels, turned with the display by TouchMapper (LovyanGFX's default calibration and rotation, so a board's touch bounds carry over). Without an INT pin, a point that stops changing is released and not pressed again until it moves.
  • QSPI panels: DisplayPanelComponent's busType QSPI drives an AMOLED through QspiPanelIO (each register write framed 0x02, pixels 0x32 on four lines, as LovyanGFX frames them). The RM67162 is the first: its start-up, MADCTL and offsets ported from LovyanGFX, rows widened to even, and brightness by command where an LCD has a backlight.
  • The CST816S/T touch controller (TouchController::CST816). It sleeps, and does not answer on I2C, until touched: it is set up when it first answers, and a read it does not answer is no touch.
  • Async present works with render buffers (usePsram, doubleBuffer) too: the worker sends a copy, so the engine never draws into what is being sent.
  • Tear-free presenting: DisplayPanelComponent's tePin, the panel's tearing-effect output. A panel refreshes its own rows one after another, and a frame written while the refresh passes shows half of each frame. With TE on (0x35), each frame starts as the panel starts a refresh, and goes in the panel's own row order, which a write outruns the refresh in. A screen shown turned is turned as it is sent: the controller stays at its own orientation, and the renderer is asked for strips of columns in the order the panel refreshes them (deki-rendering's IScanOrderedDisplay). With async present the worker does the turning, a band at a time while the band before it is on the wire. Whole frames and strips both. A TE line that never pulses is said once in the log, and frames go on without waiting. On a LilyGO T-Display S3 AMOLED (its panel refreshes at 59.6 Hz): 59.5 fps in strips with no tearing, and 28.6 in whole frames, which take 25 ms to draw in PSRAM.
  • More controllers, each a table ported from LovyanGFX 1.2.29: ILI9341, ST7735 (the B version), ST7735S (the R version), GC9A01, ST7789P3, ST7796, ILI9488 and SSD1351 on SPI or a parallel bus, and the CO5300 AMOLED on QSPI. A controller names its own commands (ControllerCommands): the SSD1351's window is a byte a coordinate, its column and row commands trade places when turned, and its remap takes MADCTL's place. The GC9A01 opens its rows before a turned window's columns, as LovyanGFX does. Checked against LovyanGFX's bytes in tests; none has run on a board yet.
  • 18 bits a pixel: the ILI9488 takes no RGB565 over SPI, so its pixels go out as RGB666 through Rgb666PanelIO, which widens each piece into its own DMA buffers while the piece before is sent, and COLMOD is 0x66.
  • Parallel panels: busType Parallel8 and Parallel16, an 8080 bus of 8 or 16 data lines with DC, WR and RD pins and its write clock (parallelWriteHz). The platform drives the bus (ParallelBus, a factory it registers; ParallelPanelIO speaks to it), and the component starts it itself. On 16 lines a pixel goes as memory holds it, so a little-endian frame needs no byte swap.
  • Touch: the FT5x06 family (FT5206, FT6206, FT6336 and others) on I2C, and the XPT2046 resistive controller on SPI, both ported from LovyanGFX. The XPT2046's raw readings need calibrating in the touch bounds. A controller whose count or pressure says when the finger has gone (ITouchController::MayRepeatLastTouch false) is not released for holding still.
  • Tear-free: a frame that missed its refresh's start is started for the longest write any frame has taken (PanelDisplay::LongestWriteUs), not the shorter of the last two. The frame that opens a new scene is late (the scene loads in it) and changes the whole screen, while the frames before it changed a little: started for theirs, it began too late, the refresh caught it, and the screen tore across.
  • Tear-free with no TE line: scanlineSync reads the panel's scanline (Get_Scanline, 0x45) back through the panel IO (IPanelIO::Read) at its own clock (spiReadHz), on MISO or, with readsOnMosi, on the panel's data line. ScanlineClock finds two refresh starts, where the count drops from the last row to the blanking (it holds there; the low byte rolling over at row 256 does not), and keeps the clock right with each start it catches. On a LilyGO T-Deck Plus: 60.2 Hz found, every demo scene at 60 fps with frames in step with the refresh.
  • A frame that takes about a refresh to go out, or up to two, is started just behind the refresh rather than at once (PanelDisplay::WritesBehind): it never catches the refresh's rows up, and the next refresh shows it whole. Before, such a frame was taken to tear wherever it started.
  • A turned frame is read a screen row at a time while it is turned, a run of neighbouring pixels from each, instead of one pixel per screen row: a whole frame in PSRAM, turned on the way out, went from 47.8 fps to 60 on the T-Deck Plus.
  • asyncPresent and scanlineSync are on by default: a board gets frames sent from the other core, and tear-free frames without a TE line, where it can. Where it cannot (one core, no PSRAM for the copies, a panel that cannot be read back) frames are sent as they are drawn, and the boot log says so as information, not an error. Scenes that set them keep what they set.

Earlier releases: CHANGELOG.md.


Generated from the package's reflection data (engine 0.20.2). Property names are the keys used in scene files. Renames keep an alias (compatibility).