【LvGL】Arduino(Wio Terminal)での使い方を抽象化しようとした
を使いやすくまとめたかった
個人的に思うオブジェクト指向の利点は、「まとまる」ことと信じている。件のlv_obj_t
で統一され、一見すると
オブジェクト指向の代表的な概念であるクラスを使うならば、ボタンのための関数はボタンしか呼び出せないように、ある種の実行権限のようなものが効く。このことが分かりやすさにつながるが、
実際のところ、高速な言語として知られる
抽象部
抽象と雖も、TFT_eSPI.h
と併用する場合しか知らないため、あらゆる
ここでは、画面や入力の「設定」を実装した。いくら表面上の「内容」、即ち画面上に配置するものの種類や数が変われど、これらの設定は大きく変わらないように見える。対して画面の内容に関わる部分は大きく変わるため、virtual
を付して改変(overload
)を許すか、そもそも記述しなかった。
なお純粋仮想関数__mFContentsSetup()
があるため、このクラスLAAL
はインスタンス化できない。
#ifndef __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__
#define __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
template<class T>
class LAAL {
private:
/* delete copy constructor */
LAAL(const LAAL&) = delete;
/* delete copy operator */
LAAL& operator=(const LAAL&) = delete;
protected:
/* singleton */
static T* __smpLaalSingleton;
/* TFT LCD */
static TFT_eSPI* __mpTft;
/* tick (1~10) */
uint32_t __mu32TickPeriod;
/* ディスプレイ */
/* display buffer */
lv_disp_buf_t __smDisplayBuffer;
/* main color buffer */
lv_color_t __smColorBufferMain[LV_HOR_RES_MAX * 10];
/* sub color buffer */
lv_color_t __smColorBufferSub[LV_HOR_RES_MAX * 10];
/* display driver */
lv_disp_drv_t __mDisplayDriver;
/* display */
lv_disp_t* __mpDisplay;
/* 画面初期化 */
static void __mFDisplayFlush(
lv_disp_drv_t* pDisplayDriver,
const lv_area_t* pArea,
lv_color_t* pColor
);
/* 画面設定 */
virtual void __mFDisplaySetup();
/* 入力デバイス(encode) */
/* input device driver */
lv_indev_drv_t __mInputDeviceDriverEncoder;
/* input device */
lv_indev_t* __mpInputDeviceEncoder;
/* Reading encoder callback */
static bool __mFbReadEncoder(
lv_indev_drv_t* pInputDeviceDriver,
lv_indev_data_t* pInputDevicesData
);
/* デバイス設定 */
virtual void __mFDeviceSetup();
/* 画面要素 */
/* widget */
/*
example:
lv_obj_t* __mpObject;
void __mFObjectEventhandler(
lv_obj_t* pObject,
lv_event_t event
) = 0;
*/
/* group */
/*
example:
lv_group_t* __mpGroup;
void __mFGroupSetup();
*/
/* 画面構成 */
virtual void __mFContentsSetup() = 0;
/* constructor */
void __mFNew();
LAAL() = default;
/* destructor */
virtual ~LAAL() = default;
public:
/* 設定 */
void mFSetup();
/* 画面実行 */
void mFOperateGraphic();
};
#include "LvglArduinoAbstractLayer.ipp"
#endif // __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__
template<class T>
T* LAAL<T>::__smpLaalSingleton = nullptr;
template<class T>
TFT_eSPI* LAAL<T>::__mpTft = nullptr;
template<class T>
void LAAL<T>::__mFNew() {
/* lvglの初期化 */
lv_init();
/* TFTの初期設定 */
(this -> __mpTft) -> begin();
(this -> __mpTft) -> setRotation(3);
}
template<class T>
void LAAL<T>::mFSetup() {
/* 画面設定 */
(this -> __mFDisplaySetup)();
/* 入力設定 */
(this -> __mFDeviceSetup)();
/* 画面作成 */
(this -> __mFContentsSetup)();
}
template<class T>
void LAAL<T>::__mFDisplayFlush(
lv_disp_drv_t* pDisplayDriver,
const lv_area_t* pArea,
lv_color_t* pColor
) {
const uint32_t w = pArea -> x2 - pArea -> x1 + 1;
const uint32_t h = pArea -> y2 - pArea -> y1 + 1;
LAAL<T>::__mpTft -> startWrite();
LAAL<T>::__mpTft -> setAddrWindow(pArea -> x1, pArea -> y1, w, h);
LAAL<T>::__mpTft -> pushColors(&pColor -> full, w * h, true);
LAAL<T>::__mpTft -> endWrite();
lv_disp_flush_ready(pDisplayDriver);
}
template<class T>
void LAAL<T>::mFOperateGraphic() {
while (1) {
/* operate tasks */
this -> __mu32TickPeriod = lv_task_handler();
/* delay */
delay(this -> __mu32TickPeriod);
/* tell lvgl system elapsed time */
lv_tick_inc(this -> __mu32TickPeriod);
}
}
template<class T>
void LAAL<T>::__mFDisplaySetup() {
/* 画面表示用bufferそのものの初期化 */
lv_disp_buf_init(
&(this -> __smDisplayBuffer),
this -> __smColorBufferMain,
this -> __smColorBufferSub,
LV_HOR_RES_MAX * 10
);
/* 画面制御設定 */
lv_disp_drv_init(&(this -> __mDisplayDriver));
(this -> __mDisplayDriver).ver_res = LV_VER_RES_MAX;
(this -> __mDisplayDriver).flush_cb = this -> __mFDisplayFlush;
(this -> __mDisplayDriver).buffer = &(this -> __smDisplayBuffer);
this -> __mpDisplay = lv_disp_drv_register(&(this -> __mDisplayDriver));
}
template<class T>
bool LAAL<T>::__mFbReadEncoder(
lv_indev_drv_t* pInputDeviceDriver,
lv_indev_data_t* pInputDevicesData
) {
(void) pInputDeviceDriver;
(void) pInputDevicesData;
return false;
}
template<class T>
void LAAL<T>::__mFDeviceSetup() {
/* 入力の設定 */
lv_indev_drv_init(&(this -> __mInputDeviceDriverEncoder));
(this -> __mInputDeviceDriverEncoder).type = LV_INDEV_TYPE_ENCODER;
(this -> __mInputDeviceDriverEncoder).read_cb = this -> __mFbReadEncoder;
this -> __mpInputDeviceEncoder = lv_indev_drv_register(&(this -> __mInputDeviceDriverEncoder));
}
アプリケーション部
このクラスは、ボタンを表示するサンプルプログラムを実装する。書き方を変えたばかりで内容は前記事の儘である。
#ifndef __SIMPLE_BUTTON_APP_HPP__
#define __SIMPLE_BUTTON_APP_HPP__
#include "LvglArduinoAbstractLayer.hpp"
class App final: public LAAL<App> {
private:
/* 画面設定 */
void __mFDisplaySetup();
/* Reading encoder callback */
static bool __mFbReadEncoder(
lv_indev_drv_t* pInputDeviceDriver,
lv_indev_data_t* pInputDevicesData
);
/* デバイス設定 */
void __mFDeviceSetup();
/* button1 */
lv_obj_t* __mpObjectButton1;
/* label1 */
lv_obj_t* __mpObjectLabel1;
/* button2 */
lv_obj_t* __mpObjectButton2;
/* label2 */
lv_obj_t* __mpObjectLabel2;
/* button配置 */
void __mFButtonsSetup();
/* group */
lv_group_t* __mpGroup;
/* group設定 */
void __mFGroupSetup();
/* 画面構成 */
void __mFContentsSetup();
/* constructor */
App();
/* destructor */
~App();
public:
/* 初期化 */
static App* mFpLaalInitialize(TFT_eSPI* pTft);
};
#endif // __SIMPLE_BUTTON_APP_HPP__
#include "SimpleButtonApp.hpp"
App* App::mFpLaalInitialize(TFT_eSPI* pTft) {
/* 未定ならば初期化 */
if (LAAL<App>::__smpLaalSingleton == nullptr) {
/* static member variableの初期化 */
LAAL<App>::__mpTft = pTft;
/* 子クラスの初期化 */
LAAL<App>::__smpLaalSingleton = new App();
}
/* 既初期化の場合でも既存インスタンスへのポインターが返却される */
return LAAL<App>::__smpLaalSingleton;
}
App::App() {
LAAL<App>::__mFNew();
}
App::~App() {}
void App::__mFDisplaySetup() {
LAAL<App>::__mFDisplaySetup();
}
bool App::__mFbReadEncoder(
lv_indev_drv_t* pInputDeviceDriver,
lv_indev_data_t* pInputDevicesData
) {
(void) pInputDeviceDriver;
if (digitalRead(WIO_5S_PRESS) == LOW) {
pInputDevicesData -> state = LV_INDEV_STATE_PR;
} else {
pInputDevicesData -> state = LV_INDEV_STATE_REL;
}
/*
indev_encoder_proc()に基づく左右の定義
*/
if (digitalRead(WIO_5S_LEFT) == LOW) {
pInputDevicesData -> enc_diff = -1;
} else if (digitalRead(WIO_5S_RIGHT) == LOW) {
pInputDevicesData -> enc_diff = 1;
} else {
pInputDevicesData -> enc_diff = 0;
}
/* no data buffered */
return false;
}
void App::__mFDeviceSetup() {
/* 入力の設定 */
lv_indev_drv_init(&(LAAL<App>::__mInputDeviceDriverEncoder));
(LAAL<App>::__mInputDeviceDriverEncoder).type = LV_INDEV_TYPE_ENCODER;
(LAAL<App>::__mInputDeviceDriverEncoder).read_cb = App::__mFbReadEncoder;
LAAL<App>::__mpInputDeviceEncoder = lv_indev_drv_register(&(LAAL<App>::__mInputDeviceDriverEncoder));
/* enable 5way switch */
pinMode(WIO_5S_UP, INPUT);
pinMode(WIO_5S_DOWN, INPUT);
pinMode(WIO_5S_LEFT, INPUT);
pinMode(WIO_5S_RIGHT, INPUT);
pinMode(WIO_5S_PRESS, INPUT);
}
void App::__mFButtonsSetup() {
this -> __mpObjectButton1 = lv_btn_create(lv_scr_act(), NULL);
// lv_obj_set_event_cb(this -> __mpObjectButton1, event_handler);
lv_obj_align(this -> __mpObjectButton1, NULL, LV_ALIGN_CENTER, 0, -40);
this -> __mpObjectLabel1 = lv_label_create(this -> __mpObjectButton1, NULL);
lv_label_set_text(this -> __mpObjectLabel1, "Button");
this -> __mpObjectButton2 = lv_btn_create(lv_scr_act(), NULL);
// lv_obj_set_event_cb(this -> __mpObjectButton2, event_handler);
lv_obj_align(this -> __mpObjectButton2, NULL, LV_ALIGN_CENTER, 0, 40);
lv_btn_set_checkable(this -> __mpObjectButton2, true);
lv_btn_toggle(this -> __mpObjectButton2);
lv_btn_set_fit2(this -> __mpObjectButton2, LV_FIT_NONE, LV_FIT_TIGHT);
this -> __mpObjectLabel2 = lv_label_create(this -> __mpObjectButton2, NULL);
lv_label_set_text(this -> __mpObjectLabel2, "Toggled");
}
void App::__mFGroupSetup() {
this -> __mpGroup = lv_group_create();
lv_indev_set_group(LAAL<App>::__mpInputDeviceEncoder, this -> __mpGroup);
lv_group_add_obj(this -> __mpGroup, this -> __mpObjectButton1);
lv_group_add_obj(this -> __mpGroup, this -> __mpObjectButton2);
}
void App::__mFContentsSetup() {
(this -> __mFButtonsSetup)();
(this -> __mFGroupSetup)();
}
.ino
ファイル
通常のやり方でサンプルプログラムを真似た場合、.ino
ファイルだけであるとはいえ
#include <TFT_eSPI.h>
#include <lvgl.h>
#include "SimpleButtonApp.hpp"
/* TFT LCD */
static TFT_eSPI TFT;
/* App */
App* pApp;
void setup() {
pApp = App::mFpLaalInitialize(&TFT);
pApp -> mFSetup();
}
void loop() {
pApp -> mFOperateGraphic();
}
Discussion