Files
planar-EL512.256-H/docs

Notes

Approach

I2S0 in 8-bit parallel (LCD) mode with a circular DMA descriptor chain. Each DMA byte is one VCLK period:

Bit Signal
0 VID (even columns)
1 TVID (odd columns)
2 HS
3 VS

The I2S0 BCK output drives VCLK. ENABLE (GPIO 25) and _LOWPWR (GPIO 26) are plain GPIO.

I2S0 setup (EL512Panel::startI2S()), following bitluni's ESP32Lib 8-bit mode:

  • Clock: PLL_D2_CLK 160 MHz / clkm_div_num 32, tx_bck_div_num 1 -> 5 MHz, one byte per BCK period.
  • lcd_en, lcd_tx_wrx2_en, tx_bits_mod 8, tx_fifo_mod 1, tx_chan_mod 1, tx_right_first. With this setup each 32-bit DMA word goes out as bytes 2, 3, 0, 1, so encodeRow() stores clock c at c ^ 2.
  • Stream bits 0..3 -> I2S0O_DATA_OUT16_IDX + n; BCK -> VCLK (INVERT_VCLK flips it).
  • 18 descriptors of up to 15 lines (4080 bytes) each, looped; eof set once per frame.

From the datasheet

The datasheet is the source of truth for everything in this section and wins over any other doc, comment or the plan.

  • Two-bit parallel mode is selected by removing jumper 2 (DCONFIG) of pin strip PS1 (Table 3; factory default has jumpers 1 and 2 fitted). Even columns on VID, odd columns on TVID, numbered from the left as seen by the viewer; 256 VCLKs per row. The datasheet numbers from 1 (Fig. 3: "row 1", "Last pixel (512 / 256)"), so the leftmost pixel, x = 0 in the code, is column 1 and goes on TVID.
  • VCLK is active on the rising edge and should be kept running.
  • HS envelopes the pixels of a row: the visible pixels are the last 512 (two-bit mode: last 256 clocks) before HS falls. The number of VCLKs during HS high should be even.
  • The topmost row is the first HS high time ending after the rising edge of VS. Only the rising edge of VS is used; VS high/low time is at least 1 VCLK (T2).
  • Table 5: front porch T1 >= 100 µs, HS setup to VS T4 >= 1 VCLK, HS hold from VS T5 >= 3 µs, HS low T6 >= 4 VCLK, HS period T8 >= 50 µs, VS 70 Hz typ / 75 Hz max.
  • Figure 4 / Table 6: VID, TVID, HS and VS need 8 ns setup and 8 ns hold around the rising VCLK edge; VCLK period >= 33 ns (30 MHz max).
  • Inputs are HCT compatible with pull-ups (Table 4: high >= 2 V, low <= 0.8 V, abs. max Vcc1 + 0.5 V). ENABLE and _LOWPWR are on J2 and pulled high internally.

Open items

Done:

  • Implement EL512Panel::startI2S() (I2S0 LCD mode, 5 MHz from PLL_D2 / 32, GPIO matrix routing, circular lldesc_t chain).
  • Handle the 8-bit LCD-mode byte-pair swap in encodeRow() (clock c at c ^ 2).
  • Map x = 0 (datasheet column 1, odd) to TVID, not VID.

Hardware checks (need the panel and a scope or logic analyser):

  • Self test first (Table 3): remove PS1 jumper 1, power up; all pixels except the first half of the top row should light. Refit jumper 1, remove jumper 2.
  • Scope: VCLK 5 MHz on GPIO 18; HS 256 clocks high / 16 low (54.4 µs, T6-T8); VS one line high per 14.2 ms frame (70.4 Hz); data changing on the falling VCLK edge with >= 8 ns setup/hold (Table 6); T1 220.8 µs, T4/T5 as in encodeRow(). If data changes on the rising edge, set INVERT_VCLK.
  • Confirm the I2S0 data signal base (I2S0O_DATA_OUT16_IDX): if GPIO 19/21/22/23 stay idle, try I2S0O_DATA_OUT8_IDX.
  • Confirm the ^ 2 byte order. HS edges sit on word boundaries, so a wrong order does not show on HS; on the panel it shuffles pixels in groups of 8 (a 1-pixel line at x = 0 lands at x = 4).
  • Confirm the column parity: the checkerboard in main.cpp must look identical to the framebuffer, and the left border must sit on the panel's first column.
  • Add the 5 V buffer (74HCT245/74AHCT541) and a pull-down on ENABLE so the panel stays off while the ESP32 boots without VCLK (Table 1: ENABLE pulled high internally; Table 5 note 3: VCLK should be kept running).

Software:

  • Tear-free updates: show() rewrites the stream while DMA reads it. Encode into a second buffer and swap descriptors at the per-frame EOF, or encode only during vertical blanking.
  • Unit test for encodeRow() (see test/README).
  • The 71 KB stream is one contiguous DMA allocation; with Wi-Fi on it may not fit. Fallback: per-line buffers or a line ring refilled from the EOF interrupt.