見出し画像

RustでST7735ディスプレイに画像を描画する

前回の記事で、RustとESP32を使ってLチカを試しました。

今回はST7735のTFT液晶ディスプレイで画像の描画を行ってみます。

準備

以下のライブラリを追加します。

組み込み用の2Dグラフィックライブラリ

ST7735向けのデバイスドライバ

 依存パッケージを追加します。

$ cargo add embedded-graphics st7735-lcd

Cargo.tomlのdependenciesに以下が追加されました。

embedded-graphics = "0.8.1"
st7735-lcd = "0.10.0"

SPI通信について

ESP32とディスプレイはSPI(Serial Peripheral Interface)を通して通信します。

SPIはマスターデバイスと複数のスレーブデバイスで通信を行います。 通常、以下の4本の信号線SPIバスを含みます。

  • SCLK: クロック用で、データの同期に使用する

  • MOSI (Master Out Slave In): マスターがスレーブに情報を送る

  • MISO (Master In Slave Out): スレーブがマスターに情報を返す

  • CS/SS (Chip Select/Slave Select) : スレーブを選択する

ESP32とST7735ディスプレイを接続する場合、マスターがESP32、スレーブがST7735になります。 基本的に、ESP32がデータを送信し、ST7735がデータを受信するため、今回の用途ではMISOは使いません。

ピン設定

今回は以下のピン設定で、ESP32とST7735ディスプレイを接続します。

ピン設定について

今回、ディスプレイは常時点灯させるため、LEDピンを3.3V電源に直接接続しました。

① ディスプレイにHelloWorldを描画

はじめに、ST7735ディスプレイ1枚に対して、"hello world"と表示するプログラムを作成します。

全体のコードは以下の通りです。 エラーハンドリングなどは雑なのでご注意ください。

use embedded_graphics::mono_font::ascii::FONT_6X10;
use embedded_graphics::mono_font::MonoTextStyleBuilder;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use embedded_graphics::text::{Baseline, Text};
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::{AnyIOPin, PinDriver};
use esp_idf_hal::prelude::Peripherals;
use esp_idf_hal::prelude::*;
use esp_idf_hal::spi;

use st7735_lcd;

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;

    // ピンの設定
    let spi = peripherals.spi2;
    let sclk = peripherals.pins.gpio18;
    let sdo = peripherals.pins.gpio23;
    let cs = peripherals.pins.gpio15;
    let rst = PinDriver::output(peripherals.pins.gpio4)?;
    let dc = PinDriver::output(peripherals.pins.gpio2)?;

    // SPIドライバーの設定
    let driver_config = Default::default();
    let spi_config = spi::SpiConfig::new().baudrate(20.MHz().into());
    let spi =
        spi::SpiDeviceDriver::new_single(spi, sclk, sdo, None::<AnyIOPin>, Some(cs), &driver_config, &spi_config)?;

    // ディスプレイの設定
    let rgb = false;
    let inverted = false;
    let width = 128;
    let height = 128;

    let mut delay = FreeRtos;
    let mut display = st7735_lcd::ST7735::new(spi, dc, rst, rgb, inverted, width, height);

    // ディスプレイの初期化
    display.init(&mut delay).unwrap();
    display.clear(Rgb565::BLACK).unwrap();
    display.set_offset(0, 0);

    // テキストのスタイルを設定
    let text_style = MonoTextStyleBuilder::new()
        .font(&FONT_6X10)
        .text_color(Rgb565::WHITE)
        .build();

    // "hello world"テキストを描画
    Text::with_baseline(
        "hello world",
        Point::new(0, 0),
        text_style,
        Baseline::Top,
    ).draw(&mut display).unwrap();

    println!("The screen should display 'hello world'.");
    loop {
        FreeRtos::delay_ms(1000);
    }
}

以下、コードを解説します。

ピンの設定

let spi = peripherals.spi2;
let sclk = peripherals.pins.gpio18;
let sdo = peripherals.pins.gpio23;
let cs = peripherals.pins.gpio15;
let rst = PinDriver::output(peripherals.pins.gpio4)?;
let dc = PinDriver::output(peripherals.pins.gpio2)?;

ST7735 LCDディスプレイとの通信に必要なピンを設定します。 上述の今回のピン設定に対応しています。

SPIドライバーの設定

let driver_config = Default::default();
let spi_config = spi::SpiConfig::new().baudrate(20.MHz().into());
let spi = spi::SpiDeviceDriver::new_single(spi, sclk, sdo, None::<AnyIOPin>, Some(cs), &driver_config, &spi_config)?;

SPIドライバーを設定しています。 通信速度を20MHzに設定し、ピン設定を指定してSPIデバイスドライバーを初期化します。 MISOピンは使用しないため、None::<AnyIOPin>を指定します。

ディスプレイの初期化

let rgb = false;
let inverted = false;
let width = 128;
let height = 128;

let mut delay = FreeRtos;
let mut display = st7735_lcd::ST7735::new(spi, dc, rst, rgb, inverted, width, height);

display.init(&mut delay).unwrap();
display.clear(Rgb565::BLACK).unwrap();
display.set_offset(0, 0);

ST7735ディスプレイオブジェクトを作成し、初期化します。 ディスプレイのサイズを128x128ピクセルに設定し、バックグラウンドを黒色にクリアします。

テキスト描画

let text_style = MonoTextStyleBuilder::new()
    .font(&FONT_6X10)
    .text_color(Rgb565::WHITE)
    .build();

Text::with_baseline(
    "hello world",
    Point::new(0, 0),
    text_style,
    Baseline::Top,
).draw(&mut display).unwrap();

テキストスタイルを設定し、"hello world"というテキストをディスプレイの左上隅に白色で描画します。

テキスト描画の実行

実行すると、ディスプレイに"hello world"と表示されました。

② ディスプレイに画像を描画

次に、ST7735ディスプレイに画像を描画します。

ひとまず上記の画像を表示します。

描画するにあたって、128x128のbitmap画像に変換する必要があり、画像のリサイズと変換には、以下のPhotoscape Xを使用しました。

サイズ変更した後、BMP形式で保存します。

プロジェクトにresourcesディレクトリを追加します。

project/
├── src/
│   └── main.rs
├── resources/
│   └── image.bmp
└── Cargo.toml

tinybmpを追加でインストールする。

$ cargo add tinybmp

以下のバージョンのtinybmpがCargo.tomlに追加されました。

tinybmp = "0.6.0"

以下、bitmap画像を描画するコードを示します。

use embedded_graphics::image::Image;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::{AnyIOPin, PinDriver};
use esp_idf_hal::prelude::Peripherals;
use esp_idf_hal::prelude::*;
use esp_idf_hal::spi;

use st7735_lcd;
use tinybmp::Bmp;

const BITMAP_DATA: &[u8] = include_bytes!("../resources/image.bmp");

fn main() -> anyhow::Result<()> {
    esp_idf_sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;

    // ピンの設定
    let spi = peripherals.spi2;
    let sclk = peripherals.pins.gpio18;
    let sdo = peripherals.pins.gpio23;
    let cs = peripherals.pins.gpio15;
    let rst = PinDriver::output(peripherals.pins.gpio4)?;
    let dc = PinDriver::output(peripherals.pins.gpio2)?;

    // SPIドライバーの設定
    let driver_config = Default::default();
    let spi_config = spi::SpiConfig::new().baudrate(20.MHz().into());
    let spi =
        spi::SpiDeviceDriver::new_single(spi, sclk, sdo, None::<AnyIOPin>, Some(cs), &driver_config, &spi_config)?;

    // ディスプレイの設定
    let rgb = false;
    let inverted = false;
    let width = 128;
    let height = 128;

    let mut delay = FreeRtos;
    let mut display = st7735_lcd::ST7735::new(spi, dc, rst, rgb, inverted, width, height);

    // ディスプレイの初期化
    display.init(&mut delay).unwrap();
    display.clear(Rgb565::BLACK).unwrap();
    display.set_offset(0, 0);

    // BMPデータの読み込みと描画
    let bmp = Bmp::from_slice(BITMAP_DATA).unwrap();
    let image = Image::new(&bmp, Point::zero());
    image.draw(&mut display).unwrap();

    println!("The screen should display the bitmap image.");
    loop {
        FreeRtos::delay_ms(1000);
    }
}

次に、bitmap画像を描画するために変更した箇所について解説します。

BMPデータの読み込み

const BITMAP_DATA: &[u8] = include_bytes!("../resources/image.bmp");

include_bytes!」マクロを使用して、コンパイル時にBMP画像ファイルをバイト配列として含めています。これにより、画像データがプログラム中に埋め込まれます。

BMPデータの解析と描画

// BMPデータの読み込みと描画
let bmp = Bmp::from_slice(BITMAP_DATA).unwrap();
let image = Image::new(&bmp, Point::zero());
image.draw(&mut display).unwrap();
  1. Bmp::from_slice(BITMAP_DATA)を使用して、バイナリデータからBMPオブジェクトを作成

  2. Image::new(&bmp, Point::zero())で、BMPデータからImageオブジェクトを作成

  3. image.draw(&mut display)で、作成したImageオブジェクトをディスプレイに描画

画像描画の実行

以下の通り、ディスプレイに画像が表示されました。

次の記事


いいなと思ったら応援しよう!