Using Bitmap Image file
You can use BMP (.bmp) files directly instead of raw image data by utilizing the tinybmp crate. tinybmp is a lightweight BMP parser designed for embedded environments. While it is mainly intended for drawing BMP images to embedded_graphics DrawTargets, it can also be used to parse BMP files for other applications. This is perfect for our purpose.
BMP file
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. For example, you can use the following command on Linux to convert a PNG image to a monochrome BMP:
convert ferris.png -monochrome ferris.bmp
I have created the Ferris BMP file, which you can use for this exercise. Download it from here.

Generate project using esp-generate
We will enable async (Embassy) support for this project. To create the project, use the esp-generate
command. Run the following:
esp-generate --chip esp32 oled-image
This will open a screen asking you to select options.
- Select the option "Enable unstable HAL features"
- Then, select the option "Adds embassy framework support".
Just save it by pressing "s" in the keyboard.
Update Cargo.toml
ssd1306 = { git = "https://github.com/rust-embedded-community/ssd1306.git", rev = "f3a2f7aca421fbf3ddda45ecef0dfd1f0f12330e", features = [
"async",
] }
embedded-graphics = "0.8.1"
tinybmp = "0.6.0"
Using the BMP File
Place the ferris.bmp file inside the src 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() { // the usual boilerplate code goes here... // Include the BMP file data. let bmp_data = include_bytes!("../ferris.bmp"); // Parse the BMP file. let bmp = Bmp::from_slice(bmp_data).unwrap(); // usual code: let image = Image::new(&bmp, Point::new(32, 0)); image.draw(&mut display).unwrap(); display.flush().await.unwrap(); }
Clone the existing project
You can also clone (or refer) project I created and navigate to the oled-bmp
folder.
git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/oled-bmp
Full code
#![no_std] #![no_main] use defmt::info; use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use embedded_graphics::image::Image; use embedded_graphics::prelude::Point; use embedded_graphics::prelude::*; use esp_hal::timer::timg::TimerGroup; use esp_hal::{clock::CpuClock, time::Rate}; use esp_println as _; use ssd1306::mode::DisplayConfigAsync; use ssd1306::{ prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async, }; use tinybmp::Bmp; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } #[esp_hal_embassy::main] async fn main(_spawner: Spawner) { // generator version: 0.3.1 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let timer0 = TimerGroup::new(peripherals.TIMG1); esp_hal_embassy::init(timer0.timer0); info!("Embassy initialized!"); let i2c_bus = esp_hal::i2c::master::I2c::new( peripherals.I2C0, esp_hal::i2c::master::Config::default().with_frequency(Rate::from_khz(400)), ) .unwrap() .with_scl(peripherals.GPIO18) .with_sda(peripherals.GPIO23) .into_async(); let interface = I2CDisplayInterface::new(i2c_bus); // initialize the display let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) .into_buffered_graphics_mode(); display.init().await.unwrap(); // Include the BMP file data. let bmp_data = include_bytes!("../ferris.bmp"); // Parse the BMP file. let bmp = Bmp::from_slice(bmp_data).unwrap(); // usual code: let image = Image::new(&bmp, Point::new(32, 0)); image.draw(&mut display).unwrap(); display.flush().await.unwrap(); loop { Timer::after(Duration::from_secs(1)).await; } }