Skip to content

The display

The Super IO carries a 128 × 128, 1.12-inch OLED on an SH1107 controller, reached over I²C on P0.24/P0.25 at address 0x3C, with a 12 V boost for the panel enabled by P0.23.

The bus

The controller's logic runs off 3V3; only the panel bias comes from the boost. So "it answered on the bus" is not evidence that the glass can light, and a scan gives identical results with P0.23 low or high. Both lines idle high with the internal pull-ups off, so the 5.1 kΩ resistors on the board are doing their job. The bus is scanned with writes, because a great many OLED modules implement writes and not reads, and the direction a display is driven in is the direction to scan.

The TWIM lock-up

The nRF52's TWIM can be left in a state it does not come out of after a transaction that ends in an address NACK, and embassy-nrf implements no workaround. It does not report an error. It produces plausible answers: the same address answers a read one moment and not the next, a scan disagrees with a probe half a millisecond earlier, and a bus with one device on it appears to have two. Cycling the peripheral's ENABLE register clears it; PSEL and FREQUENCY survive. oxinode::display::reset_peripheral does that, the scan does it after every transaction (a scan NACKs by design), and the panel driver does it after any failure, because a failed frame that poisons the next one turns a glitch into a display that never works again.

The controller

The SH1107 is 16 pages of 128 columns, one byte per eight vertically adjacent pixels, with the usual page/column write cursor. The datasheet describes its axis mapping twice and inconsistently; on this panel, established with an asymmetric test pattern, the layout is the familiar SSD1306 one (page and bit along y, column along x) with both axes inverted. The inversion is undone by the controller, with segment remap 0xA1 and reversed common scan 0xC8 in the init sequence, rather than by two subtractions per pixel.

0xA5 (all pixels on, independent of RAM) separates "the panel is dead" from "the driver is writing the wrong bytes".

A full 2048-byte frame takes about 218 ms at 100 kHz. The framebuffer (oxinode_core::sh1107::Frame) tracks which of the sixteen pages have changed, and pages are committed by comparing the result of a render against what the controller was last sent, so a change of one digit costs the two pages it touches (about 27 ms) rather than the whole panel. The modem loop sends at most two pages per pass, so the panel never blocks the radio for more than about 28 ms, and a whole screen fills in over about half a second.

Frame also implements monopanel::Canvas, in twenty lines, which is what lets the interface crate draw on it directly. See The interface.

The font

Fifty-five glyphs at 5 × 7: space, the digits, A to Z, and fifteen symbols. Lowercase folds to uppercase; anything outside the table renders as a hollow box, so a missing glyph looks like a missing glyph rather than a space. The glyphs were drawn as ASCII art and generated into a table, and the tests render them back into art and compare against the shape they were drawn as. The font lives in monopanel.

The RNode display protocol

A Reticulum host decides this device has a display purely from its platform byte (RNodeInterface sets self.display = True for any nRF52) and then offers an external framebuffer an application can push pictures into, a way to read it back, and a way to read what is on the screen. Neither buffer is the shape of this panel:

size layout
external framebuffer (CMD_FB_WRITE/CMD_FB_READ) 64 × 64, 512 bytes row-major, 8 bytes a row
display readback (CMD_DISP_READ) 128 × 64, 1024 bytes page-major, a byte is 8 rows
this panel 128 × 128, 2048 bytes page-major

Both byte counts are hard-coded on the host, which accumulates until it has exactly that many, so a device that sends a different number sends a frame that never completes. Each direction gets the answer that loses least:

  • The framebuffer is drawn at double size. 64 × 64 doubled is exactly 128 × 128: every source pixel becomes a 2 × 2 block, and a picture fills the panel with no cropping.
  • The readback is halved vertically, by OR. Pairs of rows are folded, so everything that is lit stays visible. It is lossy and documented; a test asserts a pixel anywhere on the panel survives the fold.

The two compose predictably: readback pixel (X, Y) is source pixel (X/2, Y), exactly, which makes the whole path checkable byte for byte from the host with no eyes involved. Rows pushed by the host are stored and the panel repainted on its own tick, because a full flush per row would be fourteen seconds of bus traffic for one picture.

CMD_FB_EXT, CMD_FB_WRITE, CMD_FB_READ, CMD_DISP_READ and CMD_DISP_INT are implemented. CMD_DISP_BLNK, CMD_DISP_ROT, CMD_DISP_RCND and CMD_BLINK are known and not implemented. CMD_DISP_ADR is reported as not applicable: the panel's address is discovered by scanning the bus at boot, so a host setting it would replace a measurement with a guess.

The bring-up image

display brings the bus up, reads the pins back from the peripheral's own registers, scans the bus with the 12 V rail off and on, draws the test pattern, and then renders a status page. It is a diagnostic for a panel that stays dark; the product image contains the same driver.