Step 30 of 56
ST7789 display and GT911 touch
A write-only SPI display that cannot be probed, and a touch controller whose I2C address depends on a pin level at reset — the two halves of a touchscreen, each with its own trap.
ST7789: no reads, no probe
The panel never talks back. The ST77xx SPI interface is write-only as wired here, so there is no status read and no way to probe for the device. If nothing appears, the driver cannot tell you whether the panel is absent, mis-wired or simply off — you get silence either way. Check wiring before suspecting code.
Three GPIOs are driven directly by the driver: DC
(data/command), CS, and an optional RST. Pass
No_Pin for RST and it falls back to a software reset.
type Color is mod 2**16; -- RGB565, MSB-first
function RGB (R, G, B : Natural) return Color; -- each 0 .. 255
type Color_Array is array (Natural range <>) of Color; -- row-major
type Rotation is (Rot_0, Rot_90, Rot_180, Rot_270);
Bring-up is Setup (records the wiring and geometry, brings the
SPI host up in mode 0) then Acquire for an exclusive session, then
Init to run the power-on sequence.
procedure Init (S : Session);
procedure Display_On (S : Session);
procedure Set_Rotation (S : Session; Rot : Rotation); -- sets MADCTL
procedure Invert (S : Session; On : Boolean);
procedure Sleep (S : Session; On : Boolean);
procedure Fill (S : Session; C : Color);
procedure Fill_Rect (S : Session; X, Y, W, H : Natural; C : Color);
procedure Set_Pixel (S : Session; X, Y : Natural; C : Color);
procedure Draw_Bitmap (S : Session; X, Y, W, H : Natural; Pixels : Color_Array);
Two levels of locking
The display has two guards, not one. The
Session owns the display exclusively, while each
operation locks the SPI host only for its own transfer —
so another device on the same bus can interleave between your drawing calls, and
a long Fill does not monopolise the bus.
Those per-display guards are a fixed library-level array keyed by the CS pin,
since a GPIO uniquely identifies one display. That is why no protected object
lives inside a Device, and why Device values are cheap
to hold.
GT911: the address is decided at reset
A Goodix 5-point capacitive controller on I2C. It is a 16-bit-register device: every transaction sends the register address MSB-first, then reads or writes a run of bytes from the chip's auto-incrementing pointer. Multi-byte values inside the map — coordinates, firmware version, output range — are little-endian, so the address and the payload disagree about byte order.
The chip latches its I2C address from the INT level while
RST is released: INT low gives 0x5D (the usual module
strapping), INT high gives 0x14. This driver never drives INT or
RST, because on many boards RST is not even on an ESP pin — the Waveshare
ESP32-S3-Touch-LCD-7 routes it through a CH422G
expander with INT weakly low. Releasing reset is therefore board wiring you do
once at startup, before touching the chip. Get the order wrong and the
part answers at the other address, which looks exactly like a dead device.
Reading touches
type Touch_State is record ... end record; -- up to 5 points, each with a track id
type Status is (OK, Bus_Error);
procedure Read_Touches (Dev : Device; State : out Touch_State; Result : out Status);
procedure Read_Product_Id, Read_Firmware_Version, Read_Resolution ...
The chip scans continuously and latches one report per scan cycle: a status
register with a buffer-ready flag and point count, then up to five
track-id/X/Y/size records. Read_Touches drains one report
and re-arms the latch by writing the flag back to zero — so
forgetting to call it stalls further reports rather than queueing them.
The INT line pulses on every fresh report. Attach a handler with the
.Interrupts child and call Read_Touches from a task it
wakes — remembering the callback rules: latch a
flag in the handler, do the I2C work at task level, never from the ISR.
Like I2C's other clients, the driver hard-codes no
wiring: you tell Setup the host, the SDA/SCL pins, the optional INT
pin and the address, and each operation opens a short-lived controlled session
for one complete transaction.