【Wio Terminal】LvGLとWi-Fiの併用・FreeRTOSでのマルチタスク
LvGL とFreeRTOS とWi -Fi と
これまで、
次に関心を遣るのは、
但し、Seeed_Arduino_rpcUnified
の実装には、既に
実装上の問題
現在私が把握するだけでも、いくつか問題がある。
1. Wio Terminal に於けるWi -Fi とFreeRTOS との両立
通常であれば、
参考:他機の記事
この法は他の機種でも劇変するものではない。完全に同等ではないものの、概ね似たようなプログラムが見られる。
- 機種不明(
?)Arduino
ESP32
-
Raspberry Pi Pico
Windows
-
ドキュメントの日本語訳記事FreeRTOS
しかし
補足
Seeed_Arduino_rpcUnified
の中に、erpc::Thread
を使ってタスクを作っている記載がある。
このように、既に複数のタスクが作成され、起動している。よって、この方法に倣うことでタスクが動くようになる。しかしながら、
loop()
に書く。erpc::Thread
で作られるタスクには、
#include <rpcWiFi.h>
constexpr uint16_t STACK_SIZE = 256;
void setup() {
Serial.begin(115200); // baudrate設定
vNopDelayMS(1000); // 一秒待機(故障対策)
while(!Serial); // シリアルモニターが開かれるまで待機
vSetErrorSerial(&Serial); // エラー出力を有効にする
erpc::Thread TaskA(&ThreadA, configMAX_PRIORITIES - 10, STACK_SIZE, "Task A");
TaskA.start();
}
void loop() {
/* ここにWi-Fiの処理を記述する */
}
static void ThreadA(void* pvParameters) {
(void) pvParameters;
while (1) {
/* ここに別の処理を記述する */
}
}
2. FreeRTOS とLvGL
ドキュメントの確認
最新バージョンのドキュメントには、幾つもある中に
ここで、
この記事に示される通り、Seeed_Arduino_LvGL
を使う。このライブラリーに使われている7.0.2
である。
ドキュメントとしてはバージョン7.11.0
の記事が残っているため、最新のものではなくこちらを読むこととなる。しかし、
このように、
想定されているとは言え、
実際のところ、先のerpc::Thread
で作成するタスクでは実行できなかった。つまり、loop()
に書かなければならないと同時に、loop()
に書かなければならないのである。
対策として、lv_task_handler()
)をloop()
に、
プログラム
適当なサンプルプログラムを改変する。
- 上下に二つのボタンが配置され、
Wio 筐体のスイッチで操作できるTerminal - ボタンの文字に数を表示する
-
-Wi アクセスポイントをスキャンして数を取得するFi - 上のボタンには経過
の値を都度反映するtick - 下のボタンにはアクセスポイント数を都度反映する
なお、細かな設定が多くコードの整頓に困ったため、勝手にクラス化している。
抽象部
コード
#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 <rpcWiFi.h>
#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 */
static lv_obj_t* __mpObjectButton1;
/* label1 */
static lv_obj_t* __mpObjectLabel1;
/* button2 */
static lv_obj_t* __mpObjectButton2;
/* label2 */
static lv_obj_t* __mpObjectLabel2;
/* button配置 */
void __mFButtonsSetup();
/* group */
static lv_group_t* __mpGroup;
/* group設定 */
void __mFGroupSetup();
/* last tick */
static uint32_t __mu32LastTick;
/* task object */
lv_task_t* __mpTaskScanWifi;
/* task function */
static void __mFScanWifi(
lv_task_t* pTask
);
/* set task */
void __mFSetTask();
/* 画面構成 */
void __mFContentsSetup();
/* constructor */
App();
/* destructor */
~App();
public:
/* 初期化 */
static App* mFpLaalInitialize(
TFT_eSPI* pTft
);
};
#endif // __SIMPLE_BUTTON_APP_HPP__
#include "SimpleButtonApp.hpp"
/* static member variable */
uint32_t App::__mu32LastTick = 0;
lv_obj_t* App::__mpObjectButton1 = nullptr;
lv_obj_t* App::__mpObjectLabel1 = nullptr;
lv_obj_t* App::__mpObjectButton2 = nullptr;
lv_obj_t* App::__mpObjectLabel2 = nullptr;
lv_group_t* App::__mpGroup = nullptr;
/* member function */
/* initialize */
/*static*/ 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() {}
/* display */
void App::__mFDisplaySetup() {
LAAL<App>::__mFDisplaySetup();
}
/* input */
/*static*/ 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);
}
/* contents */
/* callback */
/* widget */
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, "Tick:0");
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, "AP:x");
}
/* group */
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);
}
/* task */
/*static*/ void App::__mFScanWifi(
lv_task_t* pTask
) {
(void) pTask;
/* 経過時間 */
uint32_t u32TickElapsed = lv_tick_get() - App::__mu32LastTick;
/* tick更新 */
bool bUpdateTick = false;
/* label1にtickを更新 */
lv_label_set_text_fmt(
App::__mpObjectLabel1,
"Tick:%d",
lv_tick_get()
);
/* 5000tickより経過した場合 */
if (u32TickElapsed > 5000) {
/* Wi-Fiアクセスポイント数 */
const uint16_t count = WiFi.scanNetworks();
/* label2にアクセスポイント数を更新 */
lv_label_set_text_fmt(
App::__mpObjectLabel2,
"AP:%d",
count
);
bUpdateTick = true;
}
if (bUpdateTick) {
App::__mu32LastTick = lv_tick_get();
}
}
void App::__mFSetTask() {
this -> __mpTaskScanWifi = lv_task_create(
App::__mFScanWifi,
500,
LV_TASK_PRIO_MID,
NULL
);
}
void App::__mFContentsSetup() {
(this -> __mFButtonsSetup)();
(this -> __mFGroupSetup)();
(this -> __mFSetTask)();
}
ino
ファイル
コード
#include <rpcWiFi.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
#include "SimpleButtonApp.hpp"
/* シリアルデバッグ ON/OFF */
#define SERIAL_MONITOR_ON
/* threadの容量 */
constexpr uint16_t STACK_SIZE = 256;
/* TFT LCD */
static TFT_eSPI TFT;
/* App */
App* pApp;
void setup() {
#ifdef SERIAL_MONITOR_ON
Serial.begin(115200);
vNopDelayMS(1000);
while(!Serial);
vSetErrorSerial(&Serial);
Serial.println("Setup\tStart");
#endif
#ifdef SERIAL_MONITOR_ON
Serial.println("Setup\ttask: create");
#endif
erpc::Thread TaskA(
&ThreadA,
configMAX_PRIORITIES - 10,
STACK_SIZE,
"Task A"
);
TaskA.start();
#ifdef SERIAL_MONITOR_ON
Serial.println("Setup\tWi-Fi: setting");
#endif
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
#ifdef SERIAL_MONITOR_ON
Serial.println("Setup\tLvGL: setting");
#endif
pApp = App::mFpLaalInitialize(&TFT);
pApp -> mFSetup();
}
void loop() {
#ifdef SERIAL_MONITOR_ON
Serial.println("Loop\tLvGL: operating");
#endif
pApp -> mFOperateGraphic();
}
static void ThreadA(void* pvParameters) {
(void) pvParameters;
#ifdef SERIAL_MONITOR_ON
Serial.println("Thread A\tStart");
#endif
uint32_t number = 0;
while (1) {
#ifdef SERIAL_MONITOR_ON
Serial.printf("Thread A\tHello: %d\n", number);
#endif
number++;
delay(2000);
}
}
実行結果
画面の様子は撮影が面倒なため省略する。
なお、
一方、erpc::Thread
で作成したThread A Hello: ~~
の表示が止まることはない。
Setup Start
Setup task: create
Thread A Start
Thread A Hello: 0
Setup Wi-Fi: setting
Setup LvGL: setting
Loop LvGL: operating
Thread A Hello: 1
Thread A Hello: 2
Thread A Hello: 3
Thread A Hello: 4
Thread A Hello: 5
Thread A Hello: 6
Thread A Hello: 7
Thread A Hello: 8
Thread A Hello: 9
Thread A Hello: 10
︙
Discussion