Display Configuration

Cuttlefish UI talks to physical displays through a display block in your cuttlefish.config.ts. You pick a built-in display profile (or name a driver) and tell Cuttlefish which pins it’s wired to; when you build, it generates the right driver code for you.

This page covers the display wizard, the common profiles, the full list of configuration fields, touch input, and — for unusual displays — how to wire up a brand-new driver.

Display wizard

The wizard asks which display you have and how it is wired, then writes the settings for you. It is the fastest way to get a screen running.

Run it from your project directory:

npx @typecad/ui --config

If you do not have a project yet, create one first with npx @typecad/cuttlefish create.

The wizard asks which display you are using, which pins it is connected to, how the screen is rotated, whether to smooth text edges (antialiasing), and how touch is wired. Each question suggests the common answer — press Enter to accept it.

Before anything is written, the wizard shows you the finished settings and asks you to confirm. It then does three things:

  1. Writes the display settings into cuttlefish.config.ts. The rest of the file is left alone.
  2. Adds a preview script to package.json, so npm run preview shows your screen in a browser.
  3. Creates a starter screen file if you do not have one yet.

The wizard warns you if the display and the touch panel are set to the same pin.

You can run the wizard again at any time. Your previous answers are filled in, and nothing is changed without your confirmation.

Minimal SPI example (ST7796S TFT)

import type { CuttlefishConfig } from '@typecad/cuttlefish/api';

const config: CuttlefishConfig = {
  entry: './src/app.ui',
  target: 'esp32s3',
  board: 'esp32s3_devkitc/esp32s3/procpu',
  framework: '@typecad/framework-zephyr',
  frameworkData: { buildTarget: 'esp32s3_devkitc/esp32s3/procpu' },
  output: { framework: 'zephyr', outDir: './out' },
  display: {
    profile: 'st7796-zephyr',
    cs: 5,
    dc: 17,
    rst: 16,
    spiFrequency: 80000000,
    colorOrder: 'bgr',
    invertDisplay: false,
    antialias: true,
  },
};

export default config;

Minimal mono example (SSD1306 OLED)

display: {
  profile: 'ssd1306-zephyr',
  width: 128,
  height: 64,
  rotation: 0,
}

The mono profile drives direct display.* drawing — the HTML/CSS UI adapter does not lower to 1-bit panels.

Profile fields

FieldTypeDescription
profilestringBuilt-in profile name (e.g. "st7796-zephyr") — fills in driver, width, height, colorFormat, rotation
driverstringDisplay driver id (e.g. "ili9341", or a Zephyr id like "ili9341-zephyr") — set explicitly to override the profile
width / heightnumberVisible layout dimensions in pixels (after rotation)
nativeWidth / nativeHeightnumberNative panel dimensions before rotation, when they differ from the layout size
colorFormat"rgb565" | "rgb888" | "mono"Color depth
displayClass"tft" | "eink" | "oled"Panel class (drives refresh behavior)
capabilitiesobjectDeclared adapter capabilities (e.g. partial refresh)
rotationnumber0 = portrait, 1 = landscape, 2-3 = inverted
backlightPinnumberZephyr: backlight GPIO — emits a gpio-leds DT node + backlight alias. Omit when the backlight is hardwired to power
cs / dc / rstnumberSPI wiring pins
busstringBus identifier ("SPI" or "I2C")
addressnumberI2C address (I2C displays)
resetnumberReset pin for I2C displays (-1 if unwired)
spiFrequencynumberSPI frequency override
spiPins{ mosi, sck, miso }Explicit SPI wiring (Zephyr DT overlay)
colorOrder"rgb" | "bgr"Pixel color order
invertDisplaybooleanInvert panel colors
antialiasbooleanEnable text antialiasing
scanlineSyncbooleanOpt in to experimental GET_SCANLINE dirty-rectangle sync. Off by default — some ST7796S modules misbehave when reading that command
fullscreenboolean | "desktop"SDL desktop only: borderless fullscreen
title / iconstringSDL desktop only: window title / taskbar icon (BMP)
themeCssstringPath to an extra CSS file with theme rules
themeClassstringClass name selecting a build-time theme variant
scrollScrollConfigScroll engine tuning — see below
touchobjectTouch-input config (see below)

Scroll tuning (scroll)

All fields optional — defaults derive from the declared touch hardware:

FieldTypeDescription
inputTier"capacitive" | "resistive" | "none"Input class
renderTier"full" | "constrained"Whether per-frame canvas repaint (incl. rubber-band) is affordable
dragScalenumberMultiplier on drag deltas (default 1.0)
maxOverscrollnumberRubber-band excursion ceiling in px (default 40)
stiffnessnumberRubber-band resistance (higher = stiffer, default 0.5)
edgeSnapPxnumberFree snap distance to a boundary (default 12)
inputSmoothingnumberLow-pass coefficient for resistive input (default 0.3; capacitive always passthrough)

Built-in profiles

ProfileDimensionsColorNotes
st7796-zephyr320×480RGB565ST7796S SPI TFT; hardware-tuned; add touch via touch config
ili9341-zephyr320×240RGB565ILI9341 SPI TFT; UI adapter ported, not yet hardware-verified
ssd1306-zephyr128×64MonoDirect display.* ops only (no HTML/CSS UI adapter)

Registered display drivers

Beyond the built-in profiles, the transpiler registers drivers you can target directly by setting driver (and width/height/colorFormat):

DriverTargetsStatus
st7796-zephyrST7796S SPI TFT (RGB565)✅ direct SPI¹ — hardware-tuned
ili9341-zephyrILI9341 SPI TFT (RGB565)✅ direct SPI¹ — UI adapter ported, not yet hardware-verified
ssd1306-zephyrSSD1306 OLED (128×64 mono)✅ direct display.* ops²
sdlNative desktop window (RGB888)✅ Native framework only

The Zephyr framework generates native display/touch adapters — no third-party display library dependency. The display devicetree overlay, pinctrl remux, DMA, and backlight alias, plus the matching prj.conf Kconfig symbols (CONFIG_DISPLAY, CONFIG_SPI, CONFIG_I2C, CONFIG_DMA), are generated from a usage scan of your code. See Zephyr framework for details.

¹ The ST7796S and ILI9341 are driven over raw spi_write() with manual CS/DC/RST GPIO — the adapter deliberately bypasses Zephyr’s mipi-dbi-spi bridge and disables the in-tree panel drivers (CONFIG_ST7796S / CONFIG_ILI9341).

² The SSD1306 profile uses a page framebuffer under the GFX runtime for direct display.* drawing; the @typecad/ui HTML/CSS adapter does not lower to mono displays.

Touch input

Add a touch block to enable touch. Two controllers have native adapters, both polled directly (no in-tree driver): FT6336U (capacitive, over I2C) and XPT2046 (resistive, over SPI). Controllers not listed here need a custom adapter (below).

FT6336U (I2C capacitive)

touch: {
  library: 'FT6336U',
  i2cAddress: 0x38,             // default 0x38
  i2cFrequency: 400000,         // default 400 kHz
  sda: 8,                       // I2C wiring — configures the bus pinctrl
  scl: 9,                       // in the devicetree overlay
  resetPin: 4,                  // hardware-reset GPIO
  irq: 15,                      // optional (polled; not required for detection)
  calibration: { xMin: 0, xMax: 320, yMin: 0, yMax: 480 },
}

XPT2046 (SPI resistive)

touch: {
  library: 'XPT2046_Touchscreen',
  cs: 14,                       // touch chip-select (separate from display CS)
  irq: 2,                       // optional
  calibration: { xMin: 200, xMax: 3900, yMin: 240, yMax: 3800 },
}
⚙️ Legacy touch controllers

4-wire analog resistive, STMPE610, GT911, and CST816S shipped with the removed Arduino framework’s touch adapters; the Zephyr framework has no native adapter for them. Other controllers need a custom adapter:

touch: {
  adapter: './my-touch-adapter',
  calibration: { xMin, xMax, yMin, yMax },
  minPressure: 10,
}
// legacy analog-resistive shape (Arduino era)
touch: {
  library: 'Adafruit_TouchScreen',
  analogPins: { xp, yp, xm, ym, rx },
  calibration: { xMin, xMax, yMin, yMax },
}

Custom adapter

touch: {
  adapter: './my-touch-adapter',
  calibration: { xMin, xMax, yMin, yMax },
}

Calibration

calibration maps raw touch-controller readings to display pixels: { xMin, xMax, yMin, yMax }. minPressure filters out feather-light touches (resistive controllers only; capacitive controllers skip this gate).

Touch config fields

FieldTypeDescription
librarystringController library name (FT6336U or XPT2046_Touchscreen)
i2cAddressnumberI2C address (FT6336U: 0x38)
i2cFrequencynumberI2C clock speed in Hz (default 400000)
sda / sclnumberI2C wiring — configures the bus pinctrl in the devicetree overlay
csnumberSPI chip-select (XPT2046)
resetPinnumberHardware-reset GPIO
irqnumberInterrupt-request pin (polled; not required for detection)
calibrationobject{ xMin, xMax, yMin, yMax } — raw-to-pixel mapping (required)
⚙️ Advanced details — writing a new display driver

For drivers not in the built-in set, register an adapter at build time. The runtime contract every adapter must satisfy is the core display API:

  • display_init()
  • display_fillScreen(color)
  • display_startWrite() / display_endWrite()
  • display_setAddrWindow(x, y, w, h)
  • display_writePixels(pixels, count)
  • display_partial_refresh(x, y, w, h)

Register with registerDisplayAdapter(driver, generator) and provide a DisplayProfile so the transpiler knows the dimensions and color format. For the generator interface, see the display-adapter source in the @typecad/cuttlefish package.

Next: Authoring API for the ui.* functions and the .ui file format.