no-image

Pythonで動くTrinket M0で遊んでみる(3)ターミナルを使った入出力

Pythonでプログラミングできる小さなマイコン「Trinket M0」の使い方を紹介します。今回はTrinket M0のUSB経由でターミナルに接続してみます。

Trinket

前回までの記事

前回までに、Trinket M0に搭載された赤色LEDを使ったLチカと、カラーLEDの制御方法を紹介しています。併せてどうぞ!

今回のゴール

今回は、PC側のターミナルソフトとTrinket M0を繋いでデータの入出力を行なってみます。

  1. ターミナルソフトを準備する
  2. print()でTrinket M0からターミナルソフトヘデータを送ってみる
  3. input()でターミナルソフトからの入力に応答してみる

用意するもの

今回もオンボードのLEDを使うだけですので用意するものは前回と同じです。

  • Trinket M0
  • USB通信ケーブル(USB AーUSB Micro B)
  • インターネットに接続できるPC(この記事ではMacを使います)とブラウザ、テキストエディタ

1. ターミナルソフトを準備する

「ターミナルソフト」とは、シリアル通信を使って遠隔にある別のデバイスの操作を行うためのアプリケーションです。

WindowsですとTera Termが有名です。

他に、AdafruitのCircuitPythonチュートリアルにも登場するCode with Muや、Arduino IDEもターミナルソフトとして利用することができます。

皆様がお使いのPCに合わせて、お好きなターミナルソフトを準備してください。

「ターミナル」の利用方法

今回筆者は古めのMac(Macbook Air 11インチ)で記事を書いていますが、MacOSの場合、OSに標準で「ターミナル」というユーティリティが含まれており、これを今回の「ターミナルソフト」としても利用できますので、利用方法を紹介します。

ターミナルは、下記に含まれていますので、まずは起動します。

Macintosh HD > アプリケーション > ユーティリティ > ターミナル

起動したターミナルから、下記コマンドを入力してUSBに接続されたシリアルポートを探します。

USBポートに何も接続されていない場合、下記のように表示されます。

USBケーブルでTrinket M0とPCを接続したあとに、再度 ls /dev/tty.usb*と入力すると、下記のようシリアルポート名が表示されます。

シリアルポート名がわかりましたので、screenコマンドでTrinket M0に接続します。

接続に成功すると、一度「ターミナル」の画面がクリアされてシリアル通信で送られてくるデータ表示状態に変わります。

Tinket M0側から何もデータが送られて来ないと、ターミナルソフトの画面には何も表示されてないと思います。

2. print()でTrinket M0からターミナルソフトヘデータを送ってみる

それでは、Trinket M0からPCへデータ送信するコードを書いてみましょう。

下記コードをテキストエディタにコピーして、main.pyというファイル名で保存してください。

続けて、main.pyを、マウントされているCIRCUITPYにドラッグ&ドロップします。
上書きするか?確認されるので、上書きしてください。

これで、Trinket M0に先ほどのmain.pyが書き込まれました。
起動後 xx 秒のような表示が連続してコンソールに出力されたら成功です。

コンソール出力結果

コード中のprint()の部分がターミナルソフトへデータ出力している箇所です。
コードでは、1秒ごとにカウントアップする変数の内容をprint()を使って出力しています。

print()は、文字列や数値などを「標準出力(sys.stdout)」へ表示する関数ですが、Trinket M0では「標準出力」がUSBシリアルポートに設定されているため、ターミナルソフトで出力結果を受け取ることができます。

print()は、文字列や数値を組み合わせた様々な出力形式をサポートしています。
下記サイトで`print()の文字列や数値の出力方法について、詳しく解説されていましたので、参考にさせていただきました。

note.nkmk.me : Pythonのprint関数で文字列、数値および変数の値を出力

3. input()でターミナルソフトからの入力に応答してみる

ターミナルへのデータ出力に成功しましたので、次は、ターミナルソフトからの入力にTrinket M0を反応させてみましょう。

これまでと同じやり方で、下記コードをTrinket M0に転送してください。

上記コードは、Lチカのコードの途中にinput()による入力待ちを挟んだものです。
Trinket M0に接続済のターミナルソフトから、Enterを入力することで、Trinket M0に搭載された赤いLEDが1回点滅するはずです。aなどを入力しても点滅しません。
Enterを入力するまでは、PCからTrinket M0へデータが送信されていないためです。

このように、Pythonのinput()はブロッキング動作を行います。

3.1. Serial.read()みたいにできないの?

input()はユーザーからの入力待ちを行い結果を返す関数ですので、入力待ちの間プログラムが止まるのは、期待された挙動と言えますが、たとえばArduinoSerial.Read()を使えば簡単に実装できる 「点滅しているLEDの点滅を止めずに、ターミナルから入力があったら点滅速度を切り替える」といったノンブロッキングな制御にはinput()は使えないのでしょうか?

このあたりで議論になっています。

上記スレッドの中で「今動かせる方法」として、Trinket側のUART(GPIO)を使って、UART-USBシリアル変換を経由してPC側のターミナルに接続することで、busio.UARTを使ったノンブロッキングなシリアルデータ送受信方法を紹介しています。

たしかに、この方法ですと、ArduinoのSerial.read()と全く同じことができます。
しかし、せっかくUSBシリアルが搭載されているのに、余計なUART-シリアル変換を使うのは、ちょっといやですね。

どうせなら、busio.UARTで、USBシリアルポートが指定できるようになれば、それでも良い気がしますが、それもできないようなので。

と感じた人が他にも居たらしく、input()を使ってなんとかする方法をプルリクしたようです。そしてマージされた。すばらしい!

というわけで、上記プルリクを上げてくれたBillの方法を試してみたいと思います。

3.2. serial_bytes_available + input()を試してみる

Billがコントリビュートしてくれたsupervisor.runtime.serial_bytes_availableは、現在正式リリースされている最新版のVer.3.1.1には含まれていませんが、adafruit/circuitpython : releaseを見ると、Ver.4.0.0α2がPre-Releaseされており、こちらには含まれているようです。

早速試してみましょう。

まず、さきほどのCircuitPythonリリースサイトからadafruit-circuitpython-trinket_m0-en_US-4.0.0-alpha.2.uf2をダウンロードし、Trinket M0に書き込んでください。

ダウンロードするファイルだけ異なりますが、Trinket M0への書き込み方法は、Pythonで動くTrinket M0で遊んでみる(1)Lチカまで の「1. Trinket M0 をアップデートする」と同じやり方でOKです。

4.0.0αへのファームウエアアップデートが終わったら、下記コードを書き込んでみてください。

こんな感じ↓に動かせたら成功です!

EnterでLチカ(ノンブロッキング風)

注意:
今回試したコードは、正確には「input()のノンブロッキング動作」ができるようになるわけではなく、input()前にsupervisor.runtime.serial_bytes_availableを判定することで、すでに入力データがきているときだけinput()で入力処理をさせる、という流れにすることで、ユーザー入力の待ち合わせを回避するハックです。ですので、input()が入力の終了と見なす文字(改行)を含めないようなデータを流すと、supervisor.runtime.serial_bytes_availabletrueになるのに、input()はブロッキングする、という現象は発生します。

まとめ

今回は、Trinket M0をUSBケーブルでPCと繋いでターミナルソフトを使った入出力を試してみました。

次回以降、私のTrinket M0にも遂にピンヘッダをハンダ付けして、もっとGPIOを使った機能をいろいろ見て行きたいと思います! 最初何から見てみようかな?(まだ考えてないので、お楽しみに!)

参考URL

下記情報を参考にさせていただきました!貴重な情報公開ありがとうございます。