
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();
Bmp::from_slice(BITMAP_DATA)を使用して、バイナリデータからBMPオブジェクトを作成
Image::new(&bmp, Point::zero())で、BMPデータからImageオブジェクトを作成
image.draw(&mut display)で、作成したImageオブジェクトをディスプレイに描画
画像描画の実行
以下の通り、ディスプレイに画像が表示されました。
