Public Access
Project check against the datasheet; fix column parity
The datasheet numbers columns from 1 (Fig. 3: "row 1", "Last pixel (512 / 256)") and puts odd columns on TVID, so x = 0 (column 1) now goes to TVID and x = 1 to VID; the encoder had them swapped. README wiring now lists the J1/J2 pins and power rails from Table 1, the 5 V buffer, and a pull-down on ENABLE so the panel stays off while the ESP32 boots without VCLK. encodeRow() documents its T4/T5 margins. docs/README.md open items are regrouped into done, hardware checks and software work. main.cpp counts seconds, not frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
37d5bcc1f8
commit
ab3e6ad0ac
6 files changed
+72
-31
No files matched your search
@@ -22,16 +22,29 @@ yet tear-free.
|
|||||||
|
|
||||||
## Wiring
|
## Wiring
|
||||||
|
|
||||||
| Panel signal | Function | ESP32 GPIO | DMA byte bit |
|
| Panel signal | Panel pin (Table 1) | Function | ESP32 GPIO | DMA byte bit |
|
||||||
|--------------|-----------------------------------------|-----------:|-------------:|
|
|--------------|---------------------|----------------------------------------------|-----------:|-------------:|
|
||||||
| VCLK | Video clock (rising edge) | 18 | I2S0 BCK |
|
| VCLK | J1-13 | Video clock, data latched on rising edge | 18 | I2S0 BCK |
|
||||||
| VID | Pixel data, even columns | 19 | 0 |
|
| VID | J1-15 | Pixel data, even columns (x = 1, 3, …) | 19 | 0 |
|
||||||
| TVID | Pixel data, odd columns (two-bit mode) | 21 | 1 |
|
| TVID | J1-7 | Pixel data, odd columns (x = 0, 2, …) | 21 | 1 |
|
||||||
| HS | Horizontal sync | 22 | 2 |
|
| HS | J1-11 | Horizontal sync | 22 | 2 |
|
||||||
| VS | Vertical sync | 23 | 3 |
|
| VS | J1-9 | Vertical sync (rising edge used) | 23 | 3 |
|
||||||
| ENABLE | Display enable | 25 | — |
|
| ENABLE | J2-6 | Display enable, low = off | 25 | — |
|
||||||
| _LOWPWR | Low-power mode (active low) | 26 | — |
|
| _LOWPWR | J2-5 | Low-power mode, low = on | 26 | — |
|
||||||
| GND | Ground | GND | — |
|
| GND | J1-6, 8, 10, 12, 14, 16 | Signal return | GND | — |
|
||||||
|
|
||||||
|
The datasheet numbers columns 1 to 512 from the left; x in the code is 0-based, so
|
||||||
|
x = 0 is column 1 (odd, TVID).
|
||||||
|
|
||||||
|
Power (Table 1, Table 4): Vcc1 +5 V logic on J1-3, 4 (4.75–5.25 V, 0.2 A max);
|
||||||
|
Vcc2 +11…+30 V on J1-1, 2 (0.9 A max at 12 V). J1-5 is reserved: do not connect.
|
||||||
|
|
||||||
|
The panel inputs are HCT compatible with internal pull-ups of unstated voltage, and the
|
||||||
|
ESP32 is not 5 V tolerant, so drive them through a 74HCT245 or 74AHCT541 powered from
|
||||||
|
5 V (see the plan). ENABLE is pulled high inside the panel, so the panel scans while
|
||||||
|
the ESP32 is still booting and VCLK is not running yet; add a pull-down (for example
|
||||||
|
10 kΩ) on the buffer's ENABLE input so the panel stays off until `begin()` starts the
|
||||||
|
stream. Keep the cable under 300 mm, or add 47 Ω series resistors for up to 2 m.
|
||||||
|
|
||||||
The pin map lives in [include/pins.h](include/pins.h).
|
The pin map lives in [include/pins.h](include/pins.h).
|
||||||
|
|
||||||
|
|||||||
+35
-10
@@ -35,6 +35,8 @@ other doc, comment or the plan.
|
|||||||
- Two-bit parallel mode is selected by removing jumper 2 (DCONFIG) of pin strip PS1
|
- 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
|
(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.
|
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.
|
- 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:
|
- 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.
|
last 256 clocks) before HS falls. The number of VCLKs during HS high should be even.
|
||||||
@@ -49,14 +51,37 @@ other doc, comment or the plan.
|
|||||||
|
|
||||||
## Open items
|
## Open items
|
||||||
|
|
||||||
- [x] Implement `EL512Panel::startI2S()` (I2S0 LCD mode, clock dividers for 5 MHz,
|
Done:
|
||||||
|
|
||||||
|
- [x] Implement `EL512Panel::startI2S()` (I2S0 LCD mode, 5 MHz from PLL_D2 / 32,
|
||||||
GPIO matrix routing, circular `lldesc_t` chain).
|
GPIO matrix routing, circular `lldesc_t` chain).
|
||||||
- [x] Handle the 8-bit LCD-mode byte-pair swap in `encodeRow()`.
|
- [x] Handle the 8-bit LCD-mode byte-pair swap in `encodeRow()` (clock c at `c ^ 2`).
|
||||||
- [ ] Check on a scope: VCLK is 5 MHz on GPIO 18, HS is 256 clocks high / 16 low
|
- [x] Map x = 0 (datasheet column 1, odd) to TVID, not VID.
|
||||||
(54.4 us period), VS is one line high per 14.2 ms frame, data changes on the
|
|
||||||
falling VCLK edge, VS/HS setup and hold (T4/T5). Unconfirmed until then: the
|
Hardware checks (need the panel and a scope or logic analyser):
|
||||||
DATA_OUT16 base and 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
|
- [ ] Self test first (Table 3): remove PS1 jumper 1, power up; all pixels except the
|
||||||
(a 1-pixel line at x = 0 lands at x = 4).
|
first half of the top row should light. Refit jumper 1, remove jumper 2.
|
||||||
- [ ] Tear-free updates (double buffer or swap at VS).
|
- [ ] Scope: VCLK 5 MHz on GPIO 18; HS 256 clocks high / 16 low (54.4 µs, T6-T8);
|
||||||
- [ ] Unit test for `encodeRow()`.
|
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.
|
||||||
@@ -118,8 +118,9 @@ void EL512Panel::clear(bool on) {
|
|||||||
|
|
||||||
void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const {
|
void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const {
|
||||||
// VS is held high during the last blanking line so that the first HS high
|
// VS is held high during the last blanking line so that the first HS high
|
||||||
// time ending after its rising edge is row 0 (datasheet: video input timing).
|
// time ending after its rising edge is row 0 (datasheet: Video Data Input).
|
||||||
// TODO: verify VS/HS setup and hold (T4/T5) on hardware.
|
// VS rises 1104 clocks after the previous HS fall (T4 >= 1 VCLK) and 272
|
||||||
|
// clocks = 54.4 us before the next HS rise (T5 >= 3 us); check on a scope.
|
||||||
const uint8_t vs = (line == LINES_PER_FRAME - 1) ? BIT_VS : 0;
|
const uint8_t vs = (line == LINES_PER_FRAME - 1) ? BIT_VS : 0;
|
||||||
|
|
||||||
if (line >= HEIGHT) {
|
if (line >= HEIGHT) {
|
||||||
@@ -129,11 +130,12 @@ void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const {
|
|||||||
|
|
||||||
const uint8_t* src = _fb + (size_t)line * (WIDTH / 8);
|
const uint8_t* src = _fb + (size_t)line * (WIDTH / 8);
|
||||||
for (uint16_t c = 0; c < DATA_CLOCKS_PER_ROW; c++) {
|
for (uint16_t c = 0; c < DATA_CLOCKS_PER_ROW; c++) {
|
||||||
// Clock c carries columns 2c (VID, even) and 2c+1 (TVID, odd).
|
// Clock c carries x = 2c and 2c+1. The datasheet numbers columns from 1, so
|
||||||
|
// x = 2c is column 2c+1 (odd -> TVID) and x = 2c+1 is column 2c+2 (even -> VID).
|
||||||
const uint8_t bits = (uint8_t)(src[c >> 2] << ((c & 3) * 2));
|
const uint8_t bits = (uint8_t)(src[c >> 2] << ((c & 3) * 2));
|
||||||
uint8_t v = BIT_HS | vs;
|
uint8_t v = BIT_HS | vs;
|
||||||
if (bits & 0x80) v |= BIT_VID;
|
if (bits & 0x80) v |= BIT_TVID;
|
||||||
if (bits & 0x40) v |= BIT_TVID;
|
if (bits & 0x40) v |= BIT_VID;
|
||||||
// 8-bit I2S LCD mode sends each 32-bit word as bytes 2, 3, 0, 1.
|
// 8-bit I2S LCD mode sends each 32-bit word as bytes 2, 3, 0, 1.
|
||||||
out[c ^ 2] = v;
|
out[c ^ 2] = v;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
// We drive it from I2S0 in 8-bit parallel (LCD) mode with a circular DMA
|
// We drive it from I2S0 in 8-bit parallel (LCD) mode with a circular DMA
|
||||||
// descriptor chain, one byte per VCLK:
|
// descriptor chain, one byte per VCLK:
|
||||||
//
|
//
|
||||||
// byte bit 0 = VID (even columns)
|
// byte bit 0 = VID (even columns 2..512 = x 1, 3, ..., 511)
|
||||||
// byte bit 1 = TVID (odd columns)
|
// byte bit 1 = TVID (odd columns 1..511 = x 0, 2, ..., 510)
|
||||||
// byte bit 2 = HS (high while the 256 data clocks of a row are sent)
|
// byte bit 2 = HS (high while the 256 data clocks of a row are sent)
|
||||||
// byte bit 3 = VS (marks the top row)
|
// byte bit 3 = VS (marks the top row)
|
||||||
// I2S BCK -> VCLK (panel samples data on the rising edge)
|
// I2S BCK -> VCLK (panel samples data on the rising edge)
|
||||||
|
|||||||
+2
-2
@@ -52,12 +52,12 @@ void setup() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
static uint32_t last = 0;
|
static uint32_t last = 0;
|
||||||
static uint32_t frames = 0;
|
static uint32_t seconds = 0;
|
||||||
if (millis() - last >= 1000) {
|
if (millis() - last >= 1000) {
|
||||||
last = millis();
|
last = millis();
|
||||||
display.fillRect(8, 72, 120, 8, 0);
|
display.fillRect(8, 72, 120, 8, 0);
|
||||||
display.setCursor(8, 72);
|
display.setCursor(8, 72);
|
||||||
display.printf("uptime %lus", (unsigned long)frames++);
|
display.printf("uptime %lus", (unsigned long)seconds++);
|
||||||
display.display();
|
display.display();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
Unit tests (PlatformIO Unit Testing / Unity) go here.
|
Unit tests (PlatformIO Unit Testing / Unity) go here.
|
||||||
|
|
||||||
Planned first test: EL512Panel::encodeRow() - check that HS is high for exactly
|
Planned first test: EL512Panel::encodeRow() - check that HS is high for exactly
|
||||||
256 clocks per visible row, VS appears only on the last blanking line, and that
|
256 clocks per visible row, VS appears only on the last blanking line, that x = 0
|
||||||
even/odd columns land on VID/TVID.
|
(datasheet column 1, odd) lands on TVID and x = 1 on VID, and that clock c is
|
||||||
|
stored at index c ^ 2 (8-bit I2S byte order).
|
||||||
|
|
||||||
Run with: pio test -e esp32dev
|
Run with: pio test -e esp32dev
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user