本稿、昨日予定していた内容なのですが、思わぬ伏兵に足をすくわれ本日となりました。micro:bitの誇る(?)イベントとリアクティブなシステムの応答の実験です。まあ、普段からお世話になっている仕組みではありますが。外部端子でイベント発生と、別件の処理から「イベントなりすまし」もやってみます。
※「ブロックを積みながら」投稿順 index はこちら
(実験に使用した BBC micro:bitは v1.5、開発環境はオンラインのMakeCodeエディタです)
BBC micro:bit は、「リアクティブな」システムです。何かイベント、外部端子からの入力が発生、などというときに、その時点で処理中の内容を「いったん置いておいて」入力への対応を先に処理したりすることができます。勿論、処理が終われば元に戻って「置いておいた」処理を継続することができます。
裏では割り込みも動いている筈ですが、素のハードウエアの割り込み処理よりも柔軟な対応ができるのは、イベントが発生すると発生元と種別が識別できるメッセージが送られ、特定のメッセージを「リッスン」しているソフトウエアの待ち受けさん達が反応するというメッセージ・バスの仕組みが存在するからです。メッセージバスについての解説文書は以下に
上記の文書は BBC micro:bitといいつつも、JavaScriptレベルではなく、C/C++レベルでの処理の説明でした。ちょっと冷たい感じがするところを引用させていただきます。
If you don’t know what this means, then don’t worry, as that also means you won’t need it. 🙂
まあね、そんな気もしないでもないですが、も少し温かい目で見守ってくれてもいいんじゃないかと思います。読んだ人が発奮(ほとんど死語だが)するのが狙いか?うがちすぎか。。。
実験コード
実験コードで行ってみたのは以下です。
-
- エッジコネクタのP16ピンにスイッチをとりつけ、スイッチが押されたら「立下りエッジ」イベントを発生させる。
- P16ピンの立下りエッジを示すイベントを捕捉したらば、P8ピンに接続しているLEDを光らせる待ち受け処理を設定しておく。また、処理内ではイベントの捕捉回数を積算し、その値をUSBシリアルに報告する。
- foreverループ内ではP16ピンのステータス(0か1)かを毎秒ポーリングし、やはりその値をUSBシリアルに報告する。
- ボタンAが押されたら、さもP16ピンが立ち下がったかのようなイベントを発生させる。識別しやすいように、「ボタンA押された」ということをやはりUSBシリアルに報告しておく。
まず初期化部分、昨日問題であったデフォルトで入力端子のプルダウンがアクティブになっているのを避けるため、P16のプルアップ/プルダウンはnone設定です(外部回路に抵抗ついているので内蔵は要らない。)また、P16は「edge」イベントを発生するように設定しています。この設定だと立上りでも立下りでも発生を検出できるようです。なお、P8に接続してあるLEDはLOWを出力すると点灯なので、初期化時には1(3.3V)出力で消灯としておきます。
実際に、イベントを捕まえる(Listen)のはこちら。外部LEDを点灯し、イベント発生回数を数え、USBシリアルに報告し、100ms待ってからLEDを消灯します。
上の解説文書によると、C/C++では割り込まれたファイバ(軽量スレッド)のコンテキストで処理することも、別なファイバを新たに生成して処理することもでき、また割り込まれた側と並行に処理する設定も、割り込まれた側を一端キューに追いやってペンディングにしてから、などいろいろなチョイスがあるみたいです。ただし、MakeCode上ではそんな選択は見当たりません。実験ではごく短時間の間に発生したはずの複数回のイベントでもドロップせずに処理できているみたいです。デフォルトではイベント処理専用のファイバーで処理、イベントが複数到来してしまった場合、先のイベントをキューに退避させて末尾から順に処理するのではないかと思われます(実機で順序確かめてないです。またそのうち。)とりあえず「しかるべき」設定にはなっている、と。
以下は、ボタンAを使ってP16の立下りを示すイベントを発生させてみた「模擬」実行部分です。ボタンAを押したということ自体イベントですが、その中でさらにイベントを送信してます。イベントバスの威力?
実機上での実行結果
以下に実機での実行結果(USBシリアルにprintされた結果)を示します。PIN16:1というのは、foreverループでのポーリングの結果です。1はボタンが押されていない状態です。毎秒1回のポーリングなのでボタンをチョコッと押したくらいでは、なかなか0という値は見えないです。
evCount:N(Nは整数)
という表示はイベント発生時に現れます。その時点までの累積のイベント発生回数です。1回、2回連続、3回連続などの例がありますが、実際に人間がスイッチを押しているのはすべて1回でチョコっとのつもりです。2回、3回などはスイッチのチャタリングを拾っているからだと思います。チャタリングが嫌なら何か処理を考えないとならないです。逆に言えば、チャタリングくらいの時間幅で発生する事象でもイベントバスで捕捉できるということでもあります。
同じevCount値でも、直前の行に BUTTON A と書かれているのはBUTTON Aで発生させた「偽の」イベントです。BUTTON A pressed はチャタリングに無縁なので、確実に1回のイベントを送りこんで来ています。
PIN16:1 evCount:2 PIN16:1 evCount:3 evCount:4 PIN16:1 ~途中略~ PIN16:1 evCount:14 evCount:15 evCount:16 PIN16:1 ~途中略~ PIN16:1 BUTTON A evCount:19 PIN16:1 BUTTON A evCount:20 PIN16:1 BUTTON A evCount:21 PIN16:1
ハードを動かすときは、このイベント・バスと前々回のバックグラウンド実行を駆使するつもり、と。本当か?
ブロックを積みながら(32) BBC micro:bit、digitalReadPin へ戻る
ブロックを積みながら(34) MakeCode、Simulator、使える?使えない? に進む
control.onEvent(EventBusSource.MICROBIT_ID_IO_P16, EventBusValue.MICROBIT_PIN_EVT_FALL, function () { pins.digitalWritePin(DigitalPin.P8, 0) evCount += 1 serial.writeValue("evCount", evCount) basic.pause(100) pins.digitalWritePin(DigitalPin.P8, 1) }) input.onButtonPressed(Button.A, function () { serial.writeLine("BUTTON A") control.raiseEvent( EventBusSource.MICROBIT_ID_IO_P16, EventBusValue.MICROBIT_PIN_EVT_FALL ) }) let evCount = 0 serial.redirectToUSB() evCount = 0 pins.digitalWritePin(DigitalPin.P8, 1) basic.clearScreen() pins.setPull(DigitalPin.P16, PinPullMode.PullNone) pins.setEvents(DigitalPin.P16, PinEventType.Edge) basic.forever(function () { led.toggle(0, 0) serial.writeValue("PIN16", pins.digitalReadPin(DigitalPin.P16)) basic.pause(1000) })