M5StickS3 で「表示を触る」練習として、ランダムに図形を描くスケッチを作ります。
この版では、描画に加えてボタン入力で動作を変えられるようにします。
この記事でやることは次の3つです。
- Aボタン: 描画を速くする
- Bボタン: 描画を遅くする
- A+B同時押し: 画面をクリアする
描画API、ボタン入力、ループ更新の関係を1つのスケッチでまとめて確認できます。
完成コード
#include "M5Unified.h"
// 描画の待ち時間(ms)。Aで速く、Bで遅くする。
static int delayMs = 1000;
static constexpr int DELAY_MIN = 100;
static constexpr int DELAY_MAX = 2000;
static constexpr int DELAY_STEP = 100;
// 現在の操作方法と速度を上部に表示する。
static void drawStatusBar() {
M5.Display.fillRect(0, 0, M5.Display.width(), 22, TFT_WHITE);
M5.Display.setCursor(4, 4);
M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
M5.Display.printf("A: Faster B: Slower\n%d ms", delayMs);
}
// 描画先を引数で受け取り、ランダムな四角形を描く関数。
// LovyanGFX* にしておくと、将来オフスクリーンバッファにも流用しやすい。
void draw_function(LovyanGFX* gfx)
{
// 画面範囲内のランダム座標を作る
int x = rand() % gfx->width();
int y = rand() % gfx->height();
// 画面幅に連動したサイズ(width / 16 + 2)
int r = (gfx->width() >> 4) + 2;
// 16bitカラー値をランダム生成
uint16_t c = rand();
// (x, y) を中心とした四角形を描画
gfx->fillRect(x - r, y - r, r * 2, r * 2, c);
}
void setup()
{
// M5本体(画面・電源管理など)を初期化
auto cfg = M5.config();
M5.begin(cfg);
// 文字サイズを小さめに固定(1が基本最小)
M5.Display.setTextSize(1);
// 毎回違う乱数列になるようシードを設定
randomSeed(micros());
// 背景を白で初期化
M5.Display.clear(TFT_WHITE);
drawStatusBar();
}
void loop()
{
// ボタン状態更新。押下イベントを読む前に毎回呼ぶ。
M5.update();
// A+B同時押し: 描画領域をクリアして描き直しを開始
if (M5.BtnA.isPressed() && M5.BtnB.isPressed()) {
M5.Display.clear(TFT_WHITE);
drawStatusBar();
delay(150);
return;
}
// A: 速くする / B: 遅くする
if (M5.BtnA.wasPressed()) {
delayMs -= DELAY_STEP;
if (delayMs < DELAY_MIN) delayMs = DELAY_MIN;
drawStatusBar();
}
if (M5.BtnB.wasPressed()) {
delayMs += DELAY_STEP;
if (delayMs > DELAY_MAX) delayMs = DELAY_MAX;
drawStatusBar();
}
// ランダムな円を描くための座標・サイズ・色を作る
int x = rand() % M5.Display.width();
int y = rand() % M5.Display.height();
int r = (M5.Display.width() >> 4) + 2;
uint16_t c = rand();
// ランダム円を描画
M5.Display.fillCircle(x, y, r, c);
// ランダム四角を描画(関数経由)
draw_function(&M5.Display);
// ボタンで変更した速度で次の図形を追加
delay(delayMs);
}
コードの見どころ
1. M5.update() が入力処理の起点
M5.BtnA.wasPressed() や M5.BtnB.wasPressed() を正しく使うには、
ループ内で M5.update() を継続的に呼ぶ必要があります。
2. 速度調整は「状態変数」を持つ
delayMs を状態として持っておくことで、
ボタン操作で挙動が変わるUIをシンプルに作れます。
3. 描画処理を関数に分離
draw_function(LovyanGFX* gfx) のように描画処理を関数化すると、
将来バッファ描画や別画面への展開がしやすくなります。
4. 文字の改行
printf の文字列に \n を入れると改行できます。
M5.Display.printf("A: Faster B: Slower\n%d ms", delayMs);
つまずきやすいポイント
- 乱数を使うときに
randomSeed(...)を入れないと、再起動ごとに似たパターンになりやすい -
A+B同時押し判定はwasPressed()よりisPressed()のほうが扱いやすい場面が多い -
delay()を長くしすぎるとボタン反応が鈍く感じる
次に拡張するなら
- Aボタン長押しで超高速モード
- Bボタン長押しで一時停止
- 画面右上に描画カウント表示
- ボタンで図形タイプ(円/四角/三角)を切り替え
描画と入力の基本が1つのスケッチで確認できるので、
この後のUIやセンサー可視化の開発にもつなげやすいです。