Embedded Test 05 · UART とシリアルコンソール — 機器が漏らす一番のヒント

Chapter 05

UART とシリアルコンソール — 機器が漏らす一番のヒント

この章のゴール.

UART の物理(ボーレート・フレーム)を理解し、未知のシリアル線を見つけて速度を割り出し、 ログ・コマンドコンソールを観測・自動化してテストのオラクルとフックにできるようになること。

この章で使う既出の用語(定義は各リンク先). USB(01 章 2 節)、オラクル(01 章 8 節)、不変条件(01 章 3 節)、刺激(02 章 1 節)、状態遷移(02 章 1 節)、電源(02 章 2 節)、オシロ(04 章 8 節)、トリガ(04 章 3 節)、ロジックアナライザ(04 章 1 節)

1. UART は最初に探すべき窓

UART(Universal Asynchronous Receiver/Transmitter、調歩同期式シリアル)は、2 本(TX/RX)+ GND で 1 バイトずつ送る最も基本的な通信である。 多くの組み込み機器は、開発時のデバッグのために UART にブートログ・状態表示・コマンドコンソールを出している。製品版でも残っていることが多く、中身を知る最短の窓になる。

UART は 3 本——TX・RX を交差、GND を共通にPC + USB-シリアル変換3.3 V ロジックテスト対象(MCU)コンソール / printfTX → RXRX ← TXGND 共通電圧レベル(3.3V か 5V か)を必ず合わせる。合わないと壊れる/読めない
図5-1 シリアルコンソールの結線。

2. UART のフレーム構造

1 バイトは、アイドル(High)から スタートビット(Low)→ データ 8 ビット(LSB 先)→ (パリティ)→ ストップビット(High) で送られる。

1 バイトの枠組み:スタート・データ 8 ビット・ストップStartb0b1b2b3b4b5b6b7Stopアイドル(H)L に落ちて開始「8N1」=データ 8 ビット・パリティなし・ストップ 1 が最多クロック線がないので、受信側はボーレート(1 ビットの長さ)を知っている必要がある
図5-2 UART の 8N1 フレーム。

3. 未知のシリアル線を見つける

基板に「これが UART」と書いていない。探す手順:

  1. 候補ピンを探す: 4 つ並んだテストポイント(GND/VCC/TX/RX が典型)、シルクの TX/RX/DBG、未実装のピンヘッダ
  2. GND を特定: テスターの導通で、金属シェルや大きなベタと導通するピン
  3. TX を特定: 起動時に周期的に動くピンをロジックアナライザ/オシロで探す。TX はアイドル High で、データ送出時に忙しく動く。VCC(常時 High)や GND(常時 Low)と区別
  4. ボーレートを割り出す: TX の最短パルス幅を測る。それが 1 ビットの時間。ボーレート = 1 / パルス幅。例: 最短 8.68 µs → 約 115200。sigrok の「baudrate 自動検出」や、いくつかの定番速度を総当たりする手も
# 最短パルス幅からボーレートを推定
import numpy as np
edges = load_edges("tx.csv")            # エッジ時刻[秒]
widths = np.diff(edges)
bit = widths.min()                       # 最短 = 1 ビット時間
print("推定ボーレート:", round(1/bit))   # 115200 付近に丸める

4. 観測 — ログをオラクルにする

TX を USB-シリアル変換(3.3V ロジックのものを。5V を当てて壊さない)で受けて、ログを取る。

# ログを取りながらタイムスタンプを付ける
picocom -b 115200 /dev/ttyUSB0 | ts '[%H:%M:%.S]'
# Python なら pyserial
python3 -c "import serial; s=serial.Serial('/dev/ttyUSB0',115200);
[print(s.readline().decode(errors='replace').rstrip()) for _ in iter(int,1)]"

ログをオラクルに使う型:

AI 生成コードでは、ログと実際の動作が食い違うことがある(Sending... と出すが実際は送っていない、04 章・09 章の観測と突き合わせる)。ログを鵜呑みにせず、他の観測点と照合する。

5. 能動 — コマンドコンソールを叩く

コンソールがあれば、UART は観測だけでなく刺激の入口になる。

def cmd(ser, line, timeout=1.0):
    ser.reset_input_buffer(); ser.write((line+"\r\n").encode())
    import time; end=time.time()+timeout; out=b""
    while time.time()<end: out+=ser.read(ser.in_waiting or 1)
    return out.decode(errors="replace")

assert "OK" in cmd(ser, "AT+STATUS"), "状態取得に失敗"

6. 落とし穴

7. 手を動かす

シリアル線を見つけて速度を割り出す


この章のポイント