AtomS3の初見で液晶表示について、いろいろ気になったことがあったので使用するボードやライブラリ等の初期設定について、もう少し詳しく調べてみました。
基本的な使用方法は以下のリンクで詳しく紹介しています。
初見の設定では「AtomS3」の液晶(「AtomS3 Lite」のLEDも)の色の指定が赤と青で逆だったり、日本語表示やスプライト(メモリ描画領域の一括表示)のために「M5GFX」ライブラリを使おうとすると、コンパイル時にエラーが出たりしました。
日本語表示とスプライトができないと何かと不便なので、先に解決しておきます。
解決できたら便利に使えるようになったので初期設定方法等、詳しく紹介します。
1.動作確認用プログラムの動作紹介
・カウントダウンタイマー
・シンプルな初期設定での日本語表示
2.サンプルプログラム(コピペ)
・カウントダウンタイマー
・シンプルな初期設定での日本語表示
3.前回初見で使用した初期設定
・ArduinoIDE
・PlatformIO
・問題点
4.今回のサンプルプログラムで使用した初期設定
・ArduinoIDE
・PlatformIO
・ヘッダーファイル、本体ボタン入力設定
5.サンプルプログラムの詳細
・M5GFXで日本語表示
・M5GFXのスプライトでチラツキなし液晶表示
・マルチタスク(並列処理)でタイマーカウント
・PWMでブザー出力(ブザー外付け)
6.まとめ
1.動作確認用プログラムの動作紹介
先に動作確認に使用したプログラムの紹介からしておきます。
グラフィック表示ライブラリ「M5GFX」を使用した日本語表示や、スプライト(メモリ描画領域一括出力)の確認のためにカウントダウンタイマーを製作しました。
タイマのカウントにはマルチタスクを使用して並列処理でカウントしています。
カウント終了時には外付けのブザーが鳴るようにPWM制御を使用します。
カウントダウンタイマーのサンプルプログラムで、これらの使い方や動作について確認することができます。
基本的な初期設定や日本語表示については、よりシンプルな表示だけのサンプルプログラムも準備しましたので、こちらも詳しく紹介していきます。
・カウントダウンタイマー
0.01秒刻みでカウントダウンする3分のカウントダウンタイマーです。
グラフィック処理用ライブラリ「M5GFX」でチラツキの無い画面表示ができます。
0.01秒のカウントには「マルチタスク処理」を使用します。
メイン処理と並行してカウントすることができるため、それなりに安定した時間カウントができます。(精度は「タイマ割込み」に劣ります。)
カウントダウンタイマーの動作は以下のようになります。
外付けのブザーは「GROVEコネクタ」に接続できる「BUZZER UNIT」です。
(ブザーは無くても本体だけで動作確認できます。)
電源を入れると上画像のようになります。
本体ボタンを押す(液晶画面を押し込む)とカウントダウンが始まります。
カウントを一時停止するには本体ボタンを押します。もう一度押すと再開します。
リセットするには約1秒長押しします。
残り10秒になるとカウント表示が赤くなります。
カウント0になるとブザーが鳴ります。
止めるには本体ボタンを約1秒長押しします。
正確な時間をカウントするには「タイマ割込」を使用した方が良いです。
「タイマ割込」については以下のリンクで詳しく紹介しています。
・シンプルな初期設定での日本語表示
シンプルに基本的な初期設定や日本語表示を確認できるサンプルプログラムも準備しました。
「M5GFX」ライブラリを使用して、スプライト設定した2つの表示「canvas1」「canvas2」をボタンを押すごとに切り換えます。
作成した2つの表示は以下のように当ブログのロゴ風に作成しました。
「canvas1」はPゴシック体のサイズ28で表示しています。
「canvas2」はP明朝体のサイズ24で表示しています。
2.サンプルプログラム(コピペ)
・カウントダウンタイマー
カウントダウンタイマーのサンプルプログラムは以下になります。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
#include <M5AtomS3.h>
#define BTN_GPIO 41 // 本体ボタン端子番号指定
#define BZ_GPIO 2 // Groveコネクタ接続ブザー用端子番号指定(未接続でも可)
static M5Canvas canvas(&M5.Lcd); // メモリ描画領域をcanvasという名前で準備
static void count_task(void *); // マルチタスク(並列処理)の関数宣言
// 変数宣言
uint16_t count = 18000; // 総カウント値(1秒=100)
uint16_t min_cnt; // 分カウント値格納用
uint16_t sec_cnt; // 秒カウント値格納用
uint16_t msec_cnt; // ミリ秒カウント値格納用
bool btn_state = false; // ボタン単押し状態確認用
bool start_flag = false; // カウントダウンスタートフラグ
bool limit_flag = false; // カウントリミット(10秒以下)判定用
bool press_state = false; // ボタン長押し状態確認用
int press_time; // ボタン長押し時間カウント用
// 初期設定 ----------------------------------------------------------
void setup() {
auto cfg = M5.config(); // 本体初期設定
AtomS3.begin(cfg);
Serial.begin(9600); // シリアル通信初期化
delay(1000); // シリアル出力開始待ち
Serial.println("ATOM S3 [3min Countdown Timer]"); // シリアル出力
// 本体端子初期化
pinMode(BTN_GPIO, INPUT_PULLUP); // 本体ボタン端子入力設定
// PWM出力設定
pinMode(BZ_GPIO, OUTPUT); // GroveコネクタG2端子出力設定(外付けブザー)
ledcSetup(1, 1000, 10); // PWM初期設定(チャンネル, 周波数Hz, bit数)
ledcWrite(1, 512); // PWM出力開始(チャンネル、Duty比:MAXはbit数の分解能)
// 液晶初期化
M5.Lcd.init(); // 初期化
M5.Lcd.setTextWrap(false); // テキストが画面からはみ出した時の折り返し無し
M5.Lcd.clear(M5.Lcd.color565(20, 0, 0)); // 背景色
M5.Lcd.setTextColor(TFT_WHITE); // 文字色
// 初期画面
M5.Lcd.fillRect(0, 0, 128, 25, TFT_WHITE); // タイトルエリア背景
M5.Lcd.setTextColor(M5.Lcd.color565(20, 20, 20)); // 文字色
M5.Lcd.drawString("ATOM", 12, 2, &fonts::Font4); // 上中央座標を基準に文字表示(表示内容, x, y)
M5.Lcd.setTextColor(TFT_RED); // 文字色
M5.Lcd.drawString("S3", 88, 2, &fonts::Font4); // 上中央座標を基準に文字表示(表示内容, x, y)
M5.Lcd.fillRect(16, 14, 7, 2, TFT_RED); // ATOMの「A」の横線用
M5.Lcd.setTextColor(TFT_CYAN); // 文字色
M5.Lcd.drawCentreString("3分カウントダウンタイマ", 64, 28, &fonts::lgfxJapanGothicP_16); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
M5.Lcd.drawFastHLine(0, 46, 128, TFT_WHITE); // 指定座標から横線(x, y, 長さ, 色)
M5.Lcd.drawFastHLine(0, 112, 128, TFT_WHITE); // 指定座標から横線(x, y, 長さ, 色)
M5.Lcd.setTextColor(TFT_WHITE); // 文字色
M5.Lcd.drawCentreString("Long Press Reset", 64, 113, &fonts::Font2); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
// メモリ描画(カウント値表示)領域初期化
canvas.setColorDepth(16); // カラーモード設定(16bit)
canvas.setTextWrap(false); // テキストが画面からはみ出した時の折り返し無し
canvas.createSprite(128, 64); // メモリ描画(カウント値表示)領域を設定(X, Y)
// マルチタスク(並列処理)設定(カウントダウン処理)
xTaskCreatePinnedToCore(count_task, "count_task", 4096, NULL, 1, NULL, 0);
}
// メイン -------------------------------------------------------------------
void loop() {
AtomS3.update(); //本体ボタン状態更新
// カウント値換算(分:秒:ミリ秒)
min_cnt = count / 6000; // 分カウント値換算
sec_cnt = (count - (min_cnt * 6000))/100; // 秒カウント値換算
msec_cnt = count - (min_cnt * 6000) - (sec_cnt * 100); // ミリ秒カウント値換算
// カウントエリア液晶表示(スプライト)
canvas.clear(M5.Lcd.color565(20, 0, 0)); // 背景色
canvas.setCursor(0, 10); // カーソル座標
canvas.setFont(&fonts::Font7); // フォント
if (limit_flag == false) { // カウントリミットフラグがfalseなら
canvas.setTextColor(TFT_YELLOW); // 文字色(黄)
} else { // カウントリミットフラグがtrueなら
canvas.setTextColor(TFT_RED); // 文字色(赤)
}
canvas.setTextSize(0.9); // 文字サイズ倍率
canvas.printf("%d:%02d", min_cnt, sec_cnt); // カウント値表示(分:秒)
canvas.setCursor(95, 27); // カーソル座標
canvas.setTextSize(0.47); // 文字サイズ倍率
canvas.printf(":%02d", msec_cnt); // カウント値表示(:ミリ秒)
canvas.pushSprite(0, 48); // 座標を指定してメモリ描画領域表示実行(x, y)
// ボタン長押しカウント値リセット
if (digitalRead(BTN_GPIO) == LOW) { // 本体ボタンONなら
ledcDetachPin(BZ_GPIO); // ブザーOFF:PWMチャンネル割り当て解除(ピン番号, チャンネル)
if (press_state == false) { // ボタン長押し状態でなければ
press_time = millis(); // ボタン長押し時間初期値セット
press_state = true; // ボタン長押し状態に変更
}
if (millis() >= (press_time + 800)) { // ボタン長押し時間初期値 + 800msなら
count = 18000; // カウント値初期化
start_flag = false; // カウントダウンスタートフラグをfalseへ
limit_flag = false; // カウントリミットフラグをfalseへ
}
}
// ボタンON
if (digitalRead(BTN_GPIO) == LOW && btn_state == false) { // 本体ボタンONでボタン単押し状態がfalseなら
btn_state = true; // ボタン単押し状態をtrueへ
start_flag = !start_flag; // カウントダウンスタートフラグを反転
}
// ボタンOFF
if (digitalRead(BTN_GPIO) == true) { // 本体ボタンがOFFなら
btn_state = false; // ボタン単押し状態をfalseへ
press_state = false; // ボタン長押し状態をfalseへ
}
// カウントリミット処理
if (min_cnt == 0 && sec_cnt <= 10) { // 分カウント換算値が0で、秒カウントが10以下なら
limit_flag = true; // カウントリミットフラグをtrueへ
}
// カウント終了ブザー出力
if (count == 0) { // 総カウント値が0なら
ledcAttachPin(BZ_GPIO, 1); // ブザーON:PWMチャンネル割り当て(ピン番号, チャンネル)
}
delay(7); // メイン処理遅延時間
}
// カウントダウン処理(マルチタスク:並列処理)---------------------------------------
static void count_task(void *) {
while (true) { // ずっと実行(並列処理)
delay(10); // 0.01秒カウント
// カウントダウンスタートフラグでカウントが0ではなく、本体ボタンがOFFならカウント実行
if (start_flag == true && count != 0 && digitalRead(BTN_GPIO) == HIGH) {
count--; // 総カウント値 -1
}
}
}
・シンプルな初期設定での日本語表示
基本的な初期設定(本体ボタン、液晶)と、スプライト設定した、2つの表示「canvas1」「canvas2」をボタンを押すごとに切り換えるサンプルプログラムは以下になります。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
#include <M5AtomS3.h>
static M5Canvas canvas1(&M5.Lcd); // スプライト1(メモリ描画領域)をcanvas1として準備
static M5Canvas canvas2(&M5.Lcd); // スプライト2(メモリ描画領域)をcanvas2として準備
bool change_flag = false; // 表示変更フラグ
// 初期設定 ----------------------------------------------------------
void setup() {
auto cfg = M5.config(); // 本体初期設定
AtomS3.begin(cfg);
Serial.begin(9600); // シリアル通信初期化
delay(1000); // シリアル出力開始待ち
Serial.println("ATOM S3 Simple Disp"); // シリアル出力
// 液晶表示内容設定
M5.Lcd.init(); // Lcdインスタンス初期化
// スプライト1(メモリ描画領域)表示内容
canvas1.setColorDepth(16); // カラーモード設定(16bit)
canvas1.createSprite(M5.Lcd.width(), M5.Lcd.height()); // canvas1サイズ(メモリ描画領域)設定(画面サイズに設定)
canvas1.fillScreen(TFT_BLACK); // 背景色(透明)
canvas1.fillCircle(64, 64, 63, TFT_LIGHTGRAY); // 塗り潰し円
canvas1.fillCircle(64, 64, 59, TFT_ORANGE); // 塗り潰し円
canvas1.setTextColor(TFT_WHITE); // 文字色
canvas1.drawCentreString("ロジカラ", 64, 37, &fonts::lgfxJapanGothicP_28); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
canvas1.drawCentreString("ブログ", 64, 65, &fonts::lgfxJapanGothicP_28); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
// スプライト2(メモリ描画領域)表示内容
canvas2.setColorDepth(16); // カラーモード設定(16bit)
canvas2.createSprite(M5.Lcd.width(), M5.Lcd.height()); // canvas2サイズ(メモリ描画領域)設定(画面サイズに設定)
canvas2.fillScreen(TFT_BROWN); // 背景色
canvas2.fillCircle(64, 64, 63, TFT_DARKGREY); // 塗り潰し円
canvas2.fillCircle(64, 64, 59, M5.Lcd.color565(30, 0, 0)); // 塗り潰し円
canvas2.setTextColor(TFT_WHITE); // 文字色
canvas2.drawCentreString("Logikara", 64, 40, &fonts::lgfxJapanMinchoP_24); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
canvas2.drawCentreString("Blog", 64, 68, &fonts::lgfxJapanMinchoP_24); // 上中央座標を基準に文字表示(表示内容, x, y, フォント)
}
// メイン -------------------------------------------------------------------
void loop() {
AtomS3.update(); //本体状態更新
// 本体ボタンON
if (M5.BtnA.wasPressed()) { // ボタンAが押されたら
change_flag = !change_flag; // 表示変更フラグを反転
}
// 液晶表示変更
if (change_flag == false) { // 表示変更フラグがfalseなら
canvas1.pushSprite(0, 0); // canvas1を座標を指定して表示(スプライト)
} else { // 表示変更フラグがtrueなら
canvas2.pushSprite(0, 0); // canvas2を座標を指定して表示(スプライト)
}
delay(100); // 遅延時間
}
3.前回初見で使用した初期設定
「AtomS3」の初期設定については「AtomS3 Lite」とほぼ同じなため、以前以下のリンクで詳しく紹介しました。
初見(2023/3/12)で使用した初期設定のため、最適な設定は今後変わると思いますが、初見での初期設定について「ArduinoIDE」と「PlatformIO」に分けて紹介します。
・ArduinoIDE
「ArduinoIDE」の以前の初期設定は以下のようになります。
ボードマネージャ | M5Stack official バージョン2.0.6-1.0 |
ボード | M5Stack-AtomS3 |
ライブラリ | M5AtomS3 FastLED |
・PlatformIO
「PlatformIO」の以前の初期設定は以下のようになります。
ボード | Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM) |
ライブラリ | M5AtomS3 FastLED |
・問題点
上記の設定でも使用できましたが、以下のような問題点がありました。
- 色の指定「R(赤)、G(緑)、B(青)」の赤と青を逆に指定しないと希望の色にならない。
(「ORANGE」を指定すると水色のようになる。) - M5GFXライブラリをインクルードするとエラーになりコンパイルできないため、簡単に日本語を使ったり、スプライト(メモリ描画領域の一括出力)表示ができない。
4.今回のサンプルプログラムで使用した初期設定
今回のサンプルプログラムで使用した初期設定は以下のようになります。
・ArduinoIDE
「ArduinoIDE」の初期設定は以下のようにライブラリのみ変更しました。
ボードマネージャ | M5Stack Arduino バージョン2.1.0 |
ボード | M5AtomS3 |
ライブラリ | M5AtomS3(バージョン1.0.0) FastLED(バージョン3.6.0) M5Unified(バージョン0.1.13) |
・PlatformIO
「PlatformIO」の初期設定は以下のようにボードとライブラリ両方変更しています。
ボード | M5Stack AtomS3 |
ライブラリ | M5AtomS3(バージョン1.0.0) FastLED(バージョン3.6.0) M5Unified(バージョン0.1.13) |
設定ファイル「platformio.ini」の内容は以下のようになります。
・ヘッダーファイル、本体ボタン入力設定
ヘッダーファイルは以下のように指定します。
#include <M5AtomS3.h>
本体ボタンは以下のように「M5.BtnA.〜」のコマンドが使用できます
AtomS3.update(); //本体状態更新
// 本体ボタンON
if (M5.BtnA.wasPressed()) { // ボタンAが押されたら
change_flag = !change_flag; // 表示変更フラグを反転
}
ボタン操作については以下のリンクで使い方を詳しく紹介しています。
5.サンプルプログラムの詳細
日本語表示やスプライト、マルチタスク等、各処理ごとに使い方をサンプルプログラムから抜粋して紹介します。
・M5GFXで日本語表示
日本語の表示については「シンプルな初期設定での日本語表示サンプルプログラム」の、 26, 27、36, 37行目の「&fonts::lgfxJapanGothicP_28」のように「&fonts::」の後にフォントを指定します。
フォントについては「明朝体」や「ゴシック体」「M5Stack標準フォント」等、以前以下のリンクで紹介した「lovyanGFX」と同じフォントが使用できます。
・M5GFXのスプライトでチラツキなし液晶表示
「M5GFX」ライブラリを使用して「スプライト」表示すると「メモリ内に描画した画面の一括出力」ができるため、チラツキのない画面表示ができます。
スプライトする画面は複数設定できるため、状況に応じてプログラムで切り替えて表示さることができます。
「シンプルな初期設定での日本語表示サンプルプログラム」では、まず以下のように「M5GFX」のインスタンスを作成(3, 4行目)して、表示画面を描画する領域を名前をつけてメモリ内に準備します。
static M5Canvas canvas1(&M5.Lcd); // スプライト1(メモリ描画領域)をcanvas1として準備
static M5Canvas canvas2(&M5.Lcd); // スプライト2(メモリ描画領域)をcanvas2として準備
「スプライト」するための2つの画面「canvas1(19〜27行目)」と「canvas2(29〜37行目)」に表示内容を設定します。
本体ボタンを押すごとに切換えて 49行目と 51行目のように指定すると、設定した表示内容を一括表示します。
canvas1.pushSprite(0, 0); // canvas1を座標を指定して表示(スプライト)
canvas2.pushSprite(0, 0); // canvas2を座標を指定して表示(スプライト)
「スプライト」の設定方法については以下のリンクの「lovyanGFX」の使い方の中で詳しく紹介しています。
・マルチタスク(並列処理)でタイマーカウント
「マルチタスク処理」とは、メイン処理と並行して実行することができる処理です。
時間カウントを正確に行うには「タイマ割込」を使用しますが、手順が複雑になることが多く、マルチタスクを使用すると、時間精度は劣りますが短時間カウントであれば簡単にそれなりに安定した時間カウントを行うことができます。
初期設定は「カウントダウンタイマーのサンプルプログラム」の 64行目のように、マルチタスクの設定を行います。
// マルチタスク(並列処理)設定(カウントダウン処理)
xTaskCreatePinnedToCore(count_task, "count_task", 4096, NULL, 1, NULL, 0);
設定内容の詳細は以下のようになります。
「マルチタスク処理」で実行する関数は「カウントダウンタイマーのサンプルプログラム」の 128〜137行目のように、初期設定で指定した関数名で、以下のように設定します。
// カウントダウン処理(マルチタスク:並列処理)---------------------------------------
static void count_task(void *) {
while (true) { // ずっと実行(並列処理)
delay(10); // 0.01秒カウント
// カウントダウンスタートフラグでカウントが0ではなく、本体ボタンがOFFならカウント実行
if (start_flag == true && count != 0 && digitalRead(BTN_GPIO) == HIGH) {
count--; // 総カウント値 -1
}
}
}
今回は0.01秒のカウントダウンを行う処理に使用しています。
「delay(10)」でカウントしていますが、実際には「if文」等の処理の分、長い時間がかかっています。
このため正確な時間をカウントするには「delay()」の時間調整が必要ですが、正確な時間カウントが必要な場合は「タイマ割込」の使用を検討しましょう。
「タイマ割込」については以下のリンクで詳しく紹介しています。
・PWMでブザー出力(ブザー外付け)
3分のカウントが終了したら外付けのブザーを鳴らすようにしています。
ブザーの制御にはPWM(Pulse Width Modulation)制御を使用しています。
PWMを使用するには「カウントダウンタイマーのサンプルプログラム」の 32〜35行目のように初期設定を行います。
// PWM出力初期設定
pinMode(BZ_GPIO, OUTPUT); // GroveコネクタG2端子出力設定(外付けブザー)
ledcSetup(1, 1000, 10); // PWM初期設定(チャンネル, 周波数Hz, bit数)
ledcWrite(1, 512); // PWM出力開始(チャンネル、Duty比:MAXはbit数の分解能)
3分カウントが終了したら 123行目のようにPWM出力でブザーをONします。
本体ボタン長押しで 95行目 のようにPWM出力を解除しブザーをOFFします。
ledcAttachPin(BZ_GPIO, 1); // ブザーON:PWMチャンネル割り当て(ピン番号, チャンネル)
ledcDetachPin(BZ_GPIO); // ブザーOFF:PWMチャンネル割り当て解除(ピン番号, チャンネル)
PWM制御の使い方については、以下のリンクで詳しく紹介しています。
6.まとめ
「AtomS3」を初めて使用した時に、液晶の色の指定が赤と青で逆になったり「M5GFX」ライブラリが使えませんでしたが、初期設定を見直して解決したので、解決方法について詳しく紹介しました。
「M5GFX」が使えるようになると「日本語表示」や「スプライト(メモリ描画領域一括出力)」が簡単にできるため、とても便利です。
「スプライト」の動作確認用に製作したカウントダウンタイマーでは、高速でカウントの表示を行うため「スプライト」無しでは画面の更新時にチラツキが発生しますが「M5GFX」を使用することでチラツキの発生を気にすることなく、自由度の高い画面作成ができます。
今回、0.01秒のカウントに「マルチタスク(並列処理)」を使用しました。
「マルチタスク」を使用するとメイン処理と並列でカウントすることができるため、メイン処理の遅延時間を気にすることなく、一定のカウントを実行することができます。
時間カウントの精度は「タイマ割込」に劣りますが、簡易的なカウントには簡単に使用できる「マルチタスク」が便利です。
液晶の表示問題も解決したので「ATOMS3」を自由に使えるようになりました。
これから他の機能も試しながら、いろいろなものを作って紹介していきたいと思います♪
コメント