Step 19 of 56
LCD: two very different display modes
One controller, two personalities — a command-driven 8-bit i8080 bus that streams a buffer on demand, and a continuously-refreshed RGB panel that never stops.
The i8080 mode
ESP32S3.LCD drives the LCD half of the LCD_CAM controller as an
8-bit Intel-8080 parallel master: a byte buffer is streamed out the data bus, one
byte per pixel clock, over GDMA. It suits any 8-bit
parallel sink, not only displays.
type Data_Pins is array (0 .. 7) of ESP32S3.GPIO.Optional_Pin;
procedure Acquire
(S : in out Session;
Pclk_Hz : Positive := 1_000_000;
Data : Data_Pins := (others => No_Pin);
Pclk : ESP32S3.GPIO.Optional_Pin := No_Pin);
The pixel clock is quantised: you get
20 MHz / round (20 MHz / Pclk_Hz), so ask for a divisor of
20 MHz if the exact rate matters. Unlike I2S,
bringing the controller up does not tie up a GDMA channel —
Transmit claims one only for the duration of a transfer, so the
channel is available to other peripherals between frames.
procedure Transmit (S : Session; Tx : ESP32S3.GDMA.DMA_Buffer;
Length : Natural; Ok : out Boolean);
Blocking, 1 .. 4095 bytes (the single-descriptor limit again), buffer
in internal SRAM, with the usual DMA_Buffer alignment and
whole-cache-line size preconditions. Enable_Clock_Out free-runs the
pixel clock on a pad with no data transaction, which is how you check the clock
divider on a scope before trusting a panel.
The RGB mode
A TFT panel driven by continuous HSYNC / VSYNC / DE / PCLK timing, rather than by commands. This is a different discipline: the panel must be refreshed forever, so the framebuffer streams from a chained GDMA descriptor ring (step 16), which is exactly why that ring exists.
type RGB_Data_Pins is array (0 .. 15) of ESP32S3.GPIO.Optional_Pin;
type RGB_Signal_Map is array (0 .. 15) of Natural;
type RGB_Config is record
H_Sync, V_Sync : Positive; -- sync pulse widths
Two_Byte : Boolean := True; -- True: 16-bit RGB565; False: 8-bit
DE_Idle_High : Boolean := False; -- DE is usually active-high, so idle low
-- ... plus the porches, from the panel datasheet
end record;
procedure Acquire_RGB (S : in out Session; Config : RGB_Config; Pins : RGB_Pins);
Horizontal widths and porches are in pixel clocks; vertical ones in lines
— copy them from the panel's datasheet. RGB_Signal_Map says
which LCD_DATA_OUT signal drives each panel data line, for boards
whose wiring is not in the obvious order.
Acquire_RGB initialises the
peripheral — enables the controller, sets the timing and routes the pins.
Starting the continuous refresh from a framebuffer is a separate call afterwards.
Splitting the two lets you get the timing right on a scope before committing a
framebuffer to it.
The camera-receive half of LCD_CAM is not covered by this driver.