mipidsi

repository·master·Indexed 42 days ago

https://github.com/almindor/mipidsi

A generic Rust display driver for TFT displays implementing the MIPI Display Command Set. It provides a hardware-agnostic way to drive displays via SPI or Parallel GPIO using the embedded-graphics API. Supported models include ST7789, ILI9341, ILI9488, and others. Version 0.10.0 requires stable Rust 1.75.0+.

Tokens
11.4K
Snippets
37
Records
51
Agent score
54%

What's inside mipidsi

  1. How mipidsi works: Architecture and Core Concepts

    master

    The mipidsi crate provides a generic display driver for TFT displays that implement the MIPI Display Command Set.

    Core Components

    • Display driver: Contains the primary functionality for interacting with the display.
    • interface::Interface: An abstraction used to communicate with hardware via transports. Currently, it supports SPI and Parallel GPIO.
    • Model trait: Each specific display model (e.g., ST7789, ILI9341) implements this trait for every color format it supports.
    • Builder: A struct used to configure and instantiate specific display models, including handling different screen sizes and window addressing offsets.
    • embedded-graphics-core: The crate uses this as the underlying drawing API.

    Note: Despite the name, this crate does not support the MIPI Display Serial Interface (DSI) protocol; it supports displays using the MIPI Display Command Set.

  2. Migrate from Display direct construction to Builder (v0.4 -> 0.5)

    master

    In version 0.5, the recommended way to construct a display is using the Builder pattern rather than calling Display helper constructors directly.

    // v0.4
    let display = Display::st7789(di, rst, DisplayOptions::default());
    display.init(&mut delay);
    
    // v0.5
    let display = Builder::st7789(di)
        .with_display_size(240, 240)
        .init(&mut delay, Some(rst));
    // v0.4
    let display = Display::st7789(di, rst, DisplayOptions::default());
    display.init(&mut delay);
    
    // v0.5
    let display = Builder::st7789(di) // known model or with_model(model)
        .with_display_size(240, 240) // set any options on the builder before init
        .init(&mut delay, Some(rst)); // optional reset pin
  3. Migrate SPI interfaces (v0.8 -> 0.9)

    master

    In version 0.9, mipidsi replaced the display_interface dependency with its own internal traits for better performance.

    To migrate SPI-based displays:

    1. Replace display_interface_spi::SPIInterface with mipidsi::interface::SpiInterface.
    2. SpiInterface::new now requires a mutable buffer (e.g., 512 bytes) to be passed in.
    // before
    use display_interface_spi::SPIInterface;
    let di = SPIInterface::new(spi_device, dc);
    
    // after
    use mipidsi::interface::SpiInterface;
    let mut buffer = [0_u8; 512];
    let di = SpiInterface::new(spi_device, dc, &mut buffer);
    // before
    use display_interface_spi::SPIInterface;
    
    let di = SPIInterface::new(spi_device, dc);
    
    // after
    use mipidsi::interface::SpiInterface;
    
    let mut buffer = [0_u8; 512];
    let di = SpiInterface::new(spi_device, dc, &mut buffer);
  4. Migrate Display Builder usage (v0.7 -> 0.8)

    master

    In version 0.8, mipidsi updated its dependency on embedded-hal to 1.0 and changed how displays are constructed.

    Use generic Builder::new instead of model-specific constructors

    Model-specific constructors like Builder::ili9341_rgb565 were removed. Use Builder::new with a model instance instead.

    // 0.7
    use mipidsi::Builder;
    let display = Builder::ili9341_rgb565(di)
      .init(&mut delay, None)?;
    
    // 0.8
    use mipidsi::{Builder, models::ILI9341Rgb565};
    let display = Builder::new(ILI9341Rgb565, di)
      .init(&mut delay)?;

    Use .reset_pin() instead of passing to .init()

    The reset pin parameter was removed from Builder::init. Use the .reset_pin() setter on the builder instead.

    // 0.7
    let display = Builder::new(ili9341_rgb565, di)
      .init(&mut delay, Some(rst))?;
    
    // 0.8
    use mipidsi::{Builder, models::ILI9341Rgb565};
    let display = Builder::new(ILI9341Rgb565, di)
      .reset_pin(rst)
      .init(&mut delay)?;

    Builder method renaming

    Methods previously prefixed with with_ (e.g., with_invert_colors) have been renamed to remove the prefix to comply with Rust API guidelines.

    // 0.7
    let display = Builder::new(ili9341_rgb565, di)
      .with_invert_colors(ColorInversion::Normal)
      .with_orientation(Orienation::default())
      .init(&mut delay, Some(rst))?;
    
    // 0.8
    use mipidsi::{Builder, models::ILI9341Rgb565};
    let display = Builder::new(ILI9341Rgb565, di)
      .invert_colors(ColorInversion::Normal)
      .orientation(Orienation::default())
      .reset_pin(rst)
      .init(&mut delay)?;

    Color Defaults

    In 0.8, all models use consistent default values: no color inversion and RGB subpixel order. If your display shows incorrect colors, check the troubleshooting guide.

  5. Build and deploy the ST7789 example on Mac OS

    master

    To cross-compile the example for a Raspberry Pi Zero W from a Mac OS host, you need musl-cross installed (available via Homebrew: brew install FiloSottile/musl-cross/musl-cross --without-x86_64 --with-arm_hf).

    Follow these steps to build, strip the binary to reduce size, copy it to the Pi via SSH, and run it.

    # build for rpi zero w
    cargo build --release --target=arm-unknown-linux-musleabihf
    
    # look at the size of the bin file
    ls -lh target/arm-unknown-linux-musleabihf/release/spi-st7789-rpi-zero-w
    
    # strip it
    arm-linux-musleabihf-strip target/arm-unknown-linux-musleabihf/release/spi-st7789-rpi-zero-w
    
    # look at it now ;)
    ls -lh target/arm-unknown-linux-musleabihf/release/spi-st7789-rpi-zero-w
    
    # copy over ssh
    scp target/arm-unknown-linux-musleabihf/release/spi-st7789-rpi-zero-w pi@raspberrypi.local:~/
    
    # ssh into the rpi to run it
    ssh pi@raspberrypi.local
    
    # run it
    ./spi-st7789-rpi-zero-w
  6. Enable SPI on Raspberry Pi

    master

    To use the SPI interface required for this example, you must enable SPI on your Raspberry Pi. You can do this using any of the following methods:

    • Use the interactive configuration tool: sudo raspi-config
    • Use the non-interactive command: sudo raspi-config nonint do_spi 0
    • Manually edit /boot/config.txt and add the line dtparam=spi=on
    • Use the graphical Raspberry Pi Configuration UI.
  7. Migrate Parallel GPIO interfaces (v0.8 -> 0.9)

    master

    In version 0.9, parallel interfaces were updated to use mipidsi::interface types.

    To migrate parallel displays:

    1. Replace display_interface_parallel_gpio::PGPIO{8,16}BitInterface with mipidsi::interface::ParallelInterface.
    2. Use mipidsi::interface::Generic{8,16}BitBus instead of the display_interface_parallel_gpio version.
    // before
    use display_interface_parallel_gpio::Generic8BitBus;
    use display_interface_parallel_gpio::PGPIO8BitInterface;
    
    let bus = Generic8BitBus::new(pins);
    let di = PGPIO8BitInterface::new(bus, dc, wr);
    
    // after
    use mipidsi::interface::Generic8BitBus;
    use mipidsi::interface::ParallelInterface;
    
    let bus = Generic8BitBus::new(pins);
    let di = ParallelInterface::new(bus, dc, wr);
  8. Initialize a display using the Builder pattern

    master

    To use a display, you must first create an Interface (such as SpiInterface) and then use the Builder to configure the specific model, pins, and initialization sequence.

    Common steps include:

    1. Creating an interface from a hardware peripheral (like SpiDevice), a Data/Command (DC) pin, and a buffer.
    2. Using Builder::new(MODEL, interface) to start configuration.
    3. Configuring optional pins like .reset_pin(rst).
    4. Calling .init(&mut delay) to perform the hardware initialization sequence.

    Once initialized, you can use methods like .clear(color) to manipulate the display.

    // create a SpiInterface from SpiDevice, a DC pin and a buffer
    let mut buffer = [0u8; 512];
    let di = SpiInterface::new(spi, dc, &mut buffer);
    
    // create the ILI9486 display driver in rgb666 color mode from the display interface and use a HW reset pin during init
    let mut display = Builder::new(ILI9486Rgb666, di)
        .reset_pin(rst)
        .init(&mut delay)?;
    
    // clear the display to black
    display.clear(Rgb666::BLACK)?;
  9. How pixel batching works (Rows and Blocks)

    master

    To optimize SPI transmission, mipidsi uses a hierarchical batching strategy:

    1. Pixel Rows: Contiguous pixels located on the same Y-coordinate. A RowIterator groups pixels into PixelRow objects.
    2. Pixel Blocks: Contiguous PixelRows that share the same start (x_left) and end (x_right) column boundaries. A BlockIterator groups rows into PixelBlock objects.

    This hierarchy ensures that the largest possible rectangular areas of pixels are sent to the display interface at once, minimizing command overhead and maximizing throughput.

  10. Implement or use a Display Model via the Model trait

    master

    The Model trait defines the hardware capabilities and command sequences for specific display controllers (e.g., st7789, ili9341). When working with mipidsi, you interact with these models to initialize hardware, manage power states, and control display features like scrolling or tearing effects.

    Key responsibilities of a Model implementation include:

    • Initialization: Using init to set up the display and return the initial SetAddressMode.
    • Power Management: Using sleep and wake to manage the display's power state (note that wake must be called before issuing other commands).
    • Memory Access: Using write_memory_start before writing pixel data.
    • Display Control: Managing address windows, vertical scrolling regions, and tearing effects.
    /// Example of the Model trait requirements (conceptual)
    impl Model for MyCustomModel {
        type ColorFormat = Rgb565;
        const FRAMEBUFFER_SIZE: (u16, u16) = (240, 320);
    
        fn init<DELAY, DI>(&mut self, di: &mut DI, delay: &mut DELAY, options: &ModelOptions) -> Result<SetAddressMode, ModelInitError<DI::Error>> 
        where DELAY: DelayNs, DI: Interface {
            // implementation
        }
        // ... other required methods
    }