M5Stack開発について理解を深めたくて、Arduino IDEで見られるスケッチ例を読解していく。
今回は、スケッチ例「Button」を読解して、M5Stackのボタン操作について見ていく。
Arduinoスケッチの基本構造や、M5Stackで画面に文字表示する基本のやり方については、以前「Hello World」のスケッチ例を題材に整理しているのでそちらをご参照ください。
スケッチ例「Button」
Arduino IDEのメニューから [ファイル] > [スケッチ例] > [M5Stack] > [Basics] > [Button] を選択。
M5Stackに書き込んでみる。
すると、最初にディスプレイ上に黄色の文字でこのように表示される。
この状態で下の3つのボタンをどれか押すと、押したボタンに対応してA〜Cの文字がディスプレイ上にタイプされる。
また、ボタンを長押しすることで一定間隔で同じ文字が連続タイプされる。PCのキーボードを長押ししている時と同じような挙動。
そして真ん中のBボタンを長押し(700ミリ秒≒1秒弱)押すと、画面がリセットされて真っ白になる。
この白い画面の時にも、押したボタンを表示したり、再度Bボタン長押しで画面リセットをすることができる。
……というのが、ボタン制御のサンプル。
スケッチを読む
コメントなどは省略している。
▼スケッチ例「Button」
#include <M5Stack.h> void setup() { M5.begin(); M5.Power.begin(); M5.Lcd.setTextColor(YELLOW); M5.Lcd.setTextSize(2); M5.Lcd.setCursor(65, 10); M5.Lcd.println("Button example"); M5.Lcd.setCursor(3, 35); M5.Lcd.println("Press button B for 700ms"); M5.Lcd.println("to clear screen."); M5.Lcd.setTextColor(RED); } void loop() { M5.update(); if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000, 200)) { M5.Lcd.print('A'); } else if (M5.BtnB.wasReleased() || M5.BtnB.pressedFor(1000, 200)) { M5.Lcd.print('B'); } else if (M5.BtnC.wasReleased() || M5.BtnC.pressedFor(1000, 200)) { M5.Lcd.print('C'); } else if (M5.BtnB.wasReleasefor(700)) { M5.Lcd.clear(WHITE); M5.Lcd.setCursor(0, 0); } }
setup()
関数内では、M5の初期化やディスプレイへの文字表示などを行なっている。ここは今回は省略する。
loop()
関数内でボタン操作周りを行なっているので、ポイントを整理していく。
M5Stackのボタン制御の基本
まず前提として、M5Stack本体のディスプレイの下部には3つのボタンがついていて、左から「A, B, C」と名前がついている。
そしてM5ライブラリには各ボタンに対応したBtnA
, BtnB
, BtnC
クラスがあり、それぞれM5.BtnA
, M5.BtnB
, M5.BtnC
のように呼び出して使う。
ではloop()
関数の中を見ていく。
ボタンの状態のアップデート
M5.update()
ボタンの読み取り状態を更新する関数。
ボタンを扱うには、基本的にはこれをloop()
関数の先頭(もしくは末尾)に書く必要がある。
試しに実際にM5.update()
の一文をloop()
関数内に記述せずに書き込むと、ボタンを押してもどれも反応しない状態になった。
ボタンの状態によって条件分岐を行う
loop関数内では、ボタンの状態によって条件分岐が行われている。
if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000, 200)) { M5.Lcd.print('A'); } else if (M5.BtnB.wasReleased() || M5.BtnB.pressedFor(1000, 200)) { M5.Lcd.print('B'); } else if (M5.BtnC.wasReleased() || M5.BtnC.pressedFor(1000, 200)) { M5.Lcd.print('C'); } else if (M5.BtnB.wasReleasefor(700)) { M5.Lcd.clear(WHITE); M5.Lcd.setCursor(0, 0); }
これは、
- ボタンAが押されたら、画面上に「A」の文字をタイプする
- ボタンBが押されたら、画面上に「B」の文字をタイプする
- ボタンCが押されたら、画面上に「C」の文字をタイプする
- ボタンBが700秒長押しされたら、画面をクリアして白一色にする(再度画面左上から印字をスタートする)
というような処理が行われている。
(M5.Lcd.print()
関数は、前回のprintの続きから行われるので、キーボードでタイプしてPCに出てくる時のような表示になる。)
ボタンの状態は条件分岐で判定しているのだけど、ちょっとどう使われているのかわかりづらいので、関数を一つずつ見てみることにする。
シンプルに、ボタンAだけを見ていこうと思う。
ボタンの押下を感知するだけなら
M5.BtnA.wasReleased()
ボタンAが(押された状態から)離した瞬間に一度だけ1を返す関数。
日本語のドキュメントにはwasReleased
がなかった。なぜだろう……。
以下英語のドキュメント。
例えば、シンプルにボタンAを押したかどうかを判定したいだけなら、loop関数はこのようなスケッチで良い。
void loop() { M5.update(); if (M5.BtnA.wasReleased()) { M5.Lcd.print('A'); } }
書き込んで試してみると、ボタンAを押すと文字Aがタイプされる。
ただこれだと長押しに対応していない。ボタンを話した瞬間に1回だけ、文字Aが表示される。
今回のスケッチ例には出てこないけど、こちらのwasPressed()
関数も使える。
wasReleased()
との違いは、ボタンを押した時に検知するか、押したボタンを離した時に検知するかということ。
M5.BtnA.wasPressed()
ボタンAが押される度に一度だけ1を返す関数。
ボタンが長押しされたことを検知するなら
M5.BtnA.pressedFor(長押し時間)
M5.BtnA.pressedFor(長押し時間, ディレイ時間)
ボタンAが引数で指定した時間以上ボタンが押し続けられたら1を返す関数。
引数には、長押し時間をミリ秒で指定する。
また第2引数にはディレイ時間を指定することができるみたい。
(ドキュメントには見当たらなかった)
先ほど試したloop関数のif条件式をこのようにしてみる。
void loop() { M5.update(); if (M5.BtnA.pressedFor(1000)) { // 引数に長押し時間 M5.Lcd.print('A'); } }
ボタンAを1秒以上押すと、文字Aがタイプされるスケッチができた。
ただこれを書き込んで試してみると、ボタンAを長押しした瞬間に「AAAAA……」と一気に画面いっぱいに文字がタイプされてしまう。
これを防ぐために第2引数のディレイ時間を指定することで、長押ししている間は一定間隔で文字Aがタイプされるようになる。
void loop() { M5.update(); if (M5.BtnA.pressedFor(1000, 200)) { // 第2引数にディレイ時間 M5.Lcd.print('A'); } }
長押しに関しては、こちらのwasReleaseFor()
関数も使える。
pressedFor()
とは、長押ししている途中に検知されるか、長押しから離した瞬間に検知されるか、というちょうど対になっているものかな?
M5.BtnA.wasReleaseFor(長押し時間)
ボタンAを引数で指定した時間以上押された状態から離した時に1を返す関数。
PCのキーボードのような挙動を作るには
これまでの内容を組み合わせると、スケッチ例のようにPCのキーボードのような挙動を作ることができる。
void loop() { M5.update(); if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000, 200)) { // ボタンAを離した時 or 1秒以上押し続けた時 M5.Lcd.print('A'); } }
まとめ
ボタンの制御もなかなか複雑だった。
ドキュメントが揃ってなかったりして、うまく整理できなかったかも。