micro:bit MakeCodeプログラミング

2022.5.11  2021.11.11 Coskx Lab  

1 はじめに

micro:bit MakeCodeでプログラムを作成していて,ちょっと複雑なプログラムを作るときに,不思議な動作をすることあります。
それは,Micro:bitの舞台裏の仕組みに依存するものです。
ここでは,

の2点について観察してみます。次に,

についても観察してみます。
参考  https://makecode.microbit.org/device/reactive

2 使用環境

3 「[ずっと]サブプログラム」は一時停止20msを伴っている

「ずっと」ブロックのことを,makecode.microbit.orgの解説記事に合わせて,「[ずっと]サブプログラム」と呼ぶことにします。
[ずっと]サブプログラムの実行では,「ずっと」ブロック内に記述されたプログラム通りに繰り返しが行われているように見えます。
しかし,毎回の繰り返し動作の直後に,20msのpause(一時停止)が舞台裏で挿入されて動作しています。
また,6ms毎のタイマー割込みによって,ボタン押し下げなどを監視するプログラムも,舞台裏で動いています。

この動作を確認するために,[ずっと]サブプログラムだけでできている次のようなプログラムを動かしてコンソール表示を観察しました。
このプログラムではmicro:bit稼働時間を読みだして,1回の繰り返し動作にかかる時間を測定しています。

プログラム 3.1 



プログラム 3.1 のループは無限に続きますが,その中の何回かのループ部分を切り取ると,次の通りになりました。
1回の繰り返し動作は24msであることがわかりました。
20msのpause(一時停止)を含んでいるはずなので,[ずっと]サブプログラム内部の記述の実行は4msより短い時間で行われているようです。

:
170, 24  ←稼働時間[ms], インターバル[ms]
194, 24
218, 24
242, 24
266, 24
290, 24
314, 24
338, 24
:

次に,表示を2回行うようにしてみました。

プログラム 3.2 



プログラム 3.2 のループも無限に続きますが,その中の何回かのループ部分を切り取ると,次の通りになりました。
1回の繰り返し動作は28msであることがわかりました。
20msのpause(一時停止)を含んでいるはずなので,[ずっと]サブプログラム内部の記述の実行は8msより短い時間で行われているようです。
[ずっと]サブプログラムでは,ほとんどが通信に時間が使われていることがわかります。

:
7790, 28  ←稼働時間[ms], インターバル[ms]
7790, 28
7818, 28
7818, 28
7846, 28
7846, 28
7874, 28
7874, 28
:


上記2つプログラムの動作を比べてみます。プログラム 3.1 においてもし,[ずっと]サブプログラム内に記述された作業だけで24msを費やしていて舞台裏の20msのpauseが無かったとしたら,プログラム 3.2 の[ずっと]サブプログラム内に記述された作業は28msよりずっと長い時間を費やすはずです。4msしか増加しなかったことで,20msの舞台裏のpause(一時停止)の存在が確かめられたと思います。

プログラム 3.1 が時間とともに動作している様子は次のように考えられます。



4 「[ボタンAが押されたら]サブプログラム」がAPを占有する

   AP : Application Processor,micro:bitの頭脳

[ずっと]サブプログラムと,「[ボタンAが押されたら]サブプログラム(以降[ボタンA]サブプログラムと表現)」で出来ているプログラムを動作させます。
[ボタンA]サブプログラム中には時間稼ぎのための無意味な1000000回のループがあります。
プログラム 4.1 では[ボタンA]サブプログラムは3.4秒ほどAPを占有してしまい,その間[ずっと]サブプログラムの起動を止めてしまうことを確かめます。

プログラム 4.1 



プログラムが起動し,24ms毎の[ずっと]サブプログラムが動作しているときに,ボタンAを押した時の様子を次に示します。
「onbuttonA start」が表示された後,[ずっと]サブプログラムはしばらく起動せず,3.4秒後にようやく起動し,「7411, 3393」を表示してそれ以降は通常動作に戻っています。
表示された3393msから,[ずっと]サブプログラムが起動できなかった時間が,3.4秒だとわかります。

:
3946, 24  ←稼働時間[ms], インターバル[ms]
3970, 24
3994, 24
4018, 24
onbuttonA start
7411, 3393
7438, 27
7462, 24
7486, 24
:


[ずっと]サブプログラムは,24ms周期繰り返しの永久に終わらないサブプログラムです。作業中はAPを占有していますが,1回の占有時間は短く,4ms程度です。 それに続けて20msのpause(一時停止)があるため,pauseが開始されたときにAPの占有をやめます。

6msごとに起動するボタンなどを監視するプログラムによって,ボタンAが押されたことが確認されると,[ボタンA]サブプログラムが起動しようとします。
この時2つの事態が起こる可能性があります。
(1)運悪く[ずっと]サブプログラムの4msの作業中であれば,[ボタンA]サブプログラムは起動を待たされます。[ずっと]サブプログラムがpauseに入ったとき,[ボタンA]サブプログラムは起動できます。
(2)[ずっと]サブプログラムの20msのpauseの期間中だったら,[ボタンA]サブプログラムはすぐに起動できます。

[ボタンA]サブプログラムは起動したらAPを3.4秒間占有します。
[ずっと]サブプログラムは20ms休んだら作業を開始しようとしますが,APは[ボタンA]サブプログラムによって占有されているため,[ボタンA]サブプログラムがAPを明け渡すまで([ボタンA]サブプログラムが終了するまで)待たされます。
このようにそれぞれのサブプログラムは,自分の都合で起動しようとしますが,他のサブプログラムによってAPが占有されていると起動を待たされます。

プログラム 4.1 が時間とともに動作している様子は次のように考えられます。


次のプログラム 4.2 では,[ボタンA]サブプログラムは実行中に10msのpause(一時停止)を行います。pause時には,[ボタンA]サブプログラムはAPの占有をやめるため,待たされていた[ずっと]サブプログラムが起動しているところが見られます。

プログラム 4.2 



[ボタンA]サブプログラムが途中で10msのpauseを行うので,そのすきをついて[ずっと]サブプログラムが起動しているのがわかります。再び[ボタンA]サブプログラムがAPを占有するので,[ボタンA]サブプログラムが終了後まで待って,[ずっと]サブプログラムが再び起動します。

:
6206, 24  ←稼働時間[ms], インターバル[ms]
6230, 24
6254, 24
6278, 24
onbuttonA start
9802, 3524
onbuttonA start 2
13310, 3508
13334, 24
13358, 24
13382, 24
:

[ボタンA]サブプログラムの途中に1msのpauseがあるため,そのタイミングで待たされていた[ずっと]サブプログラムは1回だけ起動が可能でした。
もし[ボタンA]サブプログラムの途中のpauseが50msあったら,[ずっと]サブプログラムは3回起動が可能です。

プログラム 4.2 が時間とともに動作している様子は次のように考えられます。


5 2つの「[ボタンが押されたら]サブプログラム」がAPを取り合いする

[ボタンA]サブプログラムがAPを占有して,「[ボタンBが押されたら]サブプログラム(以降[ボタンB]サブプログラムと表現)」の起動を阻止している様子を確認します。
次のプログラムを使います。(このプログラムには,[ずっと]サブプログラムは記述されていません)

プログラム 5.1 



次の実行結果は,ボタンAのみ押して,ボタンBを押さなかったときのものです。
[ボタンA]サブプログラムは起動しますが,[ボタンB]サブプログラム」は起動しないので,変数loopstatusは1のままです。
「くりかえし1000000回」ブロックは繰り返しを最後まで行うので,繰り返しを抜け出したとき,変数countは1000000になっています。
なお[ボタンA]サブプログラムは起動から終了まで3.5秒ほどかかっています。

Hello
onbuttonA start
loopstatus = 1
loopstatus = 1
count = 1000000
onbuttonA terminated

次に,ボタンAを押して,[ボタンA]サブプログラムが終了する前(起動後3.5秒より前)にボタンBを押したときの実行結果を示します。

Hello
onbuttonA start
loopstatus = 1
loopstatus = 0
count = 1000000
onbuttonA terminated

この実行結果を解釈するには,もう少し説明が必要です。
これまで,サブプログラムがAPを手放すのは,サブプログラムが終了するか,pauseになったときでした。pause以外でも,時間のかかる処理で舞台裏で処理が終了するまで待っている時間もAPを手放します。
[ボタンA]サブプログラムでは,「シリアル通信 1行書き出す ・・・・」は時間がかかるため,通信終了までAPを手放します。
「くりかえし1000000回」ブロック作業中にボタンBが押されると,[ボタンB]サブプログラム」が起動しようとしますが,[ボタンA]サブプログラムがAPを占有しているため,待たされます。
[ボタンB]サブプログラム」は起動しないため,loopstatusを0にすることはできず,「くりかえし1000000回」ブロック中の「くりかえしを終わる」という記述は使われません。
そして,1000000回繰り返し作業を終了したときに,「シリアル通信 1行書き出す ・・・・」でloopstatusを出力しますが,このときはまだloopstatusは1なので,1が表示されます。
この1を出力する作業中に,[ボタンA]サブプログラムは待たされ,APを手放します。そのときに[ボタンB]サブプログラム」が起動し,loopstatusを0にします。
待たされる原因となったシリアル出力作業が終わり,[ボタンB]サブプログラム」も終了すると,APが使えるようになるため,[ボタンA]サブプログラムは続きの作業を行うことができます。
2回目のloopstatusの出力では0を表示します。
countも1000000になっているので,1000000が表示されます。

[参考] 1行の文字列送る通信は,人間にとっては非常に短時間で行われているように見えます。しかし,APにとっては非常に遅いので,通信時間中に別のことができるので,通信中はAPを明け渡すようになっています。その他遅い作業としてはLED画面への表示などがあります。1文字表示などは人間にとってもゆっくりした動作なので,APにとっては非常に長い時間待たされることになります。

次のプログラムでは,[ボタンA]サブプログラムの繰り返し作業中にpause(一時停止)1ms が挿入されています。

プログラム 5.2 



ボタンAを押して,[ボタンA]サブプログラムが終了する前(起動後3.5秒より前)にボタンBを押したときの実行結果を示します。

Hello
onbuttonA start
loopstatus = 0
loopstatus = 0
count = 92
onbuttonA terminated

このプログラム 5.2 では,[ボタンA]サブプログラムの繰り返し処理中にpauseがあるので,APが占有されない機会がたくさんあります。
ボタンBが押されたら,その直後最初のAPが使用できる機会に[ボタンB]サブプログラム」が起動し,loopstatusを0にすることができます。
[ボタンB]サブプログラム」が終了し,[ボタンA]サブプログラムがAPを使えるようになったとき,loopstatusが0になっているので,「くりかえしを終わる」という部分で,繰り返し処理から抜け出します。
すでにloopstatusは0になっているので,loopstatusの表示は2回とも0になります。
また,countも1000000にはならず,この例では92にとどまっています。


6 スケジューラ

Micro:bitはAPが1つしかないので,一度に1つのサブプログラム(サブプログラム)しか動作しません。
複数のサブプログラムが同時に動いているように見えるのは,スケジューラ(舞台裏で動いているサブプログラムの1つ)が,各サブプログラムの起動を管理していて,サブプログラム側の作り方にもよりますが,うまくつなぎ合わせているからです。
スケジューラはキュー(待ち行列)を持っています。このキューにはすぐにでも起動させたいサブプログラムを順番に待たせています。一時停止や通信やLED画面表示の都合で待たされているサブプログラムを覚えておく待たされプールも持っています。
(キューはラーメン屋さんの待ち行列と同じで,最初に並んだ人から順にサービスを受けられます。プールは,おなか一杯のお客さんがいる休憩室です。おなかがすいたらキューに並ぶでしょう。)
スケジューラは次のことを行います。
(1)APが使える状態になったとき(直前のサブプログラムがAPの占有をやめたとき)に,キューの先頭になっているサブプログラムにAPの占有を許し,起動させます。(APが使える状態になったとき,キューに何もなければ何もしません。APが使える状態になっているてキューに何も登録されていないとき,キューに新たなサブプログラムが登録されたら,直ちにそのサブプログラムを起動します。)
(2)AP占有中のサブプログラムが,一時停止などでAPの占有をやめたときには,そのサブプログラムは待たされプールにはいります。
(3)一時停止終了などで,待たされる原因がなくなると,そのサブプログラムは待たされプールから消され,キューの最後尾に登録されます。
(4)タイマ割り込みなどが見つけたボタンの押し下げや,無線通信の受信などが原因となって起動させなければならないサブプログラムは,すぐに起動するのではなく,キューの最後尾に登録されます。


7 pause(一時停止)

pause(一時停止)はサブプログラムの動作の都合上挿入することが多いと思います。
pause(一時停止)は作業中のサブプログラムがAPの占有を一時的にやめ,長期にわたってAPを占有することがないようにする効果があります。複数のサブプログラムがAPを奪い合うようなプログラムでは,サブプログラム中の長い繰り返し処理内など長期AP占有の元となる部分にpause(一時停止)を挿入しておくのが良いでしょう。

8 3つの[ずっと]サブプログラムをつかったプログラム

3つの[ずっと]サブプログラムを1つのプログラム中に置くと,3つのサブプログラムが順に起動し,それぞれ20msecのpauseを末尾に持って動作します。20msecのpause中は各サブプログラムがCPUを明け渡しているので,他のサブプログラムも実行でき,3つ同時に動作します。
このプログラムでは,2つの[ずっと]サブプログラムでは自分の変数を1増やしているだけです。もう一つの[ずっと]サブプログラムでは自分の変数を1増やすと同時に3つの変数をシリアル通信で表示しています。

プログラム 補1.1 



次のような実行結果が得られます。表示は24msecごとに行われています。
これは20msecのpause+シリアル通信のための若干の時間として説明できます。

forever, 1, 1, 1, 150
forever, 2, 2, 2, 174
forever, 3, 3, 3, 198
forever, 4, 4, 4, 222
forever, 5, 5, 5, 246
forever, 6, 6, 6, 270
forever, 7, 7, 7, 294
forever, 8, 8, 8, 318
forever, 9, 9, 9, 342
forever, 10, 10, 10, 366
forever, 11, 11, 11, 390
forever, 12, 12, 12, 414
forever, 13, 13, 13, 438
forever, 14, 14, 14, 462
forever, 15, 15, 15, 490
forever, 16, 16, 16, 514
forever, 17, 17, 17, 538
forever, 18, 18, 18, 562
forever, 19, 19, 19, 586
forever, 20, 20, 20, 610
forever, 21, 21, 21, 634
forever, 22, 22, 22, 658
forever, 23, 23, 23, 682
forever, 24, 24, 24, 706


ところで,3つの[ずっと]サブプログラムでそれぞれがシリアル通信を行うと,実行結果がおかしくなります。これは,シリアル通信が3つのサブプログラムから実行されようとしますが,シリアル通信は一度に1つしか実行できないことに起因します。
次のプログラムはうまくいきそうですが,実行結果を見てがっかりします。

プログラム 補1.2 



次のような実行結果が得られます。シリアル通信が重複して要求されるため,各サブプログラム中でシリアル通信部分が無視されて,変数を1増やすだけという動作も起こるため,各変数が1ずつよりもっと多く増えているように表示されてしまいます。3つの変数が1つの変数だったらこのような結果を受け入れられるように思えるのは,偶然です。

forever1, 0, 150
forever2, 1, 174
forever3, 2, 198
forever1, 3, 222
forever2, 4, 246
forever3, 5, 270
forever1, 6, 294
forever2, 7, 318
forever3, 8, 342
forever1, 9, 366
forever2, 10, 390
forever3, 11, 414
forever1, 12, 438
forever2, 13, 462
forever3, 14, 486
forever1, 15, 510
forever2, 16, 534
forever3, 17, 558
forever1, 18, 582


上記のような現象を防ぐためには,シリアル通信の所で排他制御を行います。シリアル通信のように1つしかない機能を複数のサブプログラムが奪い合う部分はクリティカルセクションと呼ばれます。排他制御の仕組みはいろいろありますが,ここでは,簡単にbusyという変数を用いています。クリティカルセクションの入り口でbusyが真だったら,本の少し待つようにし,busyが偽になったら,busyを真にして,クリティカルセクションにはいります。クリティカルセクションが終わったらbusyを偽にして,クリティカルセクションを明け渡します。

プログラム 補1.1 



次のような実行結果が得られます。意図する動作が得られています。
forever1のサブプログラムの起動後,4msecかけて表示を行い,20msecのpauseにはいります。
forever1のサブプログラムがpauseに入った後,forever2のサブプログラムが起動し,4msecかけて表示を行い,20msecのpauseにはいります。
forever2のサブプログラムがpauseに入った後,forever3のサブプログラムが起動し,4msecかけて表示を行い,20msecのpauseにはいります。
forever1のサブプログラムが,前回の動作から20msecのpauseが終了し。再度起動します。表示では24msec経過しているのがわかります。
同様な動作がforever2,3のサブプログラムが,行っているのがわかります。

forever1, 1, 150
forever2, 1, 154
forever3, 1, 158
forever1, 2, 174
forever2, 2, 178
forever3, 2, 182
forever1, 3, 198
forever2, 3, 202
forever3, 3, 206
forever1, 4, 222
forever2, 4, 226
forever3, 4, 230
forever1, 5, 246
forever2, 5, 250
forever3, 5, 254
forever1, 6, 270
forever2, 6, 274
forever3, 6, 278
forever1, 7, 294
forever2, 7, 298
forever3, 7, 302
forever1, 8, 318
forever2, 8, 322
forever3, 8, 326
forever1, 9, 342
forever2, 9, 346
forever3, 9, 350
forever1, 10, 366
forever2, 10, 370
forever3, 10, 374
forever1, 11, 390
forever2, 11, 394

9 まとめ

[ずっと]サブプログラムでは,処理の最後に舞台裏に20msのpause(一時停止)が挿入されていることを観察しました。
pause(一時停止)には,AP(Application Processor, micro:bitの頭脳)の占有を一時的にやめる意味があります。複数のサブプログラムがAPを奪い合う場面では,長いAP占有が予想されるサブプログラム中にpauseを挿入すると,プログラム全体の流れが良くなることを確認しました。

補足として,複数の[ずっと]サブプログラムがある場合のプログラムのふるまいを観察し,それぞれの[ずっと]サブプログラムが20ms毎に作業をしようとしていることを確認しました。