2024年1月14日日曜日

LovyanGFXでArduino環境向けに作った画面表示プログラムの動作確認をPC上で実施


背景

LovyanGFXとはlovyanさんが作っているLCD制御プログラムです。
それにはplatformioのnative環境で動かしてpc上で動作確認する仕組みがあるため使ってみました。


いくつか詰まりどころがあったので、備忘録として記事を残します。

何が嬉しいか?:  main.cpp書き換え後にビルドして動作するまでの時間がnativeの方が短い

ライブラリがビルドされた状態でmainだけ書き換えて再実行する場合、実行されるまでの時間がnativeの方が短いため試行錯誤の速度を上げれます。
なお、Arduinoでもnativeでも初回はライブラリを含んだビルドが走るので自分の環境だとどちらも1分前後かかります。

Arduino環境だと約23秒(ビルド約8秒、書き込み約15秒)
native環境だと約8秒(ビルド約8秒、書き込み1秒未満)

デバッガも使えるようですが、この記事では表示内容の確認だけを解説します。

参考にした公式の情報

LovyanGFX公式のSDLを利用するサンプルプロジェクトです。
https://github.com/lovyan03/LovyanGFX/tree/master/examples_for_PC/PlatformIO_SDL

記事の末尾でも再度共有します。

使ったもの

  • ubuntuをインストールしたPC
    ubuntu22.04をインストールしたものを利用しました。
  • LovyanGFXやM5GFXを利用するplatformioのArduino向けプロジェクト
    今回はAtomDisplayとfull HDモニタを組み合わせて動かすプログラムを使いました。
  • 上記プログラム動作確認用実機
    AtomDisplay13.3インチのfull HDモニタを使いました。
    (モニタによってはAtomDisplayのfull HDモードのリフレッシュレートが不十分で動かない場合があります。)

ubuntuで動かすためのライブラリをインストール

サンプルプログラムのREADMEで解説されている通り、platformioのnative環境ビルド用のライブラリと、LovyanGFXを利用したPC上での画像描画ライブラリをインストールします。
sudo apt update
sudo apt install -y build-essential libsdl2 libsdl2-dev

macやwindowsを利用される方はREADMEのそれ向けの箇所を参照してください。
LovyanGFX/examples_for_PC/PlatformIO_SDL

プロジェクト全体

全体を共有してから要所を解説します。
このプロジェクトはgithubに上げています。
https://github.com/asukiaaa/pio-lovyangfx-sdl-on-pc-practice

platformio.ini
[platformio]
default_envs = native
; default_envs = m5stack-atom

[env:native]
platform = native
lib_deps =
LovyanGFX
ArduinoFake
string_asukiaaa
lib_compat_mode = off
build_flags =
-DUSE_NATIVE
-O0 -xc++ -std=c++14
-lSDL2
-I"/usr/local/include/SDL2" ; for intel mac homebrew SDL2
-L"/usr/local/lib" ; for intel mac homebrew SDL2
-I"${sysenv.HOMEBREW_PREFIX}/include/SDL2" ; for arm mac homebrew SDL2
-L"${sysenv.HOMEBREW_PREFIX}/lib" ; for arm mac homebrew SDL2

[env:m5stack-atom]
platform = espressif32
board = m5stack-atom
framework = arduino
monitor_speed = 115200
lib_deps =
M5GFX
string_asukiaaa
board_build.partitions = no_ota.csv

src/main.cpp
#ifdef USE_NATIVE
#define LGFX_AUTODETECT
#include <LovyanGFX.h>

#include <LGFX_AUTODETECT.hpp>
#else
#include <M5AtomDisplay.h>
#endif

#include <Arduino.h>
#include <string_asukiaaa.h>
#include <unistd.h>

#include <iostream>

#if defined(USE_NATIVE)
static LGFX lcd(1080, 1920, 2);
#else
M5AtomDisplay lcd(1920, 1080);
#endif

void setup() {
lcd.init();
#ifndef USE_NATIVE
lcd.setRotation(0);
#endif
lcd.setTextSize(8);
lcd.setTextColor(TFT_WHITE, TFT_BLACK);
}

void loop() {
#ifdef USE_NATIVE
auto seconds = time(NULL);
#else
auto seconds = millis() / 1000;
#endif
lcd.setCursor(0, 0);
lcd.println("top left");
lcd.print("string test ");
lcd.println(string_asukiaaa::padStart("abcd", 10, '-').c_str());
lcd.println(("at " + String(seconds)).c_str());
lcd.drawRightString("bottom right", lcd.width(),
lcd.height() - lcd.fontHeight());
#ifdef USE_NATIVE
sleep(1);
#else
delay(1000);
#endif
}

#if defined(USE_NATIVE)
int user_func(bool* running) {
setup();
do {
loop();
} while (*running);
return 0;
}

int main(int, char**) { return lgfx::Panel_sdl::main(user_func); }
#endif

上記のプログラムを実機で動かした様子はこちらです。


native環境で動かした様子はこちらです。
何故か横広全画面で描画されてしまうので、全画面を解いて大きさの調整が必要です。


全画面を解いて大きさを調整した後の様子です。


pc上での描画ライブラリ SDL2を有効化

関連ライブラリの解説でインストールしたSDL2をplatformio.iniのbuild flagsとして-lで利用を宣言します。
macの場合はパスの指定が必要なようですが、ubuntuの場合は-lオプションだけでSDL2が利用可能になります。
[env:native]
build_flags =
-lSDL2

lcdの定義をLovyanGFX向けに置き換え

AtomDisplayはLGFX_AUTODETECTで判別できないようなので、USE_NATIVEというnative環境のときだけ有効になるマクロを定義し、ライブラリの読み込みや変数の定義を切り替えています。
[env:native]
lib_deps =
LovyanGFX
build_flags =
-DUSE_NATIVE

[env:m5stack-atom]
lib_deps =
M5GFX

#ifdef USE_NATIVE
#define LGFX_AUTODETECT
#include <LovyanGFX.h>

#include <LGFX_AUTODETECT.hpp>
#else
#include <M5AtomDisplay.h>
#endif

#if defined(USE_NATIVE)
static LGFX lcd(1080, 1920, 2);
#else
M5AtomDisplay lcd(1920, 1080);
#endif

native環境LovyanGFXx向けのmain関数を準備

native環境はsetupとloop関数ではなくPC上で動くcppプログラムと同様にmain関数で動きます。
関数をLovyanGFXのPanel_sdl::mainに渡すと画面描画処理を継続してくれる仕組みになっているので、必要な処理で関数を組み立ててmain関数内で渡します。
#if defined(USE_NATIVE)
int user_func(bool* running) {
setup();
do {
loop();
} while (*running);
return 0;
}

int main(int, char**) { return lgfx::Panel_sdl::main(user_func); }
#endif

今回のプロジェクトではsetupもloopもnativeでほぼ動く内容なのでそれらの関数を使っていますが、arduino向けに作り込んでいてそのままの利用が困難な場合はsetupとloopは使わず画面の描画関係の処理だけで関数を組み立ててPanel_sdl::mainに渡すのが良いです。

Stringクラスなどを利用したい場合はArduinoFakeを関連ライブラリに設定

lib_depsにArduinoFakeを設定するとStringやWireを呼び出せる状態になります。
[env:native]
platform = native
lib_deps =
ArduinoFake

注意点としてLovyanGFXをincludeした後にArduino.hのincludeが必要です。
Arduino.hを先にincludeするとLovyanGFXが大量のエラーを出してビルドできません。
#ifdef USE_NATIVE
#include <LovyanGFX.h>
#endif

#include <Arduino.h>

LovyanGFXはStringを利用しないnative状態で読み込まれるため、Stringを利用している呼び出しは「.c_str()」を付けてcharの配列を引数として渡す必要があります。
  lcd.println(("at " + String(seconds)).c_str());

native環境向けでないライブラリを使う場合は、lib_compat_modeを無効化

ArduinoFakeを利用することでArduinoの基本機能は呼び出せる状態にできましたが、platformio向けの設定ファイルlibraries.jsonをライブラリに含みつつnative環境に対応していると明記しているライブラリでないとlib_depsで指定してもファイルが見つからずビルドが失敗します。

その場合はlib_compat_modeを無効化すると、nativeに対応しているか否かに関わらずライブラリを取り込んでビルドしてくれます。
[env:native]
lib_deps =
ArduinoFake
string_asukiaaa
lib_compat_mode = off

上記の設定で取り込んでいるstring_asukiaaaというライブラリは記事を書いている時点で最新版の1.0.4はlibraries.jsonが無いためplatformioがnative環境に対応していないと判断しますが、lib_compat_modeを無効化することで使えました。

参考
Unrecognized library in native environment
lib_compat_mode

実機で画面を回転する場合、nativeは回転後の画面の大きさを使う

native環境でsetRotationを1以外にすると表示が回転して見づらくなるので、回転を含めてnativeと実機で切り替えるのが自分は都合が良かったです。
#if defined(USE_NATIVE)
static LGFX lcd(1080, 1920, 2);
#else
M5AtomDisplay lcd(1920, 1080);
#endif
  lcd.init();
#ifndef USE_NATIVE
lcd.setRotation(0);
#endif

色はTFT_*で指定

M5GFXではBLACKやWHITEなどで色を指定できましたが、LovyanGFXでは名前が競合するのを避けるためかTFT_が頭についたTFT_BLACKやTFT_WHITEで定義されています。
TFT_付きのマクロはM5FGXでも定義されているので、実機でもnative環境でも動かすコードを書く時はTFT_付きを使うのが良いです。
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);

native環境ではmillisが使えない

ArduinoFakeではmillisが定義されていないためビルドエラーになります。
経過秒数を扱いたいならtime関数が使えます。
#ifdef USE_NATIVE
auto seconds = time(NULL);
#else
auto seconds = millis() / 1000;
#endif

native環境ではdelayが使えない

マクロでコメントアウトするか、unistdのsleepを使います。
#include <unistd.h>
#ifdef USE_NATIVE
sleep(1);
#else
delay(1000);
#endif

気になること: 表示画面の領域を超える画面設定は、縦横比が崩れた状態で全画面表示される

1080x1920の画像を表示しているはずが、横長の全画面表示になってしまいます。


LovyanGFXのscaleは整数なので1より小さくできないが、コードを直接書き換えれば最初から小さく表示できる

下記のように書き換えれば指定した大きさの1/4で表示されます。

LovyanGFX/src/lgfx/v1/platforms/sdl/Panel_sdl.cpp の Panel_sdl::sdl_create
    // m->window = SDL_CreateWindow(_window_title,
// SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
// _cfg.panel_width * m->scaling_x, _cfg.panel_height * m->scaling_y, flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
m->window = SDL_CreateWindow(_window_title,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
_cfg.panel_width * 0.25, _cfg.panel_height * 0.25, flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/


おわり

nativeで動作確認可能な環境を作れたため、表示内容の調整やプレビューの取得が容易になって嬉しいです。

参考

LovyanGFX公式のSDLを利用するサンプルプロジェクトです。
https://github.com/lovyan03/LovyanGFX/tree/master/examples_for_PC/PlatformIO_SDL

この記事で作成したプロジェクトです。
https://github.com/asukiaaa/pio-lovyangfx-sdl-on-pc-practice

native環境でArduinoの関数(StringやWire)を利用するためのライブラリです。
ArduinoFake

native環境でArduino向けのライブラリを取り込むためにはlib_compat_modeをoffにすれば良いと把握したフォーラムと、それの公式解説ページです。
Unrecognized library in native environment
lib_compat_mode

0 件のコメント :