pm-kit: replay full CircuitPython st7796_init (extension setup before DISPON)
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) <noreply@anthropic.com>
This commit is contained in:
parent
0fa32a827f
commit
508fae56fb
3 changed files with 104 additions and 5 deletions
|
|
@ -79,16 +79,37 @@ fn main() -> ! {
|
||||||
.init(&mut timer)
|
.init(&mut timer)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// ST7796-specific setup that mipidsi's ST7789-based init omits. The critical one is 0xB6
|
// mipidsi's ST7796 model uses the ST7789 init (SLPOUT, MADCTL=0x48, INVON, COLMOD, NORON,
|
||||||
// (Display Function Control → 480 driving lines); without it the panel only scans part of
|
// DISPON — confirmed via the host initdump). It omits the ST7796 extension setup, and doing
|
||||||
// the screen. Sequence + values from the working CircuitPython firmware (st7796_init).
|
// 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() };
|
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, &[0xC3]).unwrap(); // unlock extension command set
|
||||||
di.send_command(0xF0, &[0x96]).unwrap();
|
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
|
// 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
|
// shows it without the bench; if the sim is right but the panel is wrong, it's a controller
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,6 @@ description = "Host renderer for pm-ui: draws the firmware UI to a framebuffer a
|
||||||
pm-ui = { path = "../pm-ui" }
|
pm-ui = { path = "../pm-ui" }
|
||||||
embedded-graphics = "0.8"
|
embedded-graphics = "0.8"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
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"
|
||||||
|
|
|
||||||
75
rust/uisim/src/bin/initdump.rs
Normal file
75
rust/uisim/src/bin/initdump.rs
Normal file
|
|
@ -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<u8>)>,
|
||||||
|
}
|
||||||
|
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<const N: usize>(
|
||||||
|
&mut self,
|
||||||
|
_pixels: impl IntoIterator<Item = [u8; N]>,
|
||||||
|
) -> Result<(), Infallible> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn send_repeated_pixel<const N: usize>(
|
||||||
|
&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::<Vec<_>>().join(" ");
|
||||||
|
println!(" 0x{c:02X} {args}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue