Raspberry Pi Pico 2でLVGL v9+PCシミュレータをセットアップ
以前液晶付きRaspberry Pi Pico 2互換ボードを購入したので、今回は組み込みでも使用できる軽量なUIライブラリであるLVGLのセットアップと使用方法の学習を行います。
LVGLとは

LVGL(Light and Versatile Graphics Library)は、組み込みでも使用できる軽量さが特徴のオープンソースのUIライブラリです。公式HPによれば"Smartphone-like UIs in kilobytes"、つまりkBオーダーでスマホのようなリッチなUIを作成できるらしいです。
前回記事ではWaveshare社が出しているLVGLのデモを動かしていたのですが、このデモがLVGL v8向けに書かれていて、しかも最新のv9では一部関数が変わり互換性がなくなっているようです。
というわけで、今回はLVGLの最新バージョンをRaspberry Pi Picoで使用する方法を、簡単な温度センサーデモの作成を通して確認します。さらに、今後の開発のためにPC上でLVGLの描画を確認できるPCシミュレータ環境も構築します。
なお、今回作成したコードはGitHubで公開しております。
開発環境のセットアップ
まずはRaspberry Pi Picoプロジェクトを作成し、そこにLVGLのGitHubからライブラリをクローン…してもいいんですが、せっかくなのでCMakeのFetchContentでビルド時に自動でクローンするようにします。最上位のCMakeLists.txtに以下のように設定しました。
include(FetchContent)
FetchContent_Declare(
lvgl
GIT_REPOSITORY https://github.com/lvgl/lvgl.git
GIT_TAG v9.3.0
)
FetchContent_MakeAvailable(lvgl)
target_compile_options(lvgl PRIVATE -w)LVGLライブラリに関するコンパイル警告が出ても「俺は関係ない」状態なので、全警告をオフにしておきます。また、FetchContentはCMake 3.11から追加されたのでcmake_minimum_requiredがVersion 3.11以降になっていることを確認しておきます。
次に、lvglの設定を記載するヘッダーファイルである"lv_conf.h"を作成します。これは、クローンしたディレクトリ内に"lv_conf_template.h"があるので、これをlvglフォルダがあるディレクトリにコピーしてファイル名を"lvconf.h"にリネームします。
ファイルの冒頭に以下のような記載があるので、#if 1に変更するとファイルが有効化されます。
lv_conf.h 6-15
/*
* Copy this file as `lv_conf.h`
* 1. simply next to `lvgl` folder
* 2. or to any other place and
* - define `LV_CONF_INCLUDE_SIMPLE`;
* - add the path as an include path.
*/
/* clang-format off */
#if 1 /* Set this to "1" to enable content */ひな形を埋めるだけ
次にやることはLVGL公式ドキュメントの実装例をコピペし、「自分で実装してね」と書かれている箇所を埋めることです。
作成する必要があるのは以下の3つ。
- ディスプレイ初期化関数
your_driver_init - 起動時からの絶対時間をミリ秒で返す関数
my_get_millis - ディスプレイに描画データを送る関数
my_flush_cb
ディスプレイの初期化関数はWaveshare社が提供しているデモファイルから単にコピーすれば完了。
次に、起動後の絶対時間を返す関数は、Pico SDKのhardware_timer内にあるget_absolute_time()関数で時間を取得し、それをto_ms_since_boot()関数でミリ秒に変換することで取得しました。もともとのデモファイルではlv_tick_inc関数を定期的に呼び出していましたが、このように実装することでLVGLライブラリ側が管理してくれるようになります。
最後に、ディスプレイに描画データを送る関数については、おおむねデモコードそのままではあるんですが、LVGL v8との違いとして
- 一部変数の型が違う(
lv_disp_draw_buf_t型がlv_display_t型に、lv_color_t型がなくなっている) - SPIのエンディアン入れ替え方法が違う(
#define LV_COLOR_16_SWAP 1の代わりにエンディアン入れ替え関数lv_draw_sw_rgb565_swapになっている)
など、割といろいろ変わっていたのでやや面倒でした。というか、この変更点に関する公式からの説明資料は見つけられなかったですし、LV_COLOR_16_SWAPフラグはv9で削除されたかと思いきや再度追加されたようなので、正直LVGLライブラリそのものの使い勝手は微妙な気がします。
そして、LVGLのページを見ていたら、このマイコンボードに載っているドライバICであるST7735向けドライバがLVGLライブラリに内蔵されているようなので、もっと簡単に実装できた気がします。気が向いたらこちらでの実装に切り替えたい…
追記
その後、結局ST7735向けドライバによる実装に変更しました。さらに、
- サンプルコードについていたドライバ周りをすべて自前のものに変更し、ついでにC++化
lv_draw_sw_rgb565_swapの使用をやめ、DMAを16-bitモードで使用することで効率的に転送- PCシミュレータ環境の実装(後述)
- その他リファクタリング
などの大幅な変更を行いました。
デモンストレーション
以上の関数をライブラリとしてまとめ、mainから呼び出す形としました。
そして、LVGLのデモとしてRP2350内蔵の温度センサの値を読み取り、それを目盛付きバーとラベルで表示するプログラムを作成しました。

LVGLのドキュメントにあるデモをコピペしてちょっと変更するだけでサクッとそれっぽいのが作れたのですが、内蔵の温度センサの値は室温とはかけ離れているのでシステム監視以外には使えないですね。
PCシミュレータ環境について
LVGLによるGUIを開発する際、毎回Raspberry Piに転送するのも面倒なので、lv_port_pc_vscodeを用いたPCシミュレータ環境も同梱しました。このシミュレータ環境により、次のようにPC上でLVGLの描画を確認できます。

デバイス依存部分については、動的に切り替えるわけではないので仮想関数ではなくCMakeListsによるリンク時切り替えによって実装をダミーに切り替えています。最上位のCMakeListsでPC_SIMULATORフラグを有効化することでPC向けシミュレータビルドになります。

なんかもっとうまくできる気はしますが、私がAIと対話しながら導き出した答えがこれでした。というか、もはやGitHub Copilotがいないとコーディングなんてできない(個人で契約してしまった)。