diff --git a/rust/pm-kit/src/main.rs b/rust/pm-kit/src/main.rs index 773aa4f..c324f50 100644 --- a/rust/pm-kit/src/main.rs +++ b/rust/pm-kit/src/main.rs @@ -55,7 +55,7 @@ fn main() -> ! { let sclk = pins.gpio2.into_function::(); let mosi = pins.gpio3.into_function::(); let dc = pins.gpio6.into_push_pull_output(); - let rst = pins.gpio7.into_push_pull_output(); + let mut rst = pins.gpio7.into_push_pull_output(); let cs = pins.gpio5.into_push_pull_output(); let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (mosi, sclk)); @@ -68,33 +68,54 @@ fn main() -> ! { let spi_device = ExclusiveDevice::new_no_delay(spi, cs).unwrap(); let mut buffer = [0u8; 512]; - let di = SpiInterface::new(spi_device, dc, &mut buffer); + let mut di = SpiInterface::new(spi_device, dc, &mut buffer); + // Hardware reset, then the FULL known-good CircuitPython st7796_init as the PRIMARY bring-up + // (right after reset, the only time the panel accepts the extension setup cleanly). Confirmed + // necessary: mipidsi's address window + MADCTL already match CircuitPython (host initdump), so + // the only remaining difference was this extension init not running as the primary init. + rst.set_high().unwrap(); + timer.delay_ms(5); + rst.set_low().unwrap(); + timer.delay_ms(20); + rst.set_high().unwrap(); + timer.delay_ms(150); + + 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(0x36, &[0x48]).unwrap(); // MADCTL + 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); + + // Build mipidsi for DRAWING only — NO reset_pin (already reset), so it just re-asserts the + // basics (SLPOUT/MADCTL=0x48/INVON/COLMOD/NORON/DISPON) without touching the extension setup. let mut display = Builder::new(ST7796, di) - .reset_pin(rst) .display_size(WIDTH, HEIGHT) .color_order(ColorOrder::Bgr) .invert_colors(ColorInversion::Inverted) - .orientation(Orientation::new().flip_horizontal()) // panel wants MX set (matches CircuitPython MADCTL 0x48) + .orientation(Orientation::new().flip_horizontal()) .init(&mut timer) .unwrap(); - // Minimal: mipidsi's plain init already lit the panel (milestone 2 showed content, just in a - // sub-region because the gate scan wasn't set to 480 lines). 0xB6 (Display Function Control) - // fixes the line count and is a BASIC command — it does NOT need the 0xF0 extension unlock - // (the unlock gates gamma/power, and was the likely blanker). Bracket it with DISP off/on so - // the scan is reconfigured while the display is off, the way CircuitPython does it. - { - let di = unsafe { display.dcs() }; - di.send_command(0x28, &[]).unwrap(); // DISPOFF - di.send_command(0xB6, &[0x80, 0x02, 0x3B]).unwrap(); // display function control: 480 lines - di.send_command(0x29, &[]).unwrap(); // DISPON - } - timer.delay_ms(50); - - // 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 - // issue (init/MADCTL/0xB6), not a draw bug. + // Same UI code the host simulator renders (rust/uisim → PNG). pm_ui::draw_ui(&mut display).unwrap(); // Reached the loop → display init + draw completed. Slow 1 Hz blink (vs the solid-ON during diff --git a/rust/uisim/src/bin/initdump.rs b/rust/uisim/src/bin/initdump.rs index 3803021..532542e 100644 --- a/rust/uisim/src/bin/initdump.rs +++ b/rust/uisim/src/bin/initdump.rs @@ -3,6 +3,7 @@ //! guessing at the panel bring-up. `cargo run --bin initdump`. use core::convert::Infallible; +use embedded_graphics::{pixelcolor::Rgb565, prelude::*, primitives::{PrimitiveStyle, Rectangle}}; use embedded_hal::delay::DelayNs; use embedded_hal::digital::{ErrorType, OutputPin}; use mipidsi::interface::Interface; @@ -57,7 +58,7 @@ impl DelayNs for NoDelay { fn main() { let mut delay = NoDelay; - let display = Builder::new(ST7796, Rec::default()) + let mut display = Builder::new(ST7796, Rec::default()) .reset_pin(NoPin) .display_size(320, 480) .color_order(ColorOrder::Bgr) @@ -65,11 +66,29 @@ fn main() { .orientation(Orientation::new().flip_horizontal()) .init(&mut delay) .unwrap(); + + // boundary between init and draw commands (raw access to the recording interface) + let init_len = unsafe { display.dcs() }.log.len(); + + // Full-screen fill, then a single pixel at the far corner → reveals the address window. + Rectangle::new(Point::new(0, 0), Size::new(320, 480)) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLUE)) + .draw(&mut display) + .unwrap(); + Pixel(Point::new(319, 479), Rgb565::RED).draw(&mut display).unwrap(); + let (rec, _model, _rst) = display.release(); - println!("mipidsi ST7796 init sequence ({} commands):", rec.log.len()); - for (c, a) in &rec.log { + println!("init: {init_len} commands; full log {} commands", rec.log.len()); + for (i, (c, a)) in rec.log.iter().enumerate() { let args = a.iter().map(|b| format!("{b:02X}")).collect::>().join(" "); - println!(" 0x{c:02X} {args}"); + let tag = match c { + 0x2A => " <- CASET (col x0..x1)", + 0x2B => " <- RASET (row y0..y1)", + 0x2C => " <- RAMWR", + _ => "", + }; + let phase = if i < init_len { "init" } else { "draw" }; + println!(" [{phase}] 0x{c:02X} {args}{tag}"); } }