micro:bit Makecodeで無線通信の動作テスト
(Low level radio communications)

2023.1.5 2021.7.31 Coskx Lab

通信機能が働いていないことがあります
 → 
原因と対処方法の説明はこちら

この文書内では,対処されたプログラム例を載せています。

1 はじめに

micro:bitで制御されているロボットなどを無線で遠隔操作することを目的とします。
[micro:bit - WinPC]間あるいは[micro:bit - スマートフォン]間で,直接無線通信はできないので,中継用micro:bitを用います。




miccro:bitでは次の2つの通信ができます。
(1) micro:bit間で使える無線通信(Low level radio communications)
  簡単な説明は次を参照してください。 →  無線通信 
(micro:bitどうしの無線通信に関しては,web検索すると多くの記事が出てきます。)
(2) スマートフォンとmicro:bitのシリアル有線通信
この通信に関しては次を参照してください。
    → 「シリアル通信」

上記の2つの通信を使うと,中継micro:bitの助けを借りて,メインmicro:bitの内部状態(例えば,センサの取得値やモータ指令値など)を外部(スマホやPC)に送信できます。




またスマホやPCのターミナルアプリなどからコマンド文字列をメインmicro:bitに送りメインmicro:bitを遠隔操作できます。
メインmicro:bitがロボットなどに搭載されていれば,そのロボットのリモート操縦などもできます。
動き回るロボットなどを開発するときには,有線での通信よりも無線での通信の方が便利です。

ここではメインmicro:bitが,無線通信(Low level radio communications)で中継micro:bitと通信し,さらに中継micro:bitがUSBシリアル通信を使ってAndroidスマホあるいはWindowsPCと通信することにします。
メインmicro:bitの通信相手は,中継器の存在を意識しなければ,Androidスマホ(タブレット)のアプリSerialUSBTerminal,あるいはWindowsPCのteratermやSerial_Terminal_plusになります。

〇 通信システム開発上の注意
一般に通信は2つの機器の設定や動作するプログラムのすべてが正しくできていないとうまくいきません。
そのため,いきなり目的のシステムを組み上げても,不具合が発生してしまい,その原因を特定し,対処するのに多くの時間を割かなければならないことが多いです。
そのようなことを避けるためには,単純なシステムから順に動作を確かめながら目的のシステムに到達するのが近道です。
その手順に従えば不具合が発生しても,原因の特定が容易になると思います。
まずは,有線でのmicro:bit - WindowsPC(あるいはAndroidスマートフォン)通信,2つのmicro:bit間の無線通信を別々に成功させてから,目的のシステム開発を行うのがよいと思います。

〇 無線通信の文字数制限を回避する工夫
MakeCodeライブラリの無線通信では一回の通信では18文字までという制限があります。
(https://makecode.microbit.org/v0/reference/radio/send-string ここに19文字までと書いてある[The maximum string length is 19 characters.]のですが,実際には18文字までしか遅れませんでした。)
そこで,18文字を超えるような長い文字列を送る場合は次のような工夫をしています。(メインmicro:bitからスマホ(PC)に送信するときだけ工夫しています。今のところ逆は工夫していません。)

・ メインmicro:bitは長い文字列を17文字以下の短い文字列に分けて送ります。まだ一行が終わりではないときは文字列の終わりに「&」を付加して送信します。

・ 中継micro:bitでは,メインmicro:bitから受信した文字列が「&」で終わっている場合は,改行コードを付けずに,そうでない場合は改行コードを付けて,シリアルUSB通信側(スマホやPC)に送信します。

こうすることで,複数回に分けられてメインmicro:bitから送られてきた文字列を,1行の文字列にしてスマホ(PC)側が表示できるようになります。
ただしメインmicro:bitでの無線送信時にはサンプルプログラムにあるように,「無線で文字列を送信」を使用してください。

〇 BluetoothLE
中継micro:bitを使わずに,直接PCあるいはスマートフォンと通信する場合は, BluetoothLEを使った通信を検討します。
しかし,BluetoothLE通信はランタイムライブラリが大きくて,大きなプログラムになると,プログラムサイズオーバーになりやすいです。
どちらを使うかは,状況により判断する必要があります。

2 使用環境

3 接続

micro:bitから,中継micro:bitまでは無線通信です。

(1)Androidスマホに接続する場合
中継micro:bitからUSBケーブル+OTGケーブルでAndroidスマホに接続します。



USBケーブルの中には,充電専用のものがあり,信号線がつながっていない場合があります。その場合は通信に失敗しますので注意してください。

(2)WindowsPCに接続する場合
中継micro:bitからUSBケーブルでWindowsPCに接続します。


USBケーブルの中には,充電専用のものがあり,信号線がつながっていない場合があります。その場合は通信に失敗しますので注意してください。


4 通信テストプログラム 簡単なプログラム例

メインmicro:bitから中継micro:bitを経由して,PCまたはスマートフォンのターミナルアプリに文字列を送信します。
メインmicro:bitと中継micro:bitのそれぞれプログラムが必要になりますので2つのプログラムを載せます。

4.1 メインmicro:bitプログラム

メインmicro:bitは「No pain no gain.」と「Nothing seek, nothing find.」を送信します。
ところが,2つ目の文は文字数が制限18を超えているため,19文字目以降は送信できません。そのため,スマホ(PC)受信側では文字列が途切れて見えることになってしまいます。
そのようなことを避けるため,次のように工夫しました。メインmicro:bitは「Nothing seek, nothing find.」を2つに分けて送信し,スマホ(PC)受信側では1行の文のように見えるようにしています。
文字列の分割の印として分割部分に「&」文字を使うことにしました。次のプログラムでは「Nothing seek, &」「nothing find.」の2つに分割しています。

無線を使用する場合は,最初に無線グループの設定をしますが,別のグループと混信しないように,グループ内で決めている番号にします。

ブロック
  「無線」通信テスト用プログラム(メインmicro:bit)




JavaScript
コピペする場合は,次のソースコードをJavaScriptソースとして貼り付ければ完了です。

radio.setGroup(1)
radio.sendString("")
basic.forever(function () {
    radio.sendString("No pain no gain.")
    radio.sendString("Nothing seek, nothing find.")
    radio.sendString("Nothing seek, &")
    radio.sendString("nothing find.")
    basic.pause(5000)
})

4.2 中継micro:bitプログラム

中継micro:bitはメインmicro:bitから無線で送られてきた文字列をスマホあるいはPCに向けてシリアル出力します。 逆にスマホあるいはPCからシリアル通信で送られてきた文字列はメイン側に無線で送信します。 メインからの受信では文末に「&」が送られて来たら,改行コードは付けずに,そうでない場合は改行コードを付けえシリアル通信でスマホに送ります。
無線を使用する場合は,最初に無線グループの設定をしますが,別のグループと混信しないように,メインmicro:bitと同じ番号にします。

ブロック
  「無線」通信テスト用プログラム(中継micro:bit)




追加説明1 「無線で受信したとき」
メインmicro:bitから無線で文字列受信したときに起動します。
受信した文字列が「&」で終わっている場合は,改行コードを付けずに,そうでない場合は改行コードを付けて,シリアルUSB通信側(スマホやPC)に送信します。
その結果,シリアルUSB通信側(スマホやPC)は分割されてきた2行が1行として表示されるようになります。

追加説明2 「シリアル通信 ... 受信したとき」
スマホやPCなどのシリアルターミナルからシリアル通信で文字列受信したときに起動します。
受信した文字列をそのまま無線でメインmicro:bitに送信します。


JavaScript
コピペする場合は,次のソースコードをJavaScriptソースとして貼り付ければ完了です。

serial.onDataReceived(serial.delimiters(Delimiters.NewLine), function () {
    serialReceivedString = serial.readUntil(serial.delimiters(Delimiters.NewLine))
    radio.sendString(serialReceivedString)
})
radio.onReceivedString(function (receivedString) {
    if (receivedString.includes("&")) {
        point = receivedString.indexOf("&")
        newstring = receivedString.substr(0, point)
        serial.writeString(newstring)
    } else {
        serial.writeLine(receivedString)
    }
})
let newstring = ""
let point = 0
let serialReceivedString = ""
basic.showIcon(IconNames.Yes)
led.setBrightness(20)
radio.setGroup(1)
radio.sendString("")

5 通信手順

(1) Androidスマホの場合
Androidデバイスを使い,通信アプリ「Serial USB Terminal」を使うことを想定します。


中継用micro:bitを接続します。
Androidデバイスによっては,スマホの設定画面でUSBシリアル通信を使うように設定する必要がある場合があります。
最近のAndroidスマホでは「Serial USB Terminal」を起動してからUSBシリアル通信を使うけれど構わないか聞かれると思います。

「Serial USB Terminal」が起動したら,左上3本線メニューから「Device」を選び,BBC micro:bitが見つかるので,選びます。
左上3本線メニューから「Setting」を選び,「Serial」で
Baud rate 115200
にします。
「Setting」の「Terminal」で
Buffer size 大きめに設定(200kB)
「Setting」の「Receive」で
Newline CR+LF
「Setting」の「Send」で
Newline LF
Local echo 有効 とします。

(2) WindowsPCの場合
様々なターミナルアプリや,開発環境のターミナル表示を使用することができますが,ここではWindowsPCのアプリ「Tera term」あるいは「Serial_Terminal_plus」を使うことにします。
(2.1) Tera termの場合
Tera Termは有名なアプリなので,Net検索すればすぐに見つかると思います。

中継用micro:bitをPCに接続します。
Tera termを起動し,ファイルメニューの新しい接続でシリアルを選びます。
ポートもそこで選ぶことができるので,選択肢の中から,有効になっているポート(COM3のような名前)を選びます。
設定メニューの「シリアルポート」でスピードを115200にします。(ここでもポート選択が出来ます。)
設定メニューの「端末」で改行コードも受信・送信両方ともLFにします。
ローカルエコーをONにします。

(2.2) Serial_Terminal_plus
Tera termでのmicro:bitとの通信では定型文字列(コマンド文字列)の送信が不便です。 定型文字列をカスタムボタンに登録しておき,カスタムボタンで,定型の文字列を送信できるようにした,Serial_Terminal_plusを紹介します。
Serial_Terminal_plus

中継用micro:bitをPCに接続します。
シリアルポートボックスにCOM3など有効なポートが見えますが,一番大きな番号が対象のポートになると思います。 「Connect」ボタンを押すと中継用micro:bitに接続されます。


6 通信時の様子(micro:bitからPCまたはスマホへの片方向通信)

「4.1 メインmicro:bitプログラム」を使って,メインmicro:bitから「No pain no gain.」と「Nothing seek, nothing find.」を連続送信し,スマホおよびPCで受信しているところです。
「Nothing seek, nothing find.」を何も工夫せずに送信すると文字数が制限18文字を超えてしまい途中で途切れているのもわかります。
「Nothing seek, &」と「nothing find.」に分けて送信しているときは意図通りの送信が出来ていることがわかります。

(1) Androidスマホ「Serial USB Terminal」の場合



(2) WindowsPC 「tera term」の場合



(3) WindowsPC 「Serial_Terminal_plus」の場合



7 スマホ(PC)からのコマンド送信の例(双方向通信,測定値観察)

シリアルターミナルアプリから,予め決めておいたコマンド(文字列)をメインmicro:bitに送って,それに対応した動作を行わせます。
次のような動作をすることを目指します。

ターミナルからメインmicro:bitに加速度センサ値と明るさセンサ値を要求し,測定値を送信してもらいます。
具体的には。ターミナルから,「ACC 0 10 4」を与えると,加速度センサのx軸計測値を10回平均で求め送信するを4回行います。(「ACC 1 20 5」を与えると,加速度センサのy軸計測値を20回平均で求め送信するを5回行います。「ACC 2 40 3」を与えると,加速度センサのy軸計測値を40回平均で求め送信するを3回行います。)
ターミナルから「LLevel 10 3」を与えると明るさセンサの計測値を10回平均で求め送信するを3回行います。

中継micro:bitに関しては4.2と同じものを使用しています。
無線の送信時には,中継器プログラムの都合上,「無線で数値を送信」と「無線で送信 name = xxxx」は使用しません。



次のプログラム例にあるように「無線で文字列を送信」および「文字列をつなげる」を使用して,工夫して送信します。

7.1 使用するプログラム


メインmicro:bitには次のプログラムを使用します。

ブロック
      明るさ強度と加速度測定プログラム(メインmicro:bit)




JavaScript
コピペする場合は,次のソースコードをJavaScriptソースとして貼り付ければ完了です。

function measureLightLevel (Nmeans: number, Nrepeats: number) {
    for (let index = 0; index < Nrepeats; index++) {
        value = 0
        for (let index = 0; index < Nmeans; index++) {
            value = value + input.lightLevel()
            basic.pause(20)
        }
        value = value / Nmeans
        radio.sendString("LLevel = " + ("" + value))
    }
}
radio.onReceivedString(function (receivedString) {
    radio.sendString(receivedString)
    parameters = receivedString.split(" ")
    command = parameters[0]
    paramN1 = parseFloat(parameters[1])
    paramN2 = parseFloat(parameters[2])
    paramN3 = parseFloat(parameters[3])
    if (command.includes("ACC")) {
        measureACC(paramN1, paramN2, paramN3)
    } else if (command.includes("LLevel")) {
        measureLightLevel(paramN1, paramN2)
    }
})
function measureACC (axis: number, Nmeans2: number, Nrepeats2: number) {
    for (let index = 0; index < Nrepeats2; index++) {
        value = 0
        for (let index = 0; index < Nmeans2; index++) {
            if (axis == 0) {
                value = value + input.acceleration(Dimension.X)
            } else if (axis == 1) {
                value = value + input.acceleration(Dimension.Y)
            } else {
                value = value + input.acceleration(Dimension.Z)
            }
            basic.pause(20)
        }
        value = value / Nmeans2
        radio.sendString("ACC (" + ("" + axis) + ") = " + ("" + value))
    }
}
let paramN3 = 0
let paramN2 = 0
let paramN1 = 0
let command = ""
let parameters: string[] = []
let value = 0
radio.setGroup(1)
radio.sendString("")

追加説明 シリアル通信の文字列を受け取ったときの作業について
受け取った文字列receivedstringを半角スペースで分解して,parametersという配列にしまいます。
receivedstringが,"LLevel 10 3"のとき,
parametersの0番目の値は"LLevel"
parametersの1番目の値は"10" 数値の10ではなく文字列の"10"
parametersの2番目の値は"3" 数値の3ではなく文字列の"3"
になります。(parametersの配列番号は0から始まります)
numberN1にはparametersの1番目の値"10"を数値10に変換してしまいます。
numberN2にはparametersの2番目の値"3"を数値3に変換してしまいます。
そしてnumberN1とnumberN2を与えて関数measureLightLevel (Nmeans: number, Nrepeats: number)を呼び出します。
measureLightLevelはNmeans回の平均値を求め表示するを,Nrepeats回行う関数です。
同様に,measureACC (axis: number, Nmeans2: number, Nrepeats2: number)は軸番号axis方向の加速度をNmeans2回平均で求めて表示するを,Nrepeats2回行う関数です。

7.1 windowsPCのアプリ「Serial_Terminal_plus」での実行状況

Text to sendボックスで,はじめに「LLevel 10 3」を送ったときは,10回の明るさ測定の平均値を求め表示するを,3回行なっています。
「ACC 0 10 3」を送ったときは,x軸方向の加速度を,10回平均で求め表示するを,3回行っています。




追加説明 プリセットボタンをShift+Clickすると,送信されるべき文字列を登録することができます。作業前に必要な文字列を登録しておくと,作業中はこのボタンをクリックして登録済の文字列を送信することができ遠隔操作が簡単にできます。

7.2 WindowsPC 「tera term」での実行状況

操作および実行動作は7.1と同じです。



7.3 アンドロイドスマートフォンのアプリ「Serial USB Terminal」での実行状況

操作および実行動作は7.1と同じです。



追加説明 ここでは下部のプリセットボタンを表示せずに利用していますが,「Serial USB Terminal」では,メニュー→settings→Misc.→Macro buttonsで複数行のプリセットボタンを表示しておき,プリセットボタンを長押しすると,プリセットボタンに表示される文字列と,送信されるべき文字列を登録することができるので,作業中はこのプリセットボタンを利用すると簡単に操作ができます。

8 まとめ

中継micro:bitを使った無線通信で,メインmicro:bitからPCやスマホのシリアルターミナルにメッセージを送ることができました。
また,PCやスマホのシリアルターミナルからコマンドをメインmicro:bitに与えて,作業を行わせるひな型も動作しました。
この遠隔操作のコマンドを増やしていくと,micro:bitで制御しているロボットなどを遠隔操縦できるようになるはずです。