pm-kit: full st7796 init as PRIMARY bring-up, then mipidsi for drawing
Host initdump proved mipidsi's MADCTL (0x48), COLMOD, and address window (CASET 0..319 / RASET 0..479) already match CircuitPython exactly — so the 1/4 + rotation wasn't an addressing bug. The missing piece was the ST7796 extension init (B6/power/gamma) running as the PRIMARY bring-up right after reset (grafting it onto mipidsi's already-DISPON'd panel blanked or under-configured it). Now: manual hw reset + full CircuitPython st7796_init via the raw interface, THEN Builder without reset_pin (re-asserts only the basics, extension setup persists). initdump extended to also dump CASET/RASET draw windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b2ea27f506
commit
026c20523d
2 changed files with 64 additions and 24 deletions
|
|
@ -55,7 +55,7 @@ fn main() -> ! {
|
|||
let sclk = pins.gpio2.into_function::<hal::gpio::FunctionSpi>();
|
||||
let mosi = pins.gpio3.into_function::<hal::gpio::FunctionSpi>();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>().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}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue