diff --git a/README.md b/README.md index d1d6111..a90fa69 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ peripheral in 8-bit parallel (LCD) mode streams a precomputed frame from DMA, so CPU is free while the panel refreshes. **Status:** framebuffer, GFX layer, row encoder and I2S/DMA refresh are implemented and -build cleanly; the signal timing has not yet been checked on hardware. Updates are not -yet tear-free. +run on hardware: the panel shows a correct image (VCLK 5 MHz, HS and VS checked with +the ESP32's PCNT). Updates are not yet tear-free. ## Hardware @@ -68,7 +68,7 @@ truth; if anything here disagrees with it, the datasheet wins. ``` platformio.ini build config (esp32dev, Arduino, Adafruit GFX) include/pins.h pin map -src/main.cpp test sketch: test pattern + text +src/main.cpp test sketch: cycles through test images lib/EL512/ local library src/EL512Panel.* framebuffer, row encoder, I2S DMA refresh src/EL512_GFX.* Adafruit_GFX subclass diff --git a/docs/README.md b/docs/README.md index c0c24a6..df5a27a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,8 +5,9 @@ ## Approach -I2S0 in 8-bit parallel (LCD) mode with a circular DMA descriptor chain. Each DMA byte -is one VCLK period: +I2S0 in 8-bit parallel (LCD) mode with a circular DMA descriptor chain. BCK rises once +per two DMA bytes (measured with PCNT: 2.5 MHz BCK at a 5 MHz byte rate), so each VCLK +period is two identical bytes: | Bit | Signal | |-----|--------| @@ -19,13 +20,15 @@ The I2S0 BCK output drives VCLK. ENABLE (GPIO 25) and _LOWPWR (GPIO 26) are plai 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. +- Clock: PLL_D2_CLK 160 MHz / `clkm_div_num` 16, `tx_bck_div_num` 1 -> 10 MHz byte + rate, 5 MHz BCK (two bytes 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`. + so `encodeRow()` stores stream byte i at `i ^ 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. +- Stream: 261 lines × 544 bytes ≈ 142 KB, too big for one DMA block, so it is + allocated as 38 chunks of up to 7 lines (3808 bytes), one descriptor each, looped; + `eof` set once per frame. ## From the datasheet @@ -53,26 +56,32 @@ other doc, comment or the plan. Done: -- [x] Implement `EL512Panel::startI2S()` (I2S0 LCD mode, 5 MHz from PLL_D2 / 32, - GPIO matrix routing, circular `lldesc_t` chain). -- [x] Handle the 8-bit LCD-mode byte-pair swap in `encodeRow()` (clock c at `c ^ 2`). +- [x] Implement `EL512Panel::startI2S()` (I2S0 LCD mode, GPIO matrix routing, + circular `lldesc_t` chain). +- [x] Handle the 8-bit LCD-mode byte-pair swap in `encodeRow()` (byte i at `i ^ 2`). - [x] Map x = 0 (datasheet column 1, odd) to TVID, not VID. +- [x] VCLK rate: PCNT showed BCK at half the byte rate (panel saw every other byte, + image squeezed and repeated). Fixed with two bytes per VCLK at a 10 MHz byte + rate; PCNT now reads VCLK 5.0 MHz, VS 70 Hz. +- [x] I2S0 data signal base `I2S0O_DATA_OUT16_IDX`: GPIO 19/21/22/23 all toggle. +- [x] Panel shows a correct image in two-bit mode. -Hardware checks (need the panel and a scope or logic analyser): +Hardware checks: -- [ ] 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`. +- [ ] Self test (Table 3): remove PS1 jumper 1, power up; all pixels except the + first half of the top row should light. Refit jumper 1. +- [ ] HS reads ~18,030 Hz on PCNT where 5 MHz / 272 = 18,382 Hz is expected (~2% low) + while VCLK reads 5.0 MHz. Frame rate is still ~70 Hz; cause unknown, check on a + scope (HS 256 clocks high / 16 low, T6-T8). +- [ ] Scope: data changing away from the rising VCLK edge with >= 8 ns setup/hold + (Table 6); which BCK edge lines up with the byte-pair boundary is unverified. If + pixels look shifted or noisy, set `INVERT_VCLK`. - [ ] 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. +- [ ] Confirm the column parity: the "checkerboard" and "VID/TVID columns" test images + in `main.cpp` must look as drawn, 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). @@ -83,5 +92,5 @@ Software: 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. +- [ ] The ~142 KB stream (in 38 chunks) leaves ~190 KB heap; with Wi-Fi on it may + not fit. Fallback: a line ring refilled from the EOF interrupt. diff --git a/lib/EL512/src/EL512Panel.cpp b/lib/EL512/src/EL512Panel.cpp index 8d99350..1ee0fdf 100644 --- a/lib/EL512/src/EL512Panel.cpp +++ b/lib/EL512/src/EL512Panel.cpp @@ -13,25 +13,23 @@ namespace { // I2S0 clock source when clka_en = 0: PLL_D2_CLK, 160 MHz whatever the CPU clock. constexpr uint32_t I2S_SOURCE_CLOCK_HZ = 160000000; -constexpr uint32_t I2S_CLKM_DIV = I2S_SOURCE_CLOCK_HZ / EL512Panel::PIXEL_CLOCK_HZ; -static_assert(I2S_SOURCE_CLOCK_HZ % EL512Panel::PIXEL_CLOCK_HZ == 0, - "pixel clock must divide 160 MHz exactly"); +constexpr uint32_t I2S_BYTE_RATE_HZ = EL512Panel::PIXEL_CLOCK_HZ * EL512Panel::BYTES_PER_CLOCK; +constexpr uint32_t I2S_CLKM_DIV = I2S_SOURCE_CLOCK_HZ / I2S_BYTE_RATE_HZ; +static_assert(I2S_SOURCE_CLOCK_HZ % I2S_BYTE_RATE_HZ == 0, + "byte rate must divide 160 MHz exactly"); static_assert(I2S_CLKM_DIV >= 2 && I2S_CLKM_DIV <= 255, "clkm_div_num out of range"); // In 8-bit LCD mode I2S0 puts its byte on DATA_OUT16..23 (bitluni ESP32Lib). constexpr uint32_t I2S0_DATA_SIGNAL_BASE = I2S0O_DATA_OUT16_IDX; -// BCK runs at the byte rate. As in standard I2S, data should change on its falling -// edge, so the panel's rising-edge latch sits mid-bit. Unverified: if the scope -// shows data changing on the rising edge, set this to true. +// BCK runs at half the byte rate (measured: 2.5 MHz BCK at 5 MHz bytes), so each +// VCLK period spans two identical bytes. Unverified which BCK edge lines up with +// the byte pair boundary: if pixels look shifted or noisy, set this to true. constexpr bool INVERT_VCLK = false; -// Whole lines per DMA descriptor: 15 * 272 = 4080 bytes (limit 4095). -constexpr uint16_t LINES_PER_DESC = 15; -static_assert(LINES_PER_DESC * EL512Panel::CLOCKS_PER_ROW <= 4092, "descriptor too long"); -static_assert(EL512Panel::CLOCKS_PER_ROW % 4 == 0, "rows must be whole DMA words"); -constexpr size_t DESC_COUNT = - (EL512Panel::LINES_PER_FRAME + LINES_PER_DESC - 1) / LINES_PER_DESC; +// One DMA descriptor per stream chunk (descriptor limit 4095 bytes). +static_assert(EL512Panel::LINES_PER_CHUNK * EL512Panel::ROW_BYTES <= 4092, "descriptor too long"); +static_assert(EL512Panel::ROW_BYTES % 4 == 0, "rows must be whole DMA words"); void resetI2S0() { const uint32_t lcReset = I2S_IN_RST_M | I2S_OUT_RST_M | I2S_AHBM_RST_M | I2S_AHBM_FIFO_RST_M; @@ -64,11 +62,16 @@ bool EL512Panel::begin() { setLowPower(false); _fb = (uint8_t*)calloc(FRAMEBUFFER_BYTES, 1); - // Full-frame stream: 261 * 272 = ~71 KB of DMA-capable internal RAM. + // Full-frame stream: 261 * 544 = ~142 KB of DMA-capable internal RAM, in chunks. // (A line ring buffer refilled from the DMA EOF interrupt would cut this // to a few KB if RAM gets tight.) - _stream = (uint8_t*)heap_caps_malloc(STREAM_BYTES, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); - if (!_fb || !_stream) { + bool ok = _fb != nullptr; + for (uint16_t i = 0; ok && i < CHUNK_COUNT; i++) { + _chunks[i] = (uint8_t*)heap_caps_malloc((size_t)chunkLines(i) * ROW_BYTES, + MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + ok = _chunks[i] != nullptr; + } + if (!ok) { end(); return false; } @@ -89,8 +92,19 @@ void EL512Panel::end() { _running = false; free(_fb); _fb = nullptr; - heap_caps_free(_stream); - _stream = nullptr; + for (uint8_t*& chunk : _chunks) { + heap_caps_free(chunk); + chunk = nullptr; + } +} + +uint16_t EL512Panel::chunkLines(uint16_t chunk) { + const uint16_t first = chunk * LINES_PER_CHUNK; + return min(LINES_PER_CHUNK, LINES_PER_FRAME - first); +} + +uint8_t* EL512Panel::linePtr(uint16_t line) const { + return _chunks[line / LINES_PER_CHUNK] + (size_t)(line % LINES_PER_CHUNK) * ROW_BYTES; } void EL512Panel::setEnabled(bool on) { digitalWrite(_pins.enable, on ? HIGH : LOW); } @@ -124,7 +138,7 @@ void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const { const uint8_t vs = (line == LINES_PER_FRAME - 1) ? BIT_VS : 0; if (line >= HEIGHT) { - memset(out, vs, CLOCKS_PER_ROW); + memset(out, vs, ROW_BYTES); return; } @@ -136,35 +150,35 @@ void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const { uint8_t v = BIT_HS | vs; if (bits & 0x80) 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. - out[c ^ 2] = v; + // Two bytes per VCLK; 8-bit I2S LCD mode sends each 32-bit word as bytes 2, 3, 0, 1. + const uint16_t i = c * BYTES_PER_CLOCK; + out[i ^ 2] = v; + out[(i + 1) ^ 2] = v; } // Blanking is uniform and starts on a word boundary, so it needs no swap. - memset(out + DATA_CLOCKS_PER_ROW, vs, BLANK_CLOCKS_PER_ROW); + memset(out + DATA_CLOCKS_PER_ROW * BYTES_PER_CLOCK, vs, BLANK_CLOCKS_PER_ROW * BYTES_PER_CLOCK); } void EL512Panel::show() { - if (!_fb || !_stream) return; + if (!_fb || !_chunks[CHUNK_COUNT - 1]) return; // TODO: double-buffer or sync to VS to avoid tearing. for (uint16_t line = 0; line < LINES_PER_FRAME; line++) { - encodeRow(line, _stream + (size_t)line * CLOCKS_PER_ROW); + encodeRow(line, linePtr(line)); } } bool EL512Panel::startI2S() { - // Circular descriptor chain over the stream, whole lines per descriptor. - _desc = (lldesc_t*)heap_caps_calloc(DESC_COUNT, sizeof(lldesc_t), MALLOC_CAP_DMA); + // Circular descriptor chain over the stream, one descriptor per chunk. + _desc = (lldesc_t*)heap_caps_calloc(CHUNK_COUNT, sizeof(lldesc_t), MALLOC_CAP_DMA); if (!_desc) return false; - for (size_t i = 0; i < DESC_COUNT; i++) { - const uint16_t first = i * LINES_PER_DESC; - const uint16_t lines = min(LINES_PER_DESC, LINES_PER_FRAME - first); + for (size_t i = 0; i < CHUNK_COUNT; i++) { lldesc_t& d = _desc[i]; - d.size = lines * CLOCKS_PER_ROW; + d.size = chunkLines(i) * ROW_BYTES; d.length = d.size; d.owner = 1; - d.eof = (i == DESC_COUNT - 1); // one EOF per frame - d.buf = _stream + (size_t)first * CLOCKS_PER_ROW; - d.qe.stqe_next = &_desc[(i + 1) % DESC_COUNT]; + d.eof = (i == CHUNK_COUNT - 1); // one EOF per frame + d.buf = _chunks[i]; + d.qe.stqe_next = &_desc[(i + 1) % CHUNK_COUNT]; } periph_module_enable(PERIPH_I2S0_MODULE); @@ -181,7 +195,7 @@ bool EL512Panel::startI2S() { I2S0.sample_rate_conf.tx_bits_mod = 8; I2S0.sample_rate_conf.tx_bck_div_num = 1; - // PLL_D2_CLK / (N + b/a) with b = 0: 160 MHz / 32 = 5 MHz. + // PLL_D2_CLK / (N + b/a) with b = 0: 160 MHz / 16 = 10 MHz bytes -> 5 MHz BCK. I2S0.clkm_conf.val = 0; I2S0.clkm_conf.clka_en = 0; I2S0.clkm_conf.clkm_div_num = I2S_CLKM_DIV; diff --git a/lib/EL512/src/EL512Panel.h b/lib/EL512/src/EL512Panel.h index 2053d1a..dd2222b 100644 --- a/lib/EL512/src/EL512Panel.h +++ b/lib/EL512/src/EL512Panel.h @@ -2,7 +2,8 @@ // // The panel has no frame memory of its own: it must be refreshed continuously. // We drive it from I2S0 in 8-bit parallel (LCD) mode with a circular DMA -// descriptor chain, one byte per VCLK: +// descriptor chain. BCK (= VCLK) rises once per two stream bytes (measured +// with PCNT), so every VCLK period is two identical bytes: // // byte bit 0 = VID (even columns 2..512 = x 1, 3, ..., 511) // byte bit 1 = TVID (odd columns 1..511 = x 0, 2, ..., 510) @@ -15,7 +16,8 @@ // (256 visible + 5 blanking) -> 5e6 / (272 * 261) ~= 70.4 Hz. // // In 8-bit LCD mode the ESP32 I2S emits each 32-bit DMA word as bytes 2, 3, 0, 1, -// so encodeRow() stores clock c at index c ^ 2 (as bitluni's ESP32Lib does). +// so encodeRow() stores stream byte i at index i ^ 2 (as bitluni's ESP32Lib does). +// The I2S byte rate is 2 * PIXEL_CLOCK_HZ = 10 MHz. #pragma once #include @@ -51,8 +53,17 @@ class EL512Panel { static constexpr uint8_t BIT_HS = 1 << 2; static constexpr uint8_t BIT_VS = 1 << 3; + // Stream bytes per VCLK period (BCK runs at half the I2S byte rate). + static constexpr uint8_t BYTES_PER_CLOCK = 2; + static constexpr uint16_t ROW_BYTES = CLOCKS_PER_ROW * BYTES_PER_CLOCK; + static constexpr size_t FRAMEBUFFER_BYTES = (size_t)WIDTH * HEIGHT / 8; - static constexpr size_t STREAM_BYTES = (size_t)LINES_PER_FRAME * CLOCKS_PER_ROW; + static constexpr size_t STREAM_BYTES = (size_t)LINES_PER_FRAME * ROW_BYTES; + + // The ~142 KB stream is too big for one DMA block, so it is allocated in + // chunks of whole lines, one per DMA descriptor (7 * 544 = 3808 <= 4092 bytes). + static constexpr uint16_t LINES_PER_CHUNK = 7; + static constexpr uint16_t CHUNK_COUNT = (LINES_PER_FRAME + LINES_PER_CHUNK - 1) / LINES_PER_CHUNK; explicit EL512Panel(const EL512Pins& pins); ~EL512Panel(); @@ -74,8 +85,8 @@ class EL512Panel { // Encode the framebuffer into the DMA stream (call after drawing). void show(); - // Encode one scan line (0..LINES_PER_FRAME-1) into CLOCKS_PER_ROW bytes, in - // DMA order (clock c at index c ^ 2). Lines >= HEIGHT produce blanking. + // Encode one scan line (0..LINES_PER_FRAME-1) into ROW_BYTES bytes, in DMA + // order (stream byte i at index i ^ 2). Lines >= HEIGHT produce blanking. // Public so it can be unit-tested. void encodeRow(uint16_t line, uint8_t* out) const; @@ -87,10 +98,12 @@ class EL512Panel { private: bool startI2S(); void stopI2S(); + static uint16_t chunkLines(uint16_t chunk); + uint8_t* linePtr(uint16_t line) const; EL512Pins _pins; - uint8_t* _fb = nullptr; // 1 bpp drawing buffer - uint8_t* _stream = nullptr; // STREAM_BYTES, DMA-capable - lldesc_s* _desc = nullptr; // circular DMA descriptor chain over _stream + uint8_t* _fb = nullptr; // 1 bpp drawing buffer + uint8_t* _chunks[CHUNK_COUNT] = {}; // stream, DMA-capable, LINES_PER_CHUNK lines each + lldesc_s* _desc = nullptr; // circular DMA descriptor chain, one per chunk bool _running = false; }; diff --git a/src/main.cpp b/src/main.cpp index 6d2506b..99e8588 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -// Test sketch: bring up the EL512.256-H and draw a test pattern with Adafruit GFX. +// Test sketch: bring up the EL512.256-H and slowly cycle through test images. #include #include @@ -15,27 +15,137 @@ static EL512Panel panel({ }); static EL512_GFX display(panel); -static void drawTestPattern() { - display.fillScreen(0); - display.drawRect(0, 0, display.width(), display.height(), 1); - display.drawLine(0, 0, display.width() - 1, display.height() - 1, 1); - display.drawLine(display.width() - 1, 0, 0, display.height() - 1, 1); +static constexpr uint32_t IMAGE_MS = 5000; // time per test image - // Checkerboard strip to check VID/TVID column ordering. - for (int16_t y = 8; y < 24; y++) - for (int16_t x = 8; x < 136; x++) - if ((x ^ y) & 1) display.drawPixel(x, y, 1); +static const int16_t W = EL512Panel::WIDTH; +static const int16_t H = EL512Panel::HEIGHT; - display.fillCircle(display.width() / 2, display.height() / 2, 40, 1); +// Name tag in the bottom-right corner so each image can be identified. +static void drawLabel(const char* name) { + int16_t bx, by; + uint16_t bw, bh; + display.setTextSize(1); + display.getTextBounds(name, 0, 0, &bx, &by, &bw, &bh); + const int16_t w = bw + 8, h = bh + 6; + const int16_t x = W - w - 4, y = H - h - 4; + display.fillRect(x, y, w, h, 0); + display.drawRect(x, y, w, h, 1); + display.setTextColor(1); + display.setCursor(x + 4, y + 3); + display.print(name); +} + +static void drawOverview() { + display.drawRect(0, 0, W, H, 1); + display.drawLine(0, 0, W - 1, H - 1, 1); + display.drawLine(W - 1, 0, 0, H - 1, 1); + display.fillCircle(W / 2, H / 2, 40, 1); display.setTextColor(1); display.setTextSize(2); - display.setCursor(8, 32); + display.setCursor(8, 8); display.print("EL512.256-H"); display.setTextSize(1); - display.setCursor(8, 56); - display.printf("%ux%u %.1f Hz", EL512Panel::WIDTH, EL512Panel::HEIGHT, - EL512Panel::refreshHz()); + display.setCursor(8, 32); + display.printf("%ux%u %.1f Hz", W, H, EL512Panel::refreshHz()); +} + +static void drawAllOff() {} + +static void drawAllOn() { display.fillScreen(1); } + +// Datasheet columns are numbered from 1: x = 0 is column 1 (odd, TVID). +static void drawVidColumns() { + for (int16_t x = 1; x < W; x += 2) display.drawFastVLine(x, 0, H, 1); +} + +static void drawTvidColumns() { + for (int16_t x = 0; x < W; x += 2) display.drawFastVLine(x, 0, H, 1); +} + +static void drawRowStripes() { + for (int16_t y = 0; y < H; y += 2) display.drawFastHLine(0, y, W, 1); +} + +static void drawCheckerboard() { + for (int16_t y = 0; y < H; y++) + for (int16_t x = 0; x < W; x++) + if ((x ^ y) & 1) display.drawPixel(x, y, 1); +} + +static void drawBlocks() { + for (int16_t y = 0; y < H; y += 16) + for (int16_t x = 0; x < W; x += 16) + if (((x ^ y) >> 4) & 1) display.fillRect(x, y, 16, 16, 1); +} + +static void drawGrid() { + display.setTextSize(1); + display.setTextColor(1); + for (int16_t x = 0; x < W; x += 32) display.drawFastVLine(x, 0, H, 1); + for (int16_t y = 0; y < H; y += 32) display.drawFastHLine(0, y, W, 1); + display.drawFastVLine(W - 1, 0, H, 1); + display.drawFastHLine(0, H - 1, W, 1); + for (int16_t y = 0; y < H; y += 64) + for (int16_t x = 0; x < W; x += 64) { + display.setCursor(x + 3, y + 3); + display.printf("%d,%d", x, y); + } +} + +static void drawCircles() { + for (int16_t r = 8; r < W; r += 12) display.drawCircle(W / 2, H / 2, r, 1); +} + +// Ordered 4x4 dither from left (dark) to right (bright). +static void drawDither() { + static const uint8_t bayer[4][4] = { + {0, 8, 2, 10}, {12, 4, 14, 6}, {3, 11, 1, 9}, {15, 7, 13, 5}}; + for (int16_t y = 0; y < H; y++) + for (int16_t x = 0; x < W; x++) + if (bayer[y & 3][x & 3] < (x * 17) / W) display.drawPixel(x, y, 1); +} + +static void drawText() { + display.setTextColor(1); + display.setTextSize(1); + display.setCursor(0, 0); + for (int line = 0; line < 30; line++) { + for (char c = ' ' + (line % 3) * 32; c < ' ' + (line % 3) * 32 + 32 && c <= '~'; c++) + display.print(c); + display.print(" The quick brown fox jumps over the lazy dog.\n"); + } + display.setTextSize(3); + display.setCursor(8, H - 40); + display.print("Size 3 text"); +} + +struct TestImage { + const char* name; + void (*draw)(); +}; + +static const TestImage images[] = { + {"overview", drawOverview}, + {"all off", drawAllOff}, + {"all on", drawAllOn}, + {"VID columns (even)", drawVidColumns}, + {"TVID columns (odd)", drawTvidColumns}, + {"row stripes", drawRowStripes}, + {"checkerboard", drawCheckerboard}, + {"16px blocks", drawBlocks}, + {"grid 32px", drawGrid}, + {"circles", drawCircles}, + {"dither ramp", drawDither}, + {"text", drawText}, +}; +static constexpr size_t IMAGE_COUNT = sizeof(images) / sizeof(images[0]); + +static void showImage(size_t i) { + display.fillScreen(0); + images[i].draw(); + drawLabel(images[i].name); display.display(); + Serial.printf("image %u/%u: %s\n", (unsigned)(i + 1), (unsigned)IMAGE_COUNT, images[i].name); } void setup() { @@ -46,18 +156,16 @@ void setup() { Serial.println("panel init failed (out of DMA memory?)"); while (true) delay(1000); } - drawTestPattern(); Serial.printf("free heap: %u\n", ESP.getFreeHeap()); + showImage(0); } void loop() { - static uint32_t last = 0; - static uint32_t seconds = 0; - if (millis() - last >= 1000) { + static size_t current = 0; + static uint32_t last = millis(); + if (millis() - last >= IMAGE_MS) { last = millis(); - display.fillRect(8, 72, 120, 8, 0); - display.setCursor(8, 72); - display.printf("uptime %lus", (unsigned long)seconds++); - display.display(); + current = (current + 1) % IMAGE_COUNT; + showImage(current); } } diff --git a/test/README b/test/README index f73e892..82e71ba 100644 --- a/test/README +++ b/test/README @@ -2,8 +2,8 @@ Unit tests (PlatformIO Unit Testing / Unity) go here. 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, that x = 0 -(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). +(datasheet column 1, odd) lands on TVID and x = 1 on VID, and that each clock is +two identical bytes with stream byte i stored at index i ^ 2 (8-bit I2S byte order). Run with: pio test -e esp32dev