【Wio Terminal/LvGL】センサーの値をメーター風に可視化
センサーの値を画面に反映する
その簡単な事例として、本記事ではセンサーを用いる。通常
引用:https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/
センサーから取得した値を画面に表示するに当たり、単に文字で数値を表示するだけであれば、態々しく
サンプル集の中には、アニメーションを使ってメーターを動かしているものがある。
本来であればこれを参考にすればよいはずだが、Seeed_Arduino_LvGL
には、
プログラム
先にオブジェクト指向での抽象化を試みた通り、実装を分けている。
LvglArduinoAbstractLayer
#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));
}
LightGaugeApp
#ifndef __LIGHT_GAUGE_APP_HPP__
#define __LIGHT_GAUGE_APP_HPP__
#include "LvglArduinoAbstractLayer.hpp"
class App final: public LAAL<App> {
private:
/* 画面設定 */
void __mFDisplaySetup();
/* デバイス設定 */
void __mFDeviceSetup();
/* reading light sensor */
static uint32_t __mFu32GetLightSensorValue();
/* gauge */
static lv_obj_t* __mpObjectGaugeLight;
/* gauge配置 */
void __mFGaugeSetup();
/* task object */
lv_task_t* __mpTaskUpdateGauge;
/* task function */
static void __mFUpdateGauge(lv_task_t* pTask);
/* set task */
void __mFSetTask();
/* 画面構成 */
void __mFContentsSetup();
/* constructor */
App();
/* destructor */
~App();
public:
/* 初期化 */
static App* mFpLaalInitialize(TFT_eSPI* pTft);
};
#endif // __LIGHT_GAUGE_APP_HPP__
#include "LightGaugeApp.hpp"
/* 針の数 */
constexpr uint16_t __NEEDLE_COUNT__ = 1;
/* 針の色 */
constexpr lv_color_t __NEEDLE_COLOR__[__NEEDLE_COUNT__] = {LV_COLOR_SILVER};
/* gauge */
lv_obj_t* App::__mpObjectGaugeLight = nullptr;
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();
}
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 light sensor */
pinMode(WIO_LIGHT, INPUT);
}
uint32_t App::__mFu32GetLightSensorValue() {
/* センサー値を取得する */
return analogRead(WIO_LIGHT);
}
void App::__mFGaugeSetup() {
/* create gauge */
this -> __mpObjectGaugeLight = lv_gauge_create(lv_scr_act(), NULL);
/* set needle count 1 */
lv_gauge_set_needle_count(this -> __mpObjectGaugeLight, __NEEDLE_COUNT__, __NEEDLE_COLOR__);
/* set gauge size */
lv_obj_set_size(this -> __mpObjectGaugeLight, 200, 200);
/* set align center */
lv_obj_align(this -> __mpObjectGaugeLight, NULL, LV_ALIGN_CENTER, 0, 0);
/* set gauge range */
lv_gauge_set_range(this -> __mpObjectGaugeLight, 0, 1100);
}
void App::__mFUpdateGauge(lv_task_t* pTask) {
(void) pTask;
lv_gauge_set_value(App::__mpObjectGaugeLight, 0, (App::__mFu32GetLightSensorValue)());
}
void App::__mFSetTask() {
this -> __mpTaskUpdateGauge = lv_task_create(App::__mFUpdateGauge, 50, LV_TASK_PRIO_HIGHEST, NULL);
}
void App::__mFContentsSetup() {
(this -> __mFGaugeSetup)();
(this -> __mFSetTask)();
}
taskの注意:staticとリンクエラー
センサーの値を取得するに当たり、task
を作っている。
void App::__mFSetTask() {
this -> __mpTaskUpdateGauge = lv_task_create(App::__mFUpdateGauge, 50, LV_TASK_PRIO_HIGHEST, NULL);
}
この時、lv_task_create()
の第一引数には、task
として動作する関数を指定する。但し、指定する関数はstatic
でなければならないため、App::__mFUpdateGauge()
をstatic
にする。static
としたクラスメンバー関数の中で、同クラスのメンバー変数やメンバー関数を使う場合、それらもまたstatic
にしなければならない。
static
か否かによって、次のように書き分ける。
static |
non-static |
---|---|
App::__mpObjectGaugeLight |
this -> __mpObjectGaugeLight |
このような修正を施した後、次のようなリンクエラーが出た。
C:\Users\⋯\AppData\Local\arduino\sketches\DC395E2A554803459821E977C51E7A61\sketch\LightGaugeApp.cpp.o: In function `App::__mFUpdateGauge(_lv_task_t*)':
C:\⋯\light_gauge/LightGaugeApp.cpp:64: undefined reference to `App::__mpObjectGaugeLight'
C:\Users\⋯\AppData\Local\arduino\sketches\DC395E2A554803459821E977C51E7A61\sketch\LightGaugeApp.cpp.o: In function `lv_gauge_set_range':
c:\⋯\libraries\Seeed_Arduino_LvGL\src/src/lv_widgets/lv_gauge.h:105: undefined reference to `App::__mpObjectGaugeLight'
collect2.exe: error: ld returned 1 exit status
App::__mpObjectGaugeLight
に関するリンクエラーであるため、次を明記しておくと解決する。
lv_obj_t* App::__mpObjectGaugeLight = nullptr;
static
となったものはプログラムの始まりと共に存在するが、存在することと使える状態であることとは別の問題である。この時差が問題になったものと推察する。
.ino
ファイル
#include <TFT_eSPI.h>
#include <lvgl.h>
#include "LightGaugeApp.hpp"
/* TFT LCD */
static TFT_eSPI TFT;
/* App */
App* pApp;
void setup() {
pApp = App::mFpLaalInitialize(&TFT);
pApp -> mFSetup();
}
void loop() {
pApp -> mFOperateGraphic();
}
実行結果
センサーは筐体背面側にあるため、裏から光を当てている。
Discussion