【Wio Terminal】FreeRTOSで画面遷移(求助言)
Wio Terminalで画面遷移の練習
世には
とは言え、実態はやはり
概要
本記事で作成したプログラムの概要を述べます。
画面遷移の概要
横一列に並んでいる
プログラム
プログラムは私の
一応、本記事でも記載しておきます。
プログラム
プログラム概説
簡単ながらプログラムについて説明しています。
ino ファイル
本プログラムでは
/*
画面遷移
トップ画面からA画面とB画面の孰れかに遷移できる
Aボタンを押せば画面Aへ
Bボタンを押せば画面Bへ遷移する
遷移先でCボタンを押すことでトップ画面に復帰する
*/
#include <Seeed_Arduino_FreeRTOS.h>
#include "queue.h"
#include "enumerates.h"
#include "readButton.h"
#include "TFT_Display.h"
/* タスクの許容領域 */
#define STACK_SIZE 256
/* キューの容量 */
#define QUEUE_SIZE 16
TaskHandle_t taskHandlerLCD,
taskHandlerButton;
QueueHandle_t queueButton;
/*
TFT LCDの初期化
クラスメンバーにそのまま含めたり函数内で初期化したりすると
正しく動作しない
*/
TFT_eSPI LIQUID_CRYSTAL_DISPLAY;
static void ThreadButton(void* pvParameters);
static void ThreadLCD(void* pvParameters);
void setup() {
/* キューを作る */
queueButton = xQueueCreate(
QUEUE_SIZE,
sizeof(Wio3Button)
);
/* タスクを作る */
xTaskCreate(
ThreadLCD,
"TFT LCD",
STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 1,
&taskHandlerLCD
);
xTaskCreate(
ThreadButton,
"Button",
STACK_SIZE,
NULL,
tskIDLE_PRIORITY,
&taskHandlerButton
);
/* タスクを開始する */
vTaskStartScheduler();
}
void loop() {}
/* ボタン入力処理 */
static void ThreadButton(void* pvParameters) {
(void) pvParameters;
/* 初期化 */
ButtonReader buttonReader;
/* 入力検知 */
ButtonInputDetection resultCode;
Wio3Button* (ButtonReader::*pmFpW3BGetInput)()
= &ButtonReader::mFpW3BGetInput;
void (ButtonReader::*pmFReadButtonInput)(
ButtonInputDetection* const pResult
) = &ButtonReader::mFReadButtonInput;
while (1) {
(buttonReader.*pmFReadButtonInput)(&resultCode);
if (resultCode == INPUT_DETECTED) {
/* 入力を検知した場合ボタンの種類をqueueに送信する */
xQueueSend(
queueButton,
(buttonReader.*pmFpW3BGetInput)(),
portMAX_DELAY
);
}
delay(100);
}
}
/* 画面処理 */
static void ThreadLCD(void* pvParameters) {
(void) pvParameters;
/* 初期化 */
Displayer displayer(&LIQUID_CRYSTAL_DISPLAY);
/* ボタン入力を受信する変数 */
Wio3Button receivedInputButton;
void (Displayer::*pmFChangeDisplay)(
const Wio3Button* const pButtonInput
) = &Displayer::mFChangeDisplay;
while (1) {
/* ボタン入力を受信する */
xQueueReceive(
queueButton,
&receivedInputButton,
portMAX_DELAY
);
(displayer.*pmFChangeDisplay)(&receivedInputButton);
delay(150);
}
}
機能分離
具体的な機能は分離しています。ファイルとしては整理されましたが、結合に四苦八苦した末、このような形に落ち着いています。なお、include
しています。
列挙体
種々の状態管理に用いている値を集約しています。
#ifndef __ENUMERATES__
#define __ENUMERATES__
typedef enum {
BUTTON_A,
BUTTON_B,
BUTTON_C
} Wio3Button;
typedef enum {
NO_INPUT,
INPUT_A,
INPUT_B,
INPUT_C
} LatestInputState;
typedef enum {
MAIN_SCREEN,
DISPLAY_A,
DISPLAY_B
} DisplayState;
typedef enum {
NO_INPUT_DETECTED,
INPUT_DETECTED
} ButtonInputDetection;
#endif
ボタン処理
ボタン入力に関する処理をクラスにしています。長押しによって処理が連発しないよう、直前にボタンが押されていたか否かを変数に記録するようにしています。
#ifndef __READ_BUTTON__
#define __READ_BUTTON__
#include "enumerates.h"
class ButtonReader {
private:
/*
enumの値をqueueで扱うには変数を設ける
この変数の参照を送ればよい
*/
Wio3Button mInputButton;
/*
一度のボタン操作に対して処理は一度きりとするために、
直前の入力の有無を管理する
*/
LatestInputState mLatestInput;
public:
ButtonReader();
// ~ButtonReader();
/* 入力検知 */
void mFReadButtonInput(ButtonInputDetection* const pResult);
/* getter */
Wio3Button* mFpW3BGetInput();
};
#endif
/* WIO_KEY_A WIO_KEY_B WIO_KEY_C */
#include <variant.h>
/* pinMode() */
#include <wiring_digital.h>
/* HIGH LOW */
#include <wiring_constants.h>
/* delay() */
#include <delay.h>
#include "readButton.h"
#include "enumerates.h"
ButtonReader::ButtonReader() {
/* ボタンA・B・Cピンを入力に設定する */
pinMode(WIO_KEY_A, INPUT);
pinMode(WIO_KEY_B, INPUT);
pinMode(WIO_KEY_C, INPUT);
/* 無入力を初期状態とする */
this -> mLatestInput = NO_INPUT;
}
// ButtonReader::~ButtonReader() {}
void ButtonReader::mFReadButtonInput(ButtonInputDetection* const pResult) {
/* 入力検知の有無を記録する */
*pResult = NO_INPUT_DETECTED;
if (this -> mLatestInput == NO_INPUT) {
/* 直前に入力が無かった場合 */
if (digitalRead(WIO_KEY_A) == LOW) {
this -> mInputButton = BUTTON_A;
this -> mLatestInput = INPUT_A;
*pResult = INPUT_DETECTED;
}
else if (digitalRead(WIO_KEY_B) == LOW) {
this -> mInputButton = BUTTON_B;
this -> mLatestInput = INPUT_B;
*pResult = INPUT_DETECTED;
}
else if (digitalRead(WIO_KEY_C) == LOW) {
this -> mInputButton = BUTTON_C;
this -> mLatestInput = INPUT_C;
*pResult = INPUT_DETECTED;
}
}
else {
/*
直前に入力があった場合
現在孰れのボタンも押されていない場合のみ
NO_INPUTに更新する
*/
if (
digitalRead(WIO_KEY_A) == HIGH &&
digitalRead(WIO_KEY_B) == HIGH &&
digitalRead(WIO_KEY_C) == HIGH
) {
this -> mLatestInput = NO_INPUT;
}
}
}
Wio3Button* ButtonReader::mFpW3BGetInput() {
return &(this -> mInputButton);
}
画面処理
画面表示に関する処理をクラス化しています。TFT_eSPI
インスタンスは
#ifndef __TFT_DISPLAY__
#define __TFT_DISPLAY__
#include "TFT_eSPI.h"
#include "enumerates.h"
/* 黒紅 */
#define TFT_KUROBENI 0x2926
/* 鴇鼠 */
#define TFT_TOKINEZU 0xDE7A
class Displayer {
private:
TFT_eSPI* pTft;
/* 画面表示の状態 */
DisplayState currentDisplayState;
void privateFShowMainScreen();
void privateFChangeScreen(const DisplayState newState);
void privateFChangeDisplayState(const DisplayState newState);
public:
Displayer(TFT_eSPI* const pTftLcd);
// ~Displayer();
void mFChangeDisplay(const Wio3Button* const pButtonInput);
};
#endif
#include "TFT_Display.h"
#include "enumerates.h"
Displayer::Displayer(TFT_eSPI* const pTftLcd):
pTft(pTftLcd) {
/* TFT LCDの準備 */
(this -> pTft) -> begin();
(this -> pTft) -> setRotation(3);
(this -> pTft) -> setTextSize(3);
(this -> pTft) -> setTextColor(TFT_KUROBENI);
/* トップ画面を表示する */
this -> privateFShowMainScreen();
}
// Displayer::~Displayer() {}
void Displayer::privateFShowMainScreen() {
/* 背景初期化 */
(this -> pTft) -> fillScreen(TFT_TOKINEZU);
/* ボタン指示 */
(this -> pTft) -> drawString("A", 166, 2);
(this -> pTft) -> drawString("B", 82, 2);
/* メッセージ */
(this -> pTft) -> drawString("Push button", 60, 110);
(this -> pTft) -> drawString("A or B", 110, 135);
this -> privateFChangeDisplayState(MAIN_SCREEN);
}
void Displayer::privateFChangeScreen(const DisplayState newState) {
if (this -> currentDisplayState != newState) {
char* message;
switch (newState) {
case DISPLAY_A:
message = "Button A";
break;
case DISPLAY_B:
message = "Button B";
break;
default:
message = "hello?";
break;
}
(this -> pTft) -> fillScreen(TFT_TOKINEZU);
(this -> pTft) -> drawString(message, 85, 110);
(this -> pTft) -> drawString("C", 10, 2);
(this -> pTft) -> drawString("Push C to back.", 25, 205);
this -> privateFChangeDisplayState(newState);
}
}
void Displayer::privateFChangeDisplayState(const DisplayState newState) {
if (this -> currentDisplayState != newState)
this -> currentDisplayState = newState;
}
void Displayer::mFChangeDisplay(const Wio3Button* const pButtonInput) {
switch (*pButtonInput) {
case BUTTON_A:
/* トップ画面ならA画面に遷移する */
if (this -> currentDisplayState == MAIN_SCREEN) {
this -> privateFChangeScreen(DISPLAY_A);
}
break;
case BUTTON_B:
/* トップ画面ならB画面に遷移する */
if (this -> currentDisplayState == MAIN_SCREEN) {
this -> privateFChangeScreen(DISPLAY_B);
}
break;
case BUTTON_C:
/* トップ画面でないならトップ画面に戻る */
if (this -> currentDisplayState != MAIN_SCREEN) {
this -> privateFShowMainScreen();
}
break;
default:
break;
}
}
旧プログラム
旧プログラム概説
こちらのプログラムは、TFT_eSPI
インスタンスの扱いが気に食わなかったので、後に修正しました。これに関係ない箇所はそのままです。
ino ファイル
#include <Seeed_Arduino_FreeRTOS.h>
#include "queue.h"
#include "enumerates.h"
#include "readButton.h"
#include "TFT_Display.h"
/* タスクの許容領域 */
#define STACK_SIZE 256
/* キューの容量 */
#define QUEUE_SIZE 16
TaskHandle_t taskHandlerLCD,
taskHandlerButton;
QueueHandle_t queueButton;
/*
TFT LCDの初期化
クラスメンバーに含めたり函数内で初期化したりすると
正しく動作しない
*/
TFT_eSPI LIQUID_CRYSTAL_DISPLAY;
static void ThreadButton(void* pvParameters);
static void ThreadLCD(void* pvParameters);
void setup() {
/* キューを作る */
queueButton = xQueueCreate(
QUEUE_SIZE,
sizeof(Wio3Button)
);
/* タスクを作る */
xTaskCreate(
ThreadLCD,
"TFT LCD",
STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 1,
&taskHandlerLCD
);
xTaskCreate(
ThreadButton,
"Button",
STACK_SIZE,
NULL,
tskIDLE_PRIORITY,
&taskHandlerButton
);
/* タスクを開始する */
vTaskStartScheduler();
}
void loop() {}
/* ボタン入力処理 */
static void ThreadButton(void* pvParameters) {
(void) pvParameters;
/* 初期化 */
ButtonReader buttonReader;
/* 入力検知 */
ButtonInputDetection resultCode;
Wio3Button* (ButtonReader::*pmFpW3BGetInput)()
= &ButtonReader::mFpW3BGetInput;
void (ButtonReader::*pmFReadButtonInput)(
ButtonInputDetection* const pResult
) = &ButtonReader::mFReadButtonInput;
while (1) {
(buttonReader.*pmFReadButtonInput)(&resultCode);
if (resultCode == INPUT_DETECTED) {
/* 入力を検知した場合ボタンの種類をqueueに送信する */
xQueueSend(
queueButton,
(buttonReader.*pmFpW3BGetInput)(),
portMAX_DELAY
);
}
delay(100);
}
}
/* 画面処理 */
static void ThreadLCD(void* pvParameters) {
(void) pvParameters;
/* 初期化 */
Displayer displayer(&LIQUID_CRYSTAL_DISPLAY);
/* ボタン入力を受信する変数 */
Wio3Button receivedInputButton;
void (Displayer::*pmFChangeDisplay)(
const Wio3Button* const pButtonInput,
TFT_eSPI* const pTftLcd
) = &Displayer::mFChangeDisplay;
while (1) {
/* ボタン入力を受信する */
xQueueReceive(
queueButton,
&receivedInputButton,
portMAX_DELAY
);
(displayer.*pmFChangeDisplay)(&receivedInputButton, &LIQUID_CRYSTAL_DISPLAY);
delay(150);
}
}
画面処理
これを書いた時は、TFT_eSPI
インスタンスのポインターをクラスメンバー変数として保有するという発想がなかったため、関数の引数からポインターを受けています。
#ifndef __TFT_DISPLAY__
#define __TFT_DISPLAY__
#include "TFT_eSPI.h"
#include "enumerates.h"
/* 黒紅 */
#define TFT_KUROBENI 0x2926
/* 鴇鼠 */
#define TFT_TOKINEZU 0xDE7A
class Displayer {
private:
/* 画面表示の状態 */
DisplayState currentDisplayState;
void privateFShowMainScreen(TFT_eSPI* const pTftLcd);
void privateFChangeScreen(const DisplayState newState, TFT_eSPI* const pTftLcd);
void privateFChangeDisplayState(const DisplayState newState);
public:
Displayer(TFT_eSPI* const pTftLcd);
// ~Displayer();
void mFChangeDisplay(const Wio3Button* const pButtonInput, TFT_eSPI* const pTftLcd);
};
#endif
#include "TFT_Display.h"
#include "enumerates.h"
Displayer::Displayer(TFT_eSPI* const pTftLcd) {
/* TFT LCDの準備 */
pTftLcd -> begin();
pTftLcd -> setRotation(3);
pTftLcd -> setTextSize(3);
pTftLcd -> setTextColor(TFT_KUROBENI);
/* トップ画面を表示する */
this -> privateFShowMainScreen(pTftLcd);
}
// Displayer::~Displayer() {}
void Displayer::privateFShowMainScreen(TFT_eSPI* const pTftLcd) {
/* 背景初期化 */
pTftLcd -> fillScreen(TFT_TOKINEZU);
/* ボタン指示 */
pTftLcd -> drawString("A", 166, 2);
pTftLcd -> drawString("B", 82, 2);
/* メッセージ */
pTftLcd -> drawString("Push button", 60, 110);
pTftLcd -> drawString("A or B", 110, 135);
this -> privateFChangeDisplayState(MAIN_SCREEN);
}
void Displayer::privateFChangeScreen(const DisplayState newState, TFT_eSPI* const pTftLcd) {
if (this -> currentDisplayState != newState) {
char* message;
switch (newState) {
case DISPLAY_A:
message = "Button A";
break;
case DISPLAY_B:
message = "Button B";
break;
default:
message = "hello?";
break;
}
pTftLcd -> fillScreen(TFT_TOKINEZU);
pTftLcd -> drawString(message, 85, 110);
pTftLcd -> drawString("C", 10, 2);
pTftLcd -> drawString("Push C to back.", 25, 205);
this -> privateFChangeDisplayState(newState);
}
}
void Displayer::privateFChangeDisplayState(const DisplayState newState) {
if (this -> currentDisplayState != newState)
this -> currentDisplayState = newState;
}
void Displayer::mFChangeDisplay(const Wio3Button* const pButtonInput, TFT_eSPI* const pTftLcd) {
switch (*pButtonInput) {
case BUTTON_A:
/* トップ画面ならA画面に遷移する */
if (this -> currentDisplayState == MAIN_SCREEN) {
this -> privateFChangeScreen(DISPLAY_A, pTftLcd);
}
break;
case BUTTON_B:
/* トップ画面ならB画面に遷移する */
if (this -> currentDisplayState == MAIN_SCREEN) {
this -> privateFChangeScreen(DISPLAY_B, pTftLcd);
}
break;
case BUTTON_C:
/* トップ画面でないならトップ画面に戻る */
if (this -> currentDisplayState != MAIN_SCREEN) {
this -> privateFShowMainScreen(pTftLcd);
}
break;
default:
break;
}
}
実行結果
以下に画面の様子を示します。
トップ画面
トップ画面の表示
A
画面
画面
メッセージの文法が普通に間違っています。恥ずかしい。プログラムは修正してあります。
B
画面画面
画面
跋
画面が遷移するだけのプログラムですから、これだけでは無味乾燥というものです。しかし実現することはできたので、これを応用して何か作れましょう。個人的にも幾らか案があるので、いずれ実現しようと思います。
Discussion