ivmarkov / rust-esp32-std-demo

Rust on ESP32 STD demo app. A demo STD binary crate for the ESP32[XX] and ESP-IDF, which connects to WiFi, Ethernet, drives a small HTTP server and draws on a LED screen.
Apache License 2.0
785 stars 105 forks source link

Unable to get the display working on a Lilygo Display S3 device #152

Closed milewski closed 1 year ago

milewski commented 1 year ago

Hi, I have a Lilygo Display S3, and I'm trying to run your esp32s3_usb_otg example in it, it runs fine, however, the display part is not working, (it compiles fine but nothing shows on the screen, it is all black) https://github.com/ivmarkov/rust-esp32-std-demo/blob/50ab64ed4bf51f02807864458e096c595b94cc1c/src/main.rs#L1045-L1071, I have tried to re-map the pins but no success, any idea of what I could be doing wrong?

ivmarkov commented 1 year ago
ivmarkov commented 1 year ago

By the way, these questions belong to the forum - you might want to join it.

ivmarkov commented 1 year ago

Oh, I did not realize that your board has the LCD hard-wired, so you cannot confuse the wirings. Besides the frequency advice which is still valid, are you sure you are using the right SPI, and the right pins?

milewski commented 1 year ago

Im not sure if I wired the pin correctly, I don't really know which pin corresponds to sclk and sdo I think I can only say "confidently" that these are correct: BL, CS, DC and RES

image

For the other 2 I have tried a combination of various other pins but none seems to work, Only the backlight lights on...

milewski commented 1 year ago

This is what I have currently:

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();

    let backlight = peripherals.pins.gpio38;
    let dc = peripherals.pins.gpio7;
    let rst = peripherals.pins.gpio5;
    let cs = peripherals.pins.gpio6;
    let spi = peripherals.spi2;

    let sclk = peripherals.pins.gpio12; // these 2 pins doesn't seem to be marked on the pin diagram of my board :(
    let sdo = peripherals.pins.gpio13;

    let mut backlight = PinDriver::input_output_od(backlight)?;
    backlight.set_high()?;

    let di = SPIInterfaceNoCS::new(
        spi::SpiDeviceDriver::new_single(
            spi,
            sclk,
            sdo,
            Option::<gpio::AnyIOPin>::None,
            Some(cs),
            &spi::SpiDriverConfig::new().dma(spi::Dma::Disabled),
            &spi::SpiConfig::new().baudrate(40.MHz().into()),
        )?,
        gpio::PinDriver::output(dc)?,
    );

    let mut display = mipidsi::Builder::st7789(di)
        .init(&mut delay::Ets, Some(gpio::PinDriver::output(rst)?))
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    display
        .clear(RgbColor::RED)
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    loop {
    }
}
ivmarkov commented 1 year ago

If you look at the picture, and what is written here, this board is not using SPI at all, but I8080 (parallel IO) instead.

ivmarkov commented 1 year ago

Closing. Feel free to open an issue for support for I8080 in esp-idf-hal. Ot better yet - contribute a PR for it.

milewski commented 1 year ago

Ah okay, I see so the examples on this project are not applicable .. okay I will try to figure out what to do, first time touching microcontrollers so I'm not really familiar with any of the terms yet..

ivmarkov commented 1 year ago

What we need is to wrap - in Rust - in esp-idf-hal - bits and pieces of this ESP IDF C component that implements the I8080 interface: https://github.com/espressif/esp-idf/tree/master/components/esp_lcd

It is documented here.

You can actually use this component (even if with unsafe API calls into C which we can easily expose in esp-idf-sys).

But then again - join the Matrix chat and we can discuss there: https://matrix.to/#/#esp-rs:matrix.org

milewski commented 1 year ago

Hi thanks for the tip, I was able to figure it out after you mentioned that my board uses parallel io here was the solution:

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();

    let mut backlight = PinDriver::output(peripherals.pins.gpio38)?;
    let dc = PinDriver::output(peripherals.pins.gpio7)?;
    let mut cs = PinDriver::output(peripherals.pins.gpio6)?;
    let mut rst = PinDriver::output(peripherals.pins.gpio5)?;
    let wr = PinDriver::output(peripherals.pins.gpio8)?;
    let mut rd = PinDriver::output(peripherals.pins.gpio9)?;

    backlight.set_high()?;

    // set to low to enable display
    cs.set_low()?;

    // set to high when not in use
    rd.set_high()?;

    let mut d0 = PinDriver::output(peripherals.pins.gpio39)?;
    let mut d1 = PinDriver::output(peripherals.pins.gpio40)?;
    let mut d2 = PinDriver::output(peripherals.pins.gpio41)?;
    let mut d3 = PinDriver::output(peripherals.pins.gpio42)?;
    let mut d4 = PinDriver::output(peripherals.pins.gpio45)?;
    let mut d5 = PinDriver::output(peripherals.pins.gpio46)?;
    let mut d6 = PinDriver::output(peripherals.pins.gpio47)?;
    let mut d7 = PinDriver::output(peripherals.pins.gpio48)?;

    let bus = Generic8BitBus::new((d0, d1, d2, d3, d4, d5, d6, d7))
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    let di = PGPIO8BitInterface::new(bus, dc, wr);

    let mut display = mipidsi::Builder::st7789(di)
        .with_display_size(170, 320)
        .with_invert_colors(ColorInversion::Inverted)
        .init(&mut delay::FreeRtos, Some(rst))
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    display.clear(RgbColor::RED)
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    loop {

    }
}

I joined the matrix chat!

ivmarkov commented 1 year ago

Hi thanks for the tip, I was able to figure it out after you mentioned that my board uses parallel io here was the solution:

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();

    let mut backlight = PinDriver::output(peripherals.pins.gpio38)?;
    let dc = PinDriver::output(peripherals.pins.gpio7)?;
    let mut cs = PinDriver::output(peripherals.pins.gpio6)?;
    let mut rst = PinDriver::output(peripherals.pins.gpio5)?;
    let wr = PinDriver::output(peripherals.pins.gpio8)?;
    let mut rd = PinDriver::output(peripherals.pins.gpio9)?;

    backlight.set_high()?;

    // set to low to enable display
    cs.set_low()?;

    // set to high when not in use
    rd.set_high()?;

    let mut d0 = PinDriver::output(peripherals.pins.gpio39)?;
    let mut d1 = PinDriver::output(peripherals.pins.gpio40)?;
    let mut d2 = PinDriver::output(peripherals.pins.gpio41)?;
    let mut d3 = PinDriver::output(peripherals.pins.gpio42)?;
    let mut d4 = PinDriver::output(peripherals.pins.gpio45)?;
    let mut d5 = PinDriver::output(peripherals.pins.gpio46)?;
    let mut d6 = PinDriver::output(peripherals.pins.gpio47)?;
    let mut d7 = PinDriver::output(peripherals.pins.gpio48)?;

    let bus = Generic8BitBus::new((d0, d1, d2, d3, d4, d5, d6, d7))
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    let di = PGPIO8BitInterface::new(bus, dc, wr);

    let mut display = mipidsi::Builder::st7789(di)
        .with_display_size(170, 320)
        .with_invert_colors(ColorInversion::Inverted)
        .init(&mut delay::FreeRtos, Some(rst))
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    display.clear(RgbColor::RED)
        .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;

    loop {

    }
}

Nothing wrong with this solution per se, but have in mind that it uses (if I'm not mistaken) bit-banging to send the data to the screen. In other words, it'll keep your CPU busy while the data is being transferred. Which might or might not be a problem for you. Maybe not - initially - and for "playing with stuff".

The C drivers I was pointing you at are wrapping the native ESP32-S3 LED/CAM peripheral, which can utilize DMA for pushing the pixels to the screen. During that time your CPU is free to do something else more useful. This is specific for the S3 MCU though. And hence why hardware folks started bundling I8080-based LED screens with this MCU. Your pure-Rust solution is generic though and would work on any MCU that has a bunch of output pins available.

I joined the matrix chat!

Welcome!

ivmarkov commented 1 year ago

Btw I'm surprised that this works at all:

.init(&mut delay::FreeRtos, Some(rst))

The FreeRtos "delayer" is only useful for delays >= 10ms as that's its minimal resolution. I was expecting the I8080 bitbanging to use much smaller delays (for which you should use Ets instead). Oh well.

The difference between FreeRtos and Ets is that with FreeRtos your thread will properly sleep during the delay, so the CPU can be scheduled to do something else. Ets will do a busyloop, but is more precise.

milewski commented 1 year ago

Uhm I tried to switch it to delay:Ets but I don't see any difference, however, I do have an issue, the example software that came flashed into the board had a nice smooth intro animation, which I guess is done by drawing a sequence of images frame by frame... I'm trying to do the same but each frame takes so long to render, I can see each frame being drawn line by line ...

I'm now trying to figure out the DMA thing you mentioned to see if that solves this issue, thanks again!

PavelMostovoy commented 8 months ago

Hi there .. I try to make the same "Hello World" but even in this simple code I have a problem. it works fine with ttgo-display with SPI, but with ttgo S3 - it return display.clear(RgbColor::RED) | ^^^^^ method not found inDisplay<PGPIO8BitInterface<Generic8BitBus<`

so backlight works, logig works but I can do anythinc with display (( any advice ?

upd: adding use embedded_graphics::draw_target::DrawTarget;. resolve this issue but this is strange..

ivmarkov commented 8 months ago

upd: adding use embedded_graphics::draw_target::DrawTarget;. resolve this issue but this is strange..

Nothing strange, this is how Rust works. Display<PGPIO8BitInterface<Generic8BitBus<... does implement embedded_graphics::draw_target::DrawTarget (and the clear method is a method on the DrawTarget trait), HOWEVER, to use methods from a trait, you DO need to import ("use") that trait. It is not enough to have just the implementing struct in scope.

dunef-io commented 7 months ago

To improve the performance by a lot you need to start the code with 'cargo espflash flash --monitor --release', or add

[profile.dev]
opt-level = 1 #  a number in the range of 1-3

to your Cargo.toml file. The optimization level makes a huge difference in the execution performance.