Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Draw image on TFT Display using ESP32 and Rust

Let's draw an image on the TFT Display. We can achieve this using the embedded-graphics crate, and optionally, the tinybmp crate.

We've already explored the tinybmp crate in the OLED module section. It allows us to load a BMP file and display it on the screen. Alternatively, you can use a raw byte array of the image to achieve the same result.

The crate requires the image to be in BMP format. If your image is in another format, you will need to convert it to BMP.

I'm using a nice image I found online, though I'm not sure who the original creator is to give proper credit. You can download the image from here. Feel free to use a different image, but make sure to convert it to BMP format.

embedded rust file

Generate project using esp-generate

To create the project, use the esp-generate command. Run the following:

esp-generate --chip esp32 tft-display-image

This will open a screen asking you to select options.

  • First, select the option "Enable unstable HAL features."

Just save it by pressing "s" in the keyboard.

Update cargo.toml

embedded-hal-bus = { version = "0.1" }
display-interface-spi = "0.5"
ili9341 = "0.6.0"
embedded-graphics = "0.8.1"

tinybmp = "0.6.0"

We've already covered the details of the other crates, as well as the basic setup for the SPI and display module code. So, we won't go over those details again. Instead, let's jump straight into displaying an image after clearing the screen.

Black background

Instead of using a white background, we'll fill the background with black for this project.

#![allow(unused)]
fn main() {
display.clear(Rgb565::BLACK).unwrap();
}

Display the image

Place the embedded-rust.bmp file inside the project root folder. The code is pretty straightforward: load the image as bytes and pass it to the from_slice function of the Bmp. Then, you can use it with the Image.

#![allow(unused)]
fn main() {
let bmp_data = include_bytes!("../../embedded-rust.bmp");
let bmp = Bmp::from_slice(bmp_data).unwrap();

let image = Image::new(&bmp, Point::new(10, 0));
image.draw(&mut display).unwrap();

}

Clone the existing project

You can clone (or refer) project I created and navigate to the tft-display-image folder.

git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/tft-display-image/

The full code

#![no_std]
#![no_main]

// Usual imports
use esp_hal::clock::CpuClock;
use esp_hal::main;
use esp_hal::time::{Duration, Instant};
use esp_println as _;

// Embedded Grpahics related
use embedded_graphics::image::Image;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;

// For loading bmp image
use tinybmp::Bmp;

// ESP32 SPI + Display Driver bridge
use display_interface_spi::SPIInterface;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::delay::Delay;
use esp_hal::spi::master::Config as SpiConfig;
use esp_hal::spi::master::Spi;
use esp_hal::spi::Mode as SpiMode;
use esp_hal::time::Rate; // For specifying SPI frequency
use ili9341::{DisplaySize240x320, Ili9341, Orientation};

// For managing GPIO state
use esp_hal::gpio::{Level, Output, OutputConfig};

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[main]
fn main() -> ! {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    // Initialize SPI
    let spi = Spi::new(
        peripherals.SPI2,
        SpiConfig::default()
            .with_frequency(Rate::from_mhz(60))
            .with_mode(SpiMode::_0),
    )
    .unwrap()
    //CLK
    .with_sck(peripherals.GPIO18)
    //DIN
    .with_mosi(peripherals.GPIO23);
    let cs = Output::new(peripherals.GPIO15, Level::Low, OutputConfig::default());
    let dc = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
    let reset = Output::new(peripherals.GPIO4, Level::Low, OutputConfig::default());

    let spi_dev = ExclusiveDevice::new_no_delay(spi, cs);
    let interface = SPIInterface::new(spi_dev, dc);

    let mut display = Ili9341::new(
        interface,
        reset,
        &mut Delay::new(),
        Orientation::Landscape,
        DisplaySize240x320,
    )
    .unwrap();
    display.clear(Rgb565::BLACK).unwrap();

    let bmp_data = include_bytes!("../../embedded-rust.bmp");
    let bmp = Bmp::from_slice(bmp_data).unwrap();

    let image = Image::new(&bmp, Point::new(10, 0));
    image.draw(&mut display).unwrap();

    loop {
        let delay_start = Instant::now();
        while delay_start.elapsed() < Duration::from_millis(5000) {}
    }
}