前々回、M5StickC Plusの開発環境として、いくつか選択できる中から、UIFlowと言う開発環境を立ち上げてみました。
Arduino IDEに戻ってしまうと、そこから改めてUIFlowに戻るためには、ファームウェアを再度書き込む必要があって面倒なので、暫くUIFlowで使って見ようと思います。
今回UIFlowで作るのは、IRリモコンです。
IRリモコンについては、以下の記事を参照してください。
「#3-9 キッチンタイマを作る」
キッチンタイマの時間を設定するUIとしてIRリモコンを使いました。
当時、手元にあった色々なリモコンの送信波形を解析してみました。各社、独自規格のリモコンも多いことが判ったのですが、デファクトとなっているNEC方式のリモコンも多く見つかりました。
今回作るIRリモコンは、NEC方式(下図)のデータを送信することにします。
UIFlowで探してみたところ、IR(NEC) sendブロック(下図)が見つかりました。
早速M5StickC Plusで使って見たのですが、リモコンで操作する機器が反応せず、IR LEDも光りません(CMOSセンサカメラで確認)。どうも、ブロックが機能していない様子です。
M5StickC PlusオンボードのIR LED用ではなく、外付けユニット(HAT)用のブロックなのか?さもなくば、UIFlowファームウェアのバグです。
これが使えたら、もっと簡単に実装できたのに……。
IR(NEC) sendブロックが機能しなかったため、やむを得ず、自前で送信関数を作って対処しようと考えました。この時、課題が2つありました。
以降、2つの課題を解決して行きたいと思います。
※そう言えば、ESP32には赤外線リモコン用のユニットが搭載されていたような気がします……。だとしても、UIFlowでESP32のレジスタを直接叩くのは無理なので、忘れます。
こちらで図示した通り、IR LEDを発光させるキャリア周波数は38kHzで、デューティ1/3です。M5StickC Plusでは以下図の通り、GPIO9にIR LEDが接続されているので、周波数、デューティ、出力端子を指定してPWMを出力できる必要があります。
※M5StickC Plus回路図から引用
UIFlowでは、以下図に示すPWMブロックで可能です。
なお、上図の回路だと、I/Oポート(GPIO9)がLOWを出力(シンク)した時に光ります。IR LEDをデューティ1/3で光らせるためには、PWMのデューティは2/3とする必要があることに留意してください。IR LEDのON/OFF制御は、以下の様にします。
Pause PWMブロックでPWMを停止すると、LOWレベルで停止するみたいです。IR LEDが発光し続けてしまうので、デューティでON/OFFを制御します。
こちらの図に示した様に、NEC方式では基準時間T=562.5μsを最小単位として、赤外線をON/OFFすることでデータを送受信しています。
一方、UIFlowで取得可能なタイマ、Get ticks msブロックでは、ミリ秒単位の時間しか取得できません。Tは約0.56ms(ミリ秒未満)なので、一息足りないのです。多少の誤差は許容されるとしても、ある程度正確に時間Tを計測する必要があります。
※C言語(Arduino)では、ミリ秒単位のmillis()関数より解像度の高い、micros()関数があるので憂いなし、なのですけど。今はUIFlowですから。
そこで知恵を絞り、以下図の様なPWMを出力しておき、他のI/OポートからPWM出力レベルを読み取ってポーリングすることで、ミリ秒未満の時間T=562.5μsを計測しようと考えました。
上のPWMも、PWMブロックで作ることができます(下図)。
IRキャリア波形にも、異なる周波数のPWMを使うので、2つのPWMは、別なタイマチャンネルで生成しなければなりません。PWMブロックの「use timer」パラメータで選択できるタイマはチャンネル0と3だけでしたので、以下の通りに割り当てて使う事としました。
NEC_T PWM出力をサンプリングするためのI/Oポートが別途必要です。普通は、2個のI/Oポートをジャンパ線などでショートして使います。
今回は、M5StickC Plusの拡張ポート(上部ピンソケット)でショートされているGPIO25(G25)とGPIO36(G36)を使います。ボード上でショートされているので、別途ジャンパ線などでショートする手間が省けます(下図)。
この2本がショートされているのは、デジタル系のGPIO25と、アナログ入力(AD変換器)可能なGPIO36を、本数の限られた拡張ポートに両方共、引き出すための苦肉の策だったものと思われます。M5StickC Plusの公式HPには、「両方同時に出力するな。一方は入力HIGH-Zに設定して使え」などの注意書きがあります。
今回の用途では、GPIO25にPWM出力、GPIO36からレベルを読み出すため、GPIO36側を入力HIGH-Zで使う事になりますので、問題ありません。
尚、GPIO36は普通のデジタル入力ポートで良かったのですが、UIFlowのブロックではデジタル入力ポートに設定できませんでした。……GPIO36って、デジタル入力できないポートなのかな?「GPIO」って言うくらいだから、そんなハズは無いのだけど。
GPIO36にはAD変換器の入力端子が割り当たっているので、AD変換器を使って、アナログ値として端子レベルを読み出すことにしました。以下にまとめます。
GPIO36端子から、アナログ値を読み出すのに、2つの選択肢がありました。
どちらを使っても同じ結果が取得できることが期待ですが、念のため確認してから使おうと思います。以下の様なテスト用のプログラムをUIFlowで作成しました。
実行結果だけ、以下にまとめます。
何で違うんだろ?同じGPIO36でサンプリングしていて、他の条件も全て同じなのに。しかも、analog read pinブロックの方は、フルスケールが読み出されるべき状況で317LSBが読み出されました。2冪数でさえない値って、何なのだろう? → analog read pinブロックの動作は怪しいですね。
変換時間も、ADC read valueブロックの方が速いので、ADCブロックを使う事に決めました。確認しておいて良かったです。うっかりanalog read pinブロックの方を使っていたら、嵌まっていました。
ADC read valueブロックは、1サンプリング当たり約70μsであるのに対して、計測したい時間はT=562.5μsなので、時間Tの間に約7〜8回前後サンプリングできます。サンプリングレートは低いし、十分ではないけれど、何とかなるかな?と言うレベルです。
普通のリモコンは、送信ボタンを押し続けている間、「リピート」と言うコードを送信します。今回のアプリケーションでは、スイッチAを押した時に1回だけ、データを送信します。リピートは送信しません。
IR(NEC)送信関数を以下図のように組み立てました。
16ビットデータの送信関数ir_send_16bitから始まって、1ビットデータの送信関数ir_send_1bitでキャリアPWMをON/OFFしながら、所望の波形を生成しています。波形生成に必要な基準時間T=562.5μsは、関数ir_wait_0および1で、NEC_T PWMのレベルをポーリングしながら計測しています。
上図では省略しましたが、この更に上位に、「リーダ」や「ストップビット」などを送信する関数ir_send_necが被さっています。後ほどリンクするプログラムをUIFlowで読み込んで、ご確認ください。
TFTディスプレイにメニュー項目を並べておいて、送信するデータを選択できるようにしたいと思います(下図)。
上図のlabel7ですが、y座標=204を指定してセーブするのだけど、ロードし直すとy=203に変化しています。UIエディタのバグです(執筆時点)。折角、等間隔で綺麗に並べているのに、1ドットでもずれると気になるんだよ〜〜。
※上図でlabel6とlabel7の間隔が1ドット狭いことに気が付かなかった方には、関係の無い話です(1^^;)。
一応、当方手持ちのリモコンが送信するデータが、サンプルとしてリストに設定されていますが、皆様のお手元に同じ機器があるとは限りません。皆様お手元のリモコン機器に合わせて、必要なデータを登録してお使いください。
該当箇所は、以下の関数(def_code)です。
お手元のリモコンに於いて、どのボタンがどんなコードを送っているか知りたい場合は、こちら「#3-9 キッチンタイマを作る」の装置とプログラム(test_ir_recv.ino)を使って調べてください。ただし、お使いのリモコンがNEC方式の場合に限ります。
プログラム(test_ir_recv.ino)では、LSBファーストで受信したコードをそのまま出力しているので、ビット順が逆転しています。UIFlowの方に、ビット順を逆転させる関数rvsを作っておいたので、これを使うことができます(上図にサンプルが有ります)。
完成したUIFlowのプログラムを以下にリンクします。
完成プログラム:ir_send_nec.m5f
IR(NEC) sendブロックが使えなかったので、ちょっと遠回りしてしまいました。UIFlowを使い込んでくると、色々と粗が目に付くようになってきました。
前節のプログラム(ir_send_nec.m5f)をUIFlowで読み込んで、M5StickC Plusで実行してください。以下、実験中の様子です。
以下の様な動作を確認しました。
手元の機器では、問題なく動作しました。
今回確認しただけでも以下の様な不具合がありました(執筆時点)。
この場で全て挙げることはしませんが、他にも動きの怪しい挙動を多数観測しています。本当に「多数」です。
UIFlowに関しては、まともなドキュメントが見当たらないので、「仕様」なのか、「バグ」なのかでさえ、判断付きません。少なくとも、「(当方で)期待した動作ではない」ケースが、多数確認されています。
まあ、「メチャクチャ酷い」ことには、なっていないので、言語(Python)自体は正常なのでしょう。どうせ、オープンソースで揉まれたコードを拾って来たのだろうから、言語本体は、ね。ただ、M5StickC Plus等、機種毎に拡張された部分のデバッグが甘い様です。これもまた、悪い意味の中華品質🀄ですね。
この手の開発環境は、バグにぶち当たると、もう、どうしようもないので諦めるしか手がありません。さもなくば、M5StickC Plusに書き込んだファームウェアのソースリストを探してきて、自分でデバッグして正す事になります。
※と言っても、GPL汚染していない場合、ソースリストの開示義務が発生しない場合があるので、公開されていないケースもあります。その場合は、ありのまま使うしか無いです。
この状況も含めてUIFlowは、やっぱり大規模なプログラムを組むには適さない環境と言わざるを得ません。ブロックを使ったプログラミングは楽しいのですけどね……(1;o;)。
UIFlowは、プログラムの規模が大きくなってくると辛すぎます。回避不可能なバグにぶち当たって断念する羽目に合うのも好まないので、そろそろArduino IDEでの開発に戻ろうと思います。