From 508fae56fb1c47a6ffe11c9e92f36dd7f1fb8795 Mon Sep 17 00:00:00 2001 From: Me Here Date: Sun, 31 May 2026 22:06:31 -0500 Subject: [PATCH] pm-kit: replay full CircuitPython st7796_init (extension setup before DISPON) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host initdump (rust/uisim --bin initdump) showed mipidsi emits only SLPOUT, MADCTL=0x48, INVON, COLMOD, NORON, DISPON — MADCTL already matches CircuitPython, but the ST7796 extension setup (unlock, 0xB6 480-lines, power, gamma) is missing, and sending it AFTER mipidsi's DISPON blanked the live panel. Replay the full known-good st7796_init via Display::dcs() ending in its own DISPON. Adds the initdump tool (capture init byte sequence on the host, no bench). Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/pm-kit/src/main.rs | 31 +++++++++++--- rust/uisim/Cargo.toml | 3 ++ rust/uisim/src/bin/initdump.rs | 75 ++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 rust/uisim/src/bin/initdump.rs diff --git a/rust/pm-kit/src/main.rs b/rust/pm-kit/src/main.rs index 249ba8a..b4894b0 100644 --- a/rust/pm-kit/src/main.rs +++ b/rust/pm-kit/src/main.rs @@ -79,16 +79,37 @@ fn main() -> ! { .init(&mut timer) .unwrap(); - // ST7796-specific setup that mipidsi's ST7789-based init omits. The critical one is 0xB6 - // (Display Function Control → 480 driving lines); without it the panel only scans part of - // the screen. Sequence + values from the working CircuitPython firmware (st7796_init). + // mipidsi's ST7796 model uses the ST7789 init (SLPOUT, MADCTL=0x48, INVON, COLMOD, NORON, + // DISPON — confirmed via the host initdump). It omits the ST7796 extension setup, and doing + // it *after* mipidsi's DISPON (on a live panel) blanks the screen. So replay the full, + // known-good CircuitPython st7796_init here, ending in its own DISPON. MADCTL stays 0x48, + // which is exactly what mipidsi's drawing already assumes — so draws map correctly. { let di = unsafe { display.dcs() }; + di.send_command(0x01, &[]).unwrap(); // SWRESET + timer.delay_ms(120); + di.send_command(0x11, &[]).unwrap(); // SLPOUT + timer.delay_ms(120); di.send_command(0xF0, &[0xC3]).unwrap(); // unlock extension command set di.send_command(0xF0, &[0x96]).unwrap(); - di.send_command(0xB6, &[0x80, 0x02, 0x3B]).unwrap(); // display function control: 480 lines (the essential one) + di.send_command(0x36, &[0x48]).unwrap(); // MADCTL (matches mipidsi orientation) + di.send_command(0x3A, &[0x55]).unwrap(); // COLMOD 16bpp + di.send_command(0xB4, &[0x01]).unwrap(); + di.send_command(0xB6, &[0x80, 0x02, 0x3B]).unwrap(); // display function control: 480 lines + di.send_command(0xE8, &[0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33]).unwrap(); + di.send_command(0xC1, &[0x06]).unwrap(); + di.send_command(0xC2, &[0xA7]).unwrap(); + di.send_command(0xC5, &[0x18]).unwrap(); + timer.delay_ms(120); + di.send_command(0xE0, &[0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B]).unwrap(); + di.send_command(0xE1, &[0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B]).unwrap(); + di.send_command(0xF0, &[0x3C]).unwrap(); // lock + di.send_command(0xF0, &[0x69]).unwrap(); + timer.delay_ms(120); + di.send_command(0x21, &[]).unwrap(); // INVON (INVERT_COLORS = true) + di.send_command(0x29, &[]).unwrap(); // DISPON + timer.delay_ms(50); } - timer.delay_ms(20); // Same UI code the host simulator renders (rust/uisim → PNG). If this is wrong, the sim // shows it without the bench; if the sim is right but the panel is wrong, it's a controller diff --git a/rust/uisim/Cargo.toml b/rust/uisim/Cargo.toml index c120927..8d3bf9b 100644 --- a/rust/uisim/Cargo.toml +++ b/rust/uisim/Cargo.toml @@ -8,3 +8,6 @@ description = "Host renderer for pm-ui: draws the firmware UI to a framebuffer a pm-ui = { path = "../pm-ui" } embedded-graphics = "0.8" image = { version = "0.25", default-features = false, features = ["png"] } +# initdump binary: capture mipidsi's ST7796 init command sequence on the host +mipidsi = "0.9" +embedded-hal = "1" diff --git a/rust/uisim/src/bin/initdump.rs b/rust/uisim/src/bin/initdump.rs new file mode 100644 index 0000000..3803021 --- /dev/null +++ b/rust/uisim/src/bin/initdump.rs @@ -0,0 +1,75 @@ +//! Capture the exact command sequence mipidsi's ST7796 model emits for our Builder config, +//! on the host — so we can diff it against the known-good CircuitPython st7796_init and stop +//! guessing at the panel bring-up. `cargo run --bin initdump`. + +use core::convert::Infallible; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::{ErrorType, OutputPin}; +use mipidsi::interface::Interface; +use mipidsi::models::ST7796; +use mipidsi::options::{ColorInversion, ColorOrder, Orientation}; +use mipidsi::Builder; + +/// Records every command + args mipidsi sends. +#[derive(Default)] +struct Rec { + log: Vec<(u8, Vec)>, +} +impl Interface for Rec { + type Word = u8; + type Error = Infallible; + fn send_command(&mut self, command: u8, args: &[u8]) -> Result<(), Infallible> { + self.log.push((command, args.to_vec())); + Ok(()) + } + fn send_pixels( + &mut self, + _pixels: impl IntoIterator, + ) -> Result<(), Infallible> { + Ok(()) + } + fn send_repeated_pixel( + &mut self, + _pixel: [u8; N], + _count: u32, + ) -> Result<(), Infallible> { + Ok(()) + } +} + +struct NoPin; +impl ErrorType for NoPin { + type Error = Infallible; +} +impl OutputPin for NoPin { + fn set_low(&mut self) -> Result<(), Infallible> { + Ok(()) + } + fn set_high(&mut self) -> Result<(), Infallible> { + Ok(()) + } +} + +struct NoDelay; +impl DelayNs for NoDelay { + fn delay_ns(&mut self, _ns: u32) {} +} + +fn main() { + let mut delay = NoDelay; + let display = Builder::new(ST7796, Rec::default()) + .reset_pin(NoPin) + .display_size(320, 480) + .color_order(ColorOrder::Bgr) + .invert_colors(ColorInversion::Inverted) + .orientation(Orientation::new().flip_horizontal()) + .init(&mut delay) + .unwrap(); + let (rec, _model, _rst) = display.release(); + + println!("mipidsi ST7796 init sequence ({} commands):", rec.log.len()); + for (c, a) in &rec.log { + let args = a.iter().map(|b| format!("{b:02X}")).collect::>().join(" "); + println!(" 0x{c:02X} {args}"); + } +}