pm-kit: hold CS low — fix ST7796 1/4 screen (mipidsi toggles CS mid-command)
Reading mipidsi's interface/spi.rs: send_command writes the command byte and its parameters as TWO separate SpiDevice transactions, so a normal SpiDevice de-asserts CS between them. The ST7796 needs CS continuous across command+parameters, so MADCTL/COLMOD/B6 args never loaded → default scan/orientation → 1/4 + rotated (parameter-less commands and the pixel stream still worked, which is why it lit up). CircuitPython's FourWire holds CS low for the whole command; replicate that: drive the real CS (GP5) low for the session and give ExclusiveDevice a no-op CS. DC alone selects command vs data. Diagnosed entirely on the host: panelsim (new) decodes mipidsi's actual command/ pixel stream into a PNG and rendered perfectly, proving the protocol was right and the bug was in the physical SPI/CS layer — then the driver source confirmed it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
026c20523d
commit
b154ccf493
2 changed files with 150 additions and 2 deletions
|
|
@ -28,6 +28,23 @@ const XTAL_FREQ_HZ: u32 = 12_000_000;
|
|||
const WIDTH: u16 = 320;
|
||||
const HEIGHT: u16 = 480;
|
||||
|
||||
/// No-op chip-select for the SPI wrapper. The real CS (GP5) is held low for the whole session,
|
||||
/// because mipidsi's SpiInterface sends a command and its parameters as SEPARATE SpiDevice
|
||||
/// transactions — a normal SpiDevice would toggle CS between them, and the ST7796 needs CS
|
||||
/// continuous across command+parameters (so MADCTL/COLMOD/B6 args load). DC selects cmd vs data.
|
||||
struct NoCs;
|
||||
impl embedded_hal::digital::ErrorType for NoCs {
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
impl OutputPin for NoCs {
|
||||
fn set_low(&mut self) -> Result<(), core::convert::Infallible> {
|
||||
Ok(())
|
||||
}
|
||||
fn set_high(&mut self) -> Result<(), core::convert::Infallible> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[hal::entry]
|
||||
fn main() -> ! {
|
||||
let mut pac = hal::pac::Peripherals::take().unwrap();
|
||||
|
|
@ -56,7 +73,8 @@ fn main() -> ! {
|
|||
let mosi = pins.gpio3.into_function::<hal::gpio::FunctionSpi>();
|
||||
let dc = pins.gpio6.into_push_pull_output();
|
||||
let mut rst = pins.gpio7.into_push_pull_output();
|
||||
let cs = pins.gpio5.into_push_pull_output();
|
||||
let mut cs = pins.gpio5.into_push_pull_output();
|
||||
cs.set_low().unwrap(); // hold CS low for the whole session (see NoCs) — the fix for the 1/4 panel
|
||||
|
||||
let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (mosi, sclk));
|
||||
let spi = spi.init(
|
||||
|
|
@ -65,7 +83,7 @@ fn main() -> ! {
|
|||
16.MHz(),
|
||||
embedded_hal::spi::MODE_0,
|
||||
);
|
||||
let spi_device = ExclusiveDevice::new_no_delay(spi, cs).unwrap();
|
||||
let spi_device = ExclusiveDevice::new_no_delay(spi, NoCs).unwrap();
|
||||
|
||||
let mut buffer = [0u8; 512];
|
||||
let mut di = SpiInterface::new(spi_device, dc, &mut buffer);
|
||||
|
|
|
|||
130
rust/uisim/src/bin/panelsim.rs
Normal file
130
rust/uisim/src/bin/panelsim.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
//! Panel emulator: a host mipidsi `Interface` that decodes the REAL command/pixel stream
|
||||
//! (CASET/RASET/RAMWR + send_pixels/send_repeated_pixel) into a framebuffer, then renders
|
||||
//! pm-ui *through mipidsi* and saves a PNG. Unlike the plain uisim (which draws to its own
|
||||
//! buffer and never exercises mipidsi's pixel path), this reproduces addressing/stride/count
|
||||
//! bugs — the kind causing the 1/4 + stripes on hardware. `cargo run --bin panelsim`.
|
||||
|
||||
use core::convert::Infallible;
|
||||
use embedded_graphics::pixelcolor::raw::RawU16;
|
||||
use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_graphics::prelude::*;
|
||||
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;
|
||||
|
||||
const W: usize = 320;
|
||||
const H: usize = 480;
|
||||
|
||||
struct Panel {
|
||||
fb: Vec<Rgb565>,
|
||||
xs: usize,
|
||||
xe: usize,
|
||||
ys: usize,
|
||||
ye: usize,
|
||||
cx: usize,
|
||||
cy: usize,
|
||||
pixels_written: u64,
|
||||
}
|
||||
impl Panel {
|
||||
fn new() -> Self {
|
||||
Panel { fb: vec![Rgb565::BLACK; W * H], xs: 0, xe: W - 1, ys: 0, ye: H - 1, cx: 0, cy: 0, pixels_written: 0 }
|
||||
}
|
||||
fn put(&mut self, c: Rgb565) {
|
||||
if self.cx < W && self.cy < H {
|
||||
self.fb[self.cy * W + self.cx] = c;
|
||||
}
|
||||
self.pixels_written += 1;
|
||||
self.cx += 1;
|
||||
if self.cx > self.xe {
|
||||
self.cx = self.xs;
|
||||
self.cy += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn be(a: &[u8]) -> usize {
|
||||
((a[0] as usize) << 8) | a[1] as usize
|
||||
}
|
||||
impl Interface for Panel {
|
||||
type Word = u8;
|
||||
type Error = Infallible;
|
||||
fn send_command(&mut self, cmd: u8, args: &[u8]) -> Result<(), Infallible> {
|
||||
match cmd {
|
||||
0x2A if args.len() >= 4 => {
|
||||
self.xs = be(&args[0..2]);
|
||||
self.xe = be(&args[2..4]);
|
||||
}
|
||||
0x2B if args.len() >= 4 => {
|
||||
self.ys = be(&args[0..2]);
|
||||
self.ye = be(&args[2..4]);
|
||||
}
|
||||
0x2C => {
|
||||
self.cx = self.xs;
|
||||
self.cy = self.ys;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn send_pixels<const N: usize>(&mut self, pixels: impl IntoIterator<Item = [u8; N]>) -> Result<(), Infallible> {
|
||||
for px in pixels {
|
||||
let v = ((px[0] as u16) << 8) | px[1] as u16;
|
||||
self.put(Rgb565::from(RawU16::new(v)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn send_repeated_pixel<const N: usize>(&mut self, pixel: [u8; N], count: u32) -> Result<(), Infallible> {
|
||||
let v = ((pixel[0] as u16) << 8) | pixel[1] as u16;
|
||||
let c = Rgb565::from(RawU16::new(v));
|
||||
for _ in 0..count {
|
||||
self.put(c);
|
||||
}
|
||||
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, _: u32) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut delay = NoDelay;
|
||||
let mut display = Builder::new(ST7796, Panel::new())
|
||||
.reset_pin(NoPin)
|
||||
.display_size(W as u16, H as u16)
|
||||
.color_order(ColorOrder::Bgr)
|
||||
.invert_colors(ColorInversion::Inverted)
|
||||
.orientation(Orientation::new().flip_horizontal())
|
||||
.init(&mut delay)
|
||||
.unwrap();
|
||||
|
||||
pm_ui::draw_ui(&mut display).unwrap();
|
||||
|
||||
let (panel, _m, _r) = display.release();
|
||||
println!("pixels written by mipidsi: {} (full screen = {})", panel.pixels_written, W * H);
|
||||
|
||||
let img = image::RgbImage::from_fn(W as u32, H as u32, |x, y| {
|
||||
let c = panel.fb[(y as usize) * W + x as usize];
|
||||
let r = (c.r() << 3) | (c.r() >> 2);
|
||||
let g = (c.g() << 2) | (c.g() >> 4);
|
||||
let b = (c.b() << 3) | (c.b() >> 2);
|
||||
image::Rgb([r, g, b])
|
||||
});
|
||||
img.save("panelsim.png").unwrap();
|
||||
println!("wrote panelsim.png");
|
||||
}
|
||||
Loading…
Reference in a new issue