スチームパンク風ゴーグルにLEDマトリクスを仕込んで、ライブの小道具として使おうという計画。ハードウェアは完成して、残すはプログラムだけなのに、そこで問題が発生してしまい、11月のライブには間に合わなかったのであった。
ちなみに次のライブは1月22日。さてそちらには間に合うのだろうか?
トラブルの原因はたぶん、俺が書いたタイマー割り込みの表示制御プログラムと、LEDを制御するライブラリ(こちらも割り込みを使ってる)がバッティングしてしまい、正常に動作していないこと。
一口にArduino IDE互換マイコンといっても仕様は様々で、特にタイマー割り込みについては、心臓部のマイコンによって方言があるらしい。今回使ってるM5Stamp Pico(ESP32マイコン)は、一般的なArduino(AVRマイコン)とは違うんで、その辺りを考慮しなきゃならなかったんだと思う。
もちろん、そんな方言を覚えることに時間を費やす気はサラサラないので、これ以上の追求は行わず逃げ道を探すことにするわけだが。
割り込みを使わないで一定時間ごとに処理
そもそもタイマー割り込みを使おうとしていたのは、LEDの表示やアニメーションという一定時間ごとに必要な処理をバックグラウンドで行って、WiFiリモコン周りを並行して処理したいという理由からだ。割り込みを使わないんだったら、タイマーを監視して、一定時間の経過を判定して処理するしか方法はない(と思う)。
幸い、Arduinoの標準ライブラリには「Millis()」という、常に動いているカウンタータイマーが存在するので、それを使って監視すればいい…のだけれど、もっと使いやすそうな「elapsedMillis」というライブラリを見つけたので、そちらを使うことにする。Millis()をベースに、個別のタイマーを定義できるライブラリだ。これを使って、一定時間ごとにフラグを立てる関数を作ってみた。
// elapsedMillisを使い、一定時間ごとにフラグを立てるタイマー // 引数が0ならリセット // 引数が0以上なら、リセット後にその時間[ms]だけ経過しているかを判定 // 経過していたらtrueを返してタイマーをリセット elapsedMillis time_elapsed; boolean elapsed_timer(unsigned long count) { if(count == 0) { time_elapsed = 0; return(true); } else { if (time_elapsed > count) { time_elapsed = 0; return(true); } else { return(false); } } }
loop()の中に、WiFi入力処理と、この関数でフラグが立ったら呼ぶ処理(表示周り)とを並べて入れておけば、どちらも並行して処理できるって寸法だ。
というわけで、まずはWiFiではなく、ボタン入力で切り替えるバージョンを作ってみた。表示をアニメーションさせながらボタン入力も受け付けている。
うん、首尾よく動いてるわ。あとはボタン入力部分を、WiFiコントロールに差し替えればオールオッケーだ。
M5Stamp PicoをWiFiアクセスポイントに
M5Stamp Picoは「WiFiアクセスポイント」として動作させることができる。つまり、スマフォを直接接続して通信することができるわけだ。そのためのサンプルプログラムが「スケッチ例」にあるので、とりあえず動かしてみる。
そうそう、書き忘れていたけれど、M5Stamp Picoを使うにあたってボードは「M5Stack-ATOM」として動かしている。STAMP-PICOを選んでいると、たまにコンパイルの途中で戻ってこなくなるのだ。ネットで調べた限りでは、ATOMはPICOとほぼ同じ構成なので、これで問題はないらしい。
というわけで「M5Stack-ATOM用のスケッチ例」から「WiFiAccessPoint」を動かしてみた。
うん、アクセスポイント、ちゃんと立ってるわ。バリ3だ。
もちろん、実際に何かをする場合は、アクセスポイントだけじゃダメで、サーバーも立てる必要がある。そのあたりも当然スケッチ例に含まれているので、いちいち動かして確認しながら、自分のやりたいことに合わせて改造していくわけだ。
// M5Stamp PicoをWiFiアクセスポイントにする // スケッチ例「WiFiAccessPoint」をベースに作成 // 接続した後、ブラウザで192.168.4.1(ポート80)に接続すると、リモコンページが表示される // WiFiAccessPoint用 #include <WiFi.h> // アクセスポイントのSSIDとパスワードは別ファイル #include "MyAccess.h" const char *ssid = PICOSSID; const char *password = PICOPASS; // サーバー作成 // PORTNUMは別ファイルに記載 WiFiServer server(PORTNUM); void setup() { // いつもの Serial.begin(115200); // WiFiアクセスポイント・サーバーの初期化 Serial.println("Configuring access point..."); WiFi.softAP(ssid, password); IPAddress Ip(192, 168, 4, 1); // Server IP を固定 IPAddress NMask(255, 255, 255, 0); // Server サブネットマスクを固定 WiFi.softAPConfig(Ip, Ip, NMask); // Ip, Gateway, SubNetMask IPAddress myIP = WiFi.softAPIP(); Serial.print("Access Point IP address: "); Serial.println(myIP); server.begin(); Serial.println("Server started"); // WiFi初期化はここまで } // クライアントに操作ボタンを表示するページのHTML const char html_buttons[] = "<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>\ <style>.btn{font-size: 900%;</style>\ <title>LED Goggles</title></head>\ <body><center><form method='get'>\ <input type='submit' name='vi' value='V' class='btn' />\ <input type='submit' name='la' value='笑' class='btn' />\ <input type='submit' name='an' value='怒' class='btn' />\ <input type='submit' name='cr' value='泣' class='btn' /><br>\ <input type='submit' name='ra' value='虹' class='btn' /><br>\ </form></center></body></html>"; void loop() { // クライアントからの接続処理 // ほぼスケッチ例「WiFiAccessPoint」のまま WiFiClient client = server.available(); // クライアント接続を監視 if (client) { // クライアント接続があったら Serial.println("New Client."); String currentLine = ""; // クライアントからのメッセージ while (client.connected()) { // 接続中 if (client.available()) { // クライアントからの受信があれば char c = client.read(); // 1バイト受信 // Serial.write(c); if (c == '\n') { // '\n'に到達 if (currentLine.length() == 0) { // 受信終了なら、クライアントに送信 client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println(); client.print(html_buttons); client.println(); break; } else { currentLine = ""; } } else if (c != '\r') { // 引き続き受信 currentLine += c; } // クライアントからsubmitされたボタンを判定 if (currentLine.endsWith("GET /?la")) { Serial.println("笑"); } if (currentLine.endsWith("GET /?an")) { Serial.println("怒"); } if (currentLine.endsWith("GET /?cr")) { Serial.println("泣"); } if (currentLine.endsWith("GET /?vi")) { Serial.println("V"); } if (currentLine.endsWith("GET /?ra")) { Serial.println("虹"); } } } client.stop(); // 接続終了 Serial.println("Client Disconnected."); Serial.println(""); } }
できたできた。
スマフォでM5Stamp Picoのアクセスポイントに接続してブラウザを開けば、操作ページのHTML(ソース内に記述)が表示される。ボタンを押せばPicoの方で検知できるという流れだ。
うんうん、動作も想定通り。ちなみに、スケッチ例では「WiFiClient.h」と「WiFiAP.h」をインクルードしていたけど、使っていないようだったので削除しておいた。
あとはこれを、先に作った「ボタン入力バージョン」のボタン入力部分と入れ替えるだけだ。
ついに完成! だが。
さて、プログラムを合体させてみたが、動作はどんなもんだろう?
ちゃんと動いてるじゃん!
FastLEDライブラリとWiFiライブラリがバッティングしていないかだけが心配だったけれど、どうやらうまく共存できているっぽい。スマフォ側からボタンを押せば、ゴーグルの表情はしっかり変わる。完成だ! ライブに間に合ったぜ!
――ところが。
1月22日に予定していたライブ…メンバーが2人もコロナに罹ってしまい、急遽キャンセルになってしまったのでした。
せっかく完成はしたけれど、残念ながらお披露目は延期。
でもまあ仕方がない。次のライブ、早く決めるとしよう。それまでに、表情の数を増やしたりと、さらにグレードアップさせとこうかな!
(たぶん、終わり)