3.19. コードで扱う UART

machine.UART は 1 つのハードウェア UART チャネルをラップします。バス id とボーレートを指定して生成します。それ以外は妥当なデフォルトが用意されています。

from machine import UART

uart = UART(3, baudrate=115200)

id はどのハードウェア UART を使うかを選びます。値はボードによって異なります(特定のカメラで利用できるバス番号とピン割り当てについては OpenMV ボード を参照してください)。デフォルトのフレーム形式は 8 データビット、パリティなし、1 ストップビットで、誰もが期待する「8N1」です。

3.19.1. 書き込みと読み取り

UART の出力は write() を通して行われます。

uart.write("hello\n")
uart.write(b"\x01\x02\x03")

writestr(UTF-8 としてエンコードされます)または bytes / bytearray のいずれかを受け付けます。バイトが TX バッファにキューイングされるとすぐに戻り、ハードウェアがバックグラウンドでそれらをクロックアウトし終えます。

読み取りは、必要なものに応じて 3 つのメソッドを通して行われます。

n = uart.any()              # bytes available to read right now
data = uart.read(8)         # up to 8 bytes, or None on timeout
line = uart.readline()      # bytes ending in '\n', or None on timeout

any() はブロックせずに RX バッファをチェックします。read() は固定数のバイトを読み取り、先にタイムアウト(コンストラクタの timeout 引数で設定可能)が切れた場合は None を返します。readline() は次の改行までを(改行を含めて)読み取り、行ベースのプロトコルに便利です。

受信したものをそのままエコーするシンプルなループです。

uart = UART(3, baudrate=115200, timeout=100)

while True:
    if uart.any():
        data = uart.read()
        uart.write(data)

長さ引数なしの read() は、受信線が設定された timeout の間静かなままになるまでバイトを読み続け、その後、蓄積されたものをすべて返します。timeout=100 の場合、ここでの各呼び出しはバイトの 1 バースト、つまり送信側が 100 ms のギャップを空けずにクロックアウトしたものすべてを返します。タイムアウトがないと、この呼び出しは送信側が終わったという合図を持たず、無期限にハングする可能性があります。

3.19.2. struct によるバイナリデータ

整数や浮動小数点数を線上で送るのは、まさに struct モジュールの役割です。バイト順序と各フィールドの型を指定するフォーマット文字列を使って、固定幅の値を bytes オブジェクトにパックします。

import struct

uart.write(struct.pack("<lhb", count, temperature_x100, status))

先頭の "<" はリトルエンディアンのバイト順序を選びます。"l" は 32 ビット符号付き整数、"h" は 16 ビット符号付き整数、"b" は 8 ビット符号付き整数です。もう一方の側は 同じ フォーマット文字列でアンパックします。

payload = uart.read(7)        # 4 + 2 + 1 = 7 bytes
count, temperature_x100, status = struct.unpack("<lhb", payload)

フォーマット文字列は両端の間の取り決めです。バイト順序の誤り、型サイズの誤り、フィールド順序の誤りなど、いずれかが食い違うと意味のない値になります。

3.19.3. バッファリングと高レートの読み取り

any()read() を呼び出すポーリングループは、メインループが受信バッファを満杯になる前に空にできるほど速く回る限り、ほとんどのトラフィックに自力で追いつきます。レートが上がったりループが忙しいときには、2 つのコンストラクタオプションが重要になります。

rxbuf はソフトウェア RX バッファのサイズを設定します。デフォルトは数百バイトです。1 バーストで数百バイトを出力するセンサーや、メインループがポーリングの合間に長い処理を行う場合、バッファを大きくすることで、ループが別の場所で忙しい間に到着するバイトのドロップを防げます。

uart = UART(3, baudrate=115200, timeout=100, rxbuf=4096)

バッファが満杯の間に到着するものはすべて失われます。rxbuf のサイズは、空にする間隔の最長のギャップをカバーできるように設定してください。

持続的な高レートには、readinto() を使うと、呼び出しごとに新しい bytes オブジェクトを返す代わりに、事前に割り当てたバッファに読み込みます。

buf = bytearray(256)

while True:
    n = uart.readinto(buf)
    if n:
        process(buf, n)

読み取りのパスで割り当てが発生しないため、ヒープが断片化している場合や、割り当てのレイテンシがなければバイト間のタイミング予算を超過してしまうような場合に重要です。