Zephyr RTOS 〜 LVGL 描画ライブラリによるGUI開発 〜

# 1. はじめに

この記事では、飛び込みで [Open Source Conference 2025 Tokyo/Spring](https://event.ospn.jp/osc2025-spring/) に出展したサンプルを紹介しつつ、
LVGL による描画ライブラリに触れていきたいと思います。
(本当は、Zephyrが得意としているIoTを全面に押し出した内容と考えていたのですが間に合わず…そのネタもいずれは)

# 2. 実機とリポジトリ

実機は [STM32H747I Discovery](https://docs.zephyrproject.org/latest/boards/st/stm32h747i_disco/doc/index.html) を用いました。
リポジトリはいつもの Playbook で、sample/osc2025_tokyo としてブランチを切っています。

https://github.com/Corgeek/ZephyrOpsPlaybook/tree/sample/osc2025_tokyo

# 3. 使い方

まずは動かすまでの手順を示します(以下、コピペでOK)。

```bash:作業環境の構築(Ubuntu)
mkdir -p ~/osc2025_tokyo && cd ~/osc2025_tokyo
python3 -m venv .venv
source .venv/bin/activate
pip install west

git clone https://github.com/Corgeek/ZephyrOpsPlaybook.git -b sample/osc2025_tokyo playbook

west init -l playbook
west update
pip install -r zephyr/scripts/requirements.txt
west sdk install
```

次に、playbook で用意したスクリプトを使い、ターゲットの指定や環境依存パスを解消する設定群である `scripts/west_env.bat` を生成します。

```bash:ビルド環境の準備
cd playbook
./scripts/setup.sh
```

今回は LCD を用いるのですが、このボードでは shield 指定する必要があります。
通常、`west build` の際に、`--shield hogehoge` を追加指定する必要があるのですが、
playbook のスクリプトは `--shield` オプションをまだサポートしていないため `scripts/west_env.bat` 内の `BOARD_TYPE` を下記のように手作業で編集してください。

```
BOARD_TYPE="stm32h747i_disco/stm32h747xx/m7 --shield st_b_lcd40_dsi1_mb1166_a09"
```

(※ ボードの生産が古いと、`--shield st_b_lcd40_dsi1_mb1166` の可能性があります)

以上で、ビルド環境は整いました。
以下でビルド＆書き込みを行います。

```bash:ビルド＆書き込み
./scripts/build.bat /r && ./scripts/build.bat /f
```

![osc2025tokyo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38092/b80defd5-2e7f-453f-ac3f-808c85c0ed5e.png)

# 4. 解説

続いて、実際の処理や構成について解説していきます。
大まかな流れとしては以下の通りです。

1. sample/osc2025_tokyo ブランチから派生
1. bitmap ファイルを配列データに変換
1. 配列データを描画ライブラリの LVGL 用に手直し
1. CMakeLists.txt にファイルを登録
1. main 関数で描画処理を実装

また、表示しているのは1枚の大きめなBMP画像のみ(手抜き)です。
画像データも BMP を C言語の配列形式に変換し、それを ROM の const 領域に格納する(横着な)運用を取っています。

ちなみに、本機はSDカードスロットも標準装備しており、Zephyr 側も SDカードのドライバと FAT 形式のファイルフォーマットも標準サポートしているため、
これらを利用することである程度容量を気にせず運用することも可能です。

## 4.1. sample/osc2025_tokyo ブランチから派生

ベースは dev/minimal ブランチから派生しており、prj.conf で CONFIG_LVGL を有効化などの初設定を加えています。
sample/osc2025_tokyo をそのまま利用して差分を確認するか、これをベースに新しいブランチを作成するなどしてください。

```bash: sample/osc2025_tokyo ブランチをベースに別名で新しいブランチを作成
git checkout origin/sample/osc2025_tokyo -b sample/lvgl
```

## 4.2. bitmap ファイルを配列データに変換

以下のコマンドで bitmap データを変換できます。実際に使用した画像は rsc/osc_tokyo_565.bmp に置いてあり、容量を減らすため RGB565 の 16bit color の BMP ファイルにしています。

```
xxd -i rsc/osc_tokyo_565.bmp > src/osc_tokyo_bmp.c
```

## 4.3. 配列データを描画ライブラリの LVGL 用に手直し

xxd で出力されるデータは、bmp のヘッダー部分も含んだ data 領域用の配列形式として出力されるため、ヘッダー部分を削り、さらに const 領域に収めるように以下のように手直ししています。

```c:手直し前
unsigned char rsc_osc_tokyo_565_bmp[] = {
  0x42, 0x4d, 0x42, 0xc4, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00,
  0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x90, 0x01,
  0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc4,
  0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xe0, 0x07,
  0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

```

```c:手直し後
static const uint8_t s_osc_tokyo_bmp[] = {
/* 0x42, 0x4d, 0x42, 0xc4, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00,
  0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x90, 0x01,
  0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc4,
  0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xe0, 0x07,
  0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

```

また、今回利用する描画ライブラリがこのBMPのデータにアクセスできるようにするため、以下のように追加しています。

```c: LVGL 用の設定を追加

#include <zephyr/kernel.h>
#include <lvgl_zephyr.h>
#include <lvgl.h>

const lv_image_dsc_t g_img_osc_tokyo = {
  .header.magic = LV_IMAGE_HEADER_MAGIC,
  .header.cf = LV_COLOR_FORMAT_RGB565,
  .header.flags = 0,
  .header.w = 800,
  .header.h = 400,
  .header.stride = 0,
  .data_size = sizeof(s_osc_tokyo_bmp),
  .data = s_osc_tokyo_bmp,
};
```

## 4.4. CMakeLists.txt にファイルを登録

手直しが済んだあと、このファイルを CMakeLists.txt にコンパイル対象として登録

```cmake:playbook/src/CMakeLists.txt
target_sources(app
PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}/main.c
    ${CMAKE_CURRENT_LIST_DIR}/osc_tokyo_bmp.c
)
```

## 4.5. main 関数で描画処理を実装

あとは追加した情報を lv_image_set_src() を利用して img にセットして、その描画箇所を指定するだけです。

```c: playbook/src/main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/display.h>
#include <lvgl.h>
#include "boards/unique.h"

extern const lv_image_dsc_t g_img_osc_tokyo;

int main(void)
{
	lv_obj_t *img;

	img = lv_image_create(lv_screen_active());
	lv_image_set_src(img, &g_img_osc_tokyo);
	lv_obj_center(img);

	while (true) {
		k_msleep(MIN(lv_timer_handler(), INT32_MAX));
	}

	return 0;
}
```

# 5. あとがき

今回は、秘密結社オープンフォースの他の活動や、プライベートな環境変化などが重なり、
労力に対してのコスパ優先を選び、描画ライブラリを使った展示としてみました。
LVGL 自体は今回初めての利用だったのですが、サンプルや各種機能を見る限り、画像のアニメーション描画や、フォント指定ができる文字の描画だけでなく、よくある button や spinbox, slideer なども標準装備しており、結構いろんな事ができそうです。

参考として、Zephyr で動作している LVGL の sample を動かす方法は以下の通り。

```bash:LVGL のサンプルのビルド＆書き込み
cd ~/osc2025_tokyo
source .venv/bin/activate
west build -b stm32h747i_disco/stm32h747xx/m7 --shield st_b_lcd40_dsi1_mb1166_a09 zephyr/samples/modules/lvgl/demos/
west flash
```

また、LVGL の機能の構成や利用の仕方、用語など、Qtに近しいものを感じました。
(マルチプラットフォームを想定すると同じ設計思想に落ち着くのかな？)

```bash:lvgl のディレクトリ構成
user@host:~/osc2025_tokyo$ ls modules/lib/gui/lvgl/src/layouts/
flex  grid  lv_layout.c  lv_layout.h  lv_layout_private.h

user@host:~/osc2025_tokyo$ $ ls modules/lib/gui/lvgl/src/widgets/
animimage     calendar  image        line    objx_templ  span     tabview
arc           canvas    imagebutton  list    property    spinbox  textarea
bar           chart     keyboard     lottie  roller      spinner  tileview
button        checkbox  label        menu    scale       switch   win
buttonmatrix  dropdown  led          msgbox  slider      table
```

2024年末に発表された下記件も個人的にかなり期待している所でして、まだまだ Zephyr の世界が便利に広がりそうです…！

https://www.qt.io/ja-jp/blog/qt-group-joins-zephyr-project

stm32h747i_disco 自体は 15,000〜20,000 円程度と少し高めですが、ST-LinkV3 も内蔵していて、ディスプレイや ethernet を利用した https client/server 機能、SD カードスロットのドライバや FAT ファイルシステム、Arduino 互換 pin なども Zephyr で動作することを確認しており、一式が揃っているという点では非常に便利に活用できそうです。

また、stm32h747i_disco/stm32h747xx/m4 もビルド＆書き込み確認できており、SMPは非対応なもののできることはかなり広そうです。
(※ ただし CortexM4 側は openOCD 非対応。 STM32Cube で m4 を使った単純動作のセンサー制御を開発して、m7側は Zephyr を使ってマルチメディアやGUIなどの分担が良さそう？)

https://docs.zephyrproject.org/latest/boards/st/stm32h747i_disco/doc/index.html#supported-features

まだまだプライベートなイベントが目白押しですが、少しずつでも継続してアウトプットを出せるようにしていきたいと思っています。