前回の記事で、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ディスプレイを接続する。
ST7735 LCD ピン | ESP32 GPIO | 機能 | 説明 |
---|---|---|---|
LED | 3.3V | バックライト | LCDのバックライト制御(通常は3.3Vに接続) |
SCK | GPIO18 | SCLK | SPIクロック信号 |
SDA | GPIO23 | MOSI | マスターからスレーブへのデータ送信 |
A0 | GPIO2 | DC | データ/コマンド選択 |
RESET | GPIO4 | RST | ディスプレイのリセット |
CS | GPIO15 | CS | チップセレクト信号 |
GND | GND | グラウンド | 電源のグラウンド |
VCC | 3.3V | 電源 | 3.3Vの電源供給 |
今回、ディスプレイは常時点灯させるため、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
オブジェクトをディスプレイに描画
画像描画の実行
以下の通り、ディスプレイに画像が表示された。
次の記事はこちら。