Skip to content

Commit

Permalink
raw image module
Browse files Browse the repository at this point in the history
  • Loading branch information
ImplFerris committed Jan 13, 2025
1 parent d7eec3d commit 3667e3c
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,6 @@
- [Circuit](./oled/circuit.md)
- [Crates](./oled/crates.md)
- [Hello, Rust!](./oled/hello-rust/index.md)
- [Raw Image](./oled/raw-image/index.md)
- [Code](./oled/raw-image/code.md)
- [Projects](./projects.md)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/oled/images/resistance-128x64-oled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/oled/images/resistance-pixel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions src/oled/raw-image/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
## Code

By now, i hope you understand how the image is represented in the byte array. Now, let's move on to the coding part.

## 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:

```sh
esp-generate --chip esp32 oled-image
```

This will open a screen asking you to select options.

- Select the option "Adds embassy framework support".

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

## Update Cargo.toml

```toml
ssd1306 = { git = "https://github.com/rust-embedded-community/ssd1306.git", rev = "f3a2f7aca421fbf3ddda45ecef0dfd1f0f12330e", features = [
"async",
] }
embedded-graphics = "0.8.1"
```

## Boilerplate: Initialize I2C and Display instance
We have already explained this part in the previous [chapter](../hello-rust/index.md).

```rust
let i2c0 = esp_hal::i2c::master::I2c::new(
peripherals.I2C0,
esp_hal::i2c::master::Config {
frequency: 400.kHz(),
timeout: Some(100),
},
)
.with_scl(peripherals.GPIO18)
.with_sda(peripherals.GPIO23)
.into_async();

let interface = I2CDisplayInterface::new(i2c0);
// initialize the display
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
display.init().await.unwrap();
```

## Draw Image

We have created a byte array constant to represent the Ohm symbol.

```rust
// 8x5 pixels
#[rustfmt::skip]
const IMG_DATA: &[u8] = &[
0b00111000,
0b01000100,
0b01000100,
0b00101000,
0b11101110,
];
```

We will create a raw image using the ImageRaw::new function. We need to specify the image width(i.e 8) and pixel color format with turbofish syntax `::<>`. The height of the image will be calculated automatically based on the data length and format. Since the display module we are using is only of two colors, we will use the BinaryColor enum.

Then we will draw the image at the starting position of the display, which is Point zero (x = 0, y = 0). Finally, we will flush the data to the display module.

```rust
let raw_image = ImageRaw::<BinaryColor>::new(IMG_DATA, 8);

let image = Image::new(&raw_image, Point::zero());

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-image` folder.

```sh
git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/oled-image
```

## The full code
```rust
#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use embedded_graphics::{
image::{Image, ImageRaw},
pixelcolor::BinaryColor,
prelude::Point,
};
use esp_backtrace as _;
use esp_hal::prelude::*;
use log::info;
use ssd1306::{
mode::DisplayConfigAsync, prelude::DisplayRotation, size::DisplaySize128x64,
I2CDisplayInterface, Ssd1306Async,
};

use embedded_graphics::prelude::*;

#[rustfmt::skip]
const IMG_DATA: &[u8] = &[
0b00111000,
0b01000100,
0b01000100,
0b00101000,
0b11101110,
];

#[main]
async fn main(_spawner: Spawner) {
let peripherals = esp_hal::init({
let mut config = esp_hal::Config::default();
config.cpu_clock = CpuClock::max();
config
});

esp_println::logger::init_logger_from_env();

let timer0 = esp_hal::timer::timg::TimerGroup::new(peripherals.TIMG1);
esp_hal_embassy::init(timer0.timer0);

info!("Embassy initialized!");

let i2c0 = esp_hal::i2c::master::I2c::new(
peripherals.I2C0,
esp_hal::i2c::master::Config {
frequency: 400.kHz(),
timeout: Some(100),
},
)
.with_scl(peripherals.GPIO18)
.with_sda(peripherals.GPIO23)
.into_async();

let interface = I2CDisplayInterface::new(i2c0);
// initialize the display
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
display.init().await.unwrap();

let raw_image = ImageRaw::<BinaryColor>::new(IMG_DATA, 8);

let image = Image::new(&raw_image, Point::zero());

image.draw(&mut display).unwrap();
display.flush().await.unwrap();

loop {
Timer::after(Duration::from_secs(1)).await;
}
}
```
20 changes: 20 additions & 0 deletions src/oled/raw-image/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Draw Raw Image on OLED Display with ESP32

In this exercise, we will draw a raw image using only byte arrays. We will create the Ohm (Ω) symbol in a 1BPP (1 Bit Per Pixel) format.

## 1BPP Image
The 1BPP (1 bit per pixel) format uses a single bit for each pixel. It can represent only two colors, typically black and white. If the bit value is 0, it will typically be full black. If the bit value is 1, it will typically be full white.

We will create the ohm symbol using an 8x5 pixel grid in 1bpp format. I have highlighted the 1's in the byte array to show how they turn on the pixels to form the ohm symbol.
<img style="display: block; margin: auto;" title="ohm symbol 1bpp image format" src="../images/embedded-graphics-image-illustration-1bpp.png"/>

I chose 8 as the width to keep the example simple. This makes it easy to represent the 8 pixels width using a single byte (8 bits). But if you increase the width, it won't fit in one byte anymore, so it will need to be spread across multiple elements in the byte array. I will explain this in later chapters. For now, let's keep it simple.

## Ohm symbol on the OLED Display (128x64)
Let me show you how it looks when the Ohm symbol is positioned on the OLED display (128x64 resolution) at position zero(x is 0 and y is also 0).
<img style="display: block; margin: auto;" title="ohm symbol in 128x64 pixel" src="../images/resistance-128x64-oled.png"/>

This is an enlarged illustration. When you see the symbol on the actual display module, it will be small.

## Reference
- [Embedded Graphics' ImageRaw Documentation](https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.ImageRaw.html)

0 comments on commit 3667e3c

Please sign in to comment.