From 8c32b16589493148e45e9d5daf073ef18826580d Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 19 Jun 2026 18:08:01 +0200 Subject: [PATCH] fix(sim): SSD1306 page-addressing mode (Tiny4kOLED/U8g2 OLED garbled) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SSD1306Core only handled horizontal/vertical addressing (0x20/0x21/0x22) and defaulted memMode to horizontal. Page-mode drivers (Tiny4kOLED on ATtiny85, U8g2 page buffer, classic SSD1306 libs) position the cursor with the single-byte commands 0xB0-0xB7 (page) and 0x00-0x0F / 0x10-0x1F (column nibbles) and rely on the SSD1306 power-on default of PAGE addressing — they never send 0x20. velxio ignored those cursor commands and advanced in horizontal mode, so every setCursor was a no-op and the hatching/border/text piled onto wrong rows -> garbled display. Fix: default memMode=2 (datasheet power-on) and handle the page/column-set commands. Adafruit_SSD1306 still works (it sends 0x20,0x00 + 0x21/0x22 explicitly). Verified: decoded the real ATTinyCore Tiny4kOLED I2C stream renders a clean border + '128x64'. Adds a page-addressing render test. --- frontend/src/__tests__/ssd1306-render.test.ts | 30 +++++++++++++++++++ .../src/simulation/parts/ProtocolParts.ts | 21 +++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/frontend/src/__tests__/ssd1306-render.test.ts b/frontend/src/__tests__/ssd1306-render.test.ts index d245e4d9..6fae5a9e 100644 --- a/frontend/src/__tests__/ssd1306-render.test.ts +++ b/frontend/src/__tests__/ssd1306-render.test.ts @@ -169,6 +169,36 @@ describe('SSD1306 — ImageData rendering (syncElement fix)', () => { expect(px[idxOff + 2]).toBe(0); }); + it('page addressing (Tiny4kOLED): 0xB0+page / 0x00-0x1F col, no 0x20, cursor persists across data streams', () => { + const el = makeOLEDElement(); + const sim = makeSim(); + PartSimulationRegistry.get('ssd1306')!.attachEvents!(el, sim as any, () => null); + const device = sim._devices[0]; + + // Page-addressing setCursor: page 1, column 8 (col high nibble = 0x10, + // col low nibble = 0x08). No 0x20 — relies on the power-on page-mode + // default that Tiny4kOLED / U8g2-page / classic SSD1306 drivers assume. + sendCommandStream(device, [0xb1, 0x10, 0x08]); + // TinyWireM flushes its small buffer as distinct 16-byte I2C transactions, + // so the column pointer MUST persist across separate data streams. + sendDataStream(device, [0xff, 0x00]); // col 8, 9 + sendDataStream(device, [0x00, 0xff]); // col 10, 11 — cursor continued + + const px = el.imageData.data; + const lit = (row: number, col: number) => { + const i = (row * 128 + col) * 4; + return px[i] + px[i + 1] + px[i + 2] > 0; + }; + // page 1 → rows 8..15; 0xff lights the whole 8-pixel column. + expect(lit(8, 8)).toBe(true); + expect(lit(15, 8)).toBe(true); + expect(lit(8, 9)).toBe(false); // 0x00 + expect(lit(8, 10)).toBe(false); // 0x00 (start of 2nd stream) + // col 11 lit proves the cursor advanced across the STOP/new transaction. + expect(lit(8, 11)).toBe(true); + expect(lit(15, 11)).toBe(true); + }); + it('fills all 1024 GDDRAM bytes via horizontal addressing', () => { const el = makeOLEDElement(); const sim = makeSim(); diff --git a/frontend/src/simulation/parts/ProtocolParts.ts b/frontend/src/simulation/parts/ProtocolParts.ts index 9fccc052..0fbbb023 100644 --- a/frontend/src/simulation/parts/ProtocolParts.ts +++ b/frontend/src/simulation/parts/ProtocolParts.ts @@ -64,7 +64,12 @@ class SSD1306Core { private colEnd = 127; private pageStart = 0; private pageEnd = 7; - private memMode = 0; // 0=horizontal, 1=vertical, 2=page + // 0=horizontal, 1=vertical, 2=page. SSD1306 power-on default is PAGE + // addressing (datasheet 10b). Adafruit_SSD1306 overrides it to horizontal + // via 0x20,0x00; page-mode drivers (Tiny4kOLED, U8g2 page buffer) rely on + // this default and never send 0x20 — so the default MUST be 2 or their + // setCursor (0xB0-0xB7 + 0x00-0x1F) renders garbled. + private memMode = 2; // Multi-byte command accumulation private cmdBuf: number[] = []; @@ -125,7 +130,19 @@ class SSD1306Core { this.page = this.pageStart; break; default: - if (cmd >= 0x40 && cmd <= 0x7f) { + // Page-addressing-mode cursor commands (single-byte). Used by + // Tiny4kOLED / U8g2 page buffer / classic SSD1306 drivers whose + // setCursor() does NOT use the 0x21/0x22 column/page-range commands. + if (cmd >= 0xb0 && cmd <= 0xb7) { + // set page start address (B0..B7 → page 0..7) + this.page = cmd & 0x07; + } else if (cmd <= 0x0f) { + // set lower column nibble (0x00..0x0F) + this.col = (this.col & 0xf0) | (cmd & 0x0f); + } else if (cmd >= 0x10 && cmd <= 0x1f) { + // set higher column nibble (0x10..0x1F) + this.col = (this.col & 0x0f) | ((cmd & 0x0f) << 4); + } else if (cmd >= 0x40 && cmd <= 0x7f) { /* display start line — visual, skip */ } break;