壊れた文字からボーレートを取り戻す
目標
キャプチャしたサンプル列からUARTのフレームを自分でデコードし、送信クロックがずれたキャプチャから実際のビット時間を測り直して、読める文を復元します。その過程で、パリティとフレーミングエラーがそれぞれ何を捕まえて何を見逃すかを、数字で数えます。
なぜ重要なのか
UARTには、クロックの線がありません。両側が同じビット時間を信じている必要がありますが、その信頼を確認する方法は、フレームごとに1回、スタートビットだけです。そのため、クロックがずれると、フレームの後ろ側から崩れ、症状は「文字が壊れる」としてしか上がってきません。このラボは、その報告を、何%ずれているかという数字に変える練習です。値を直すのではなく、時間軸を直すという点が核心で、その時間軸の根拠は、キャプチャファイルの中にすでに入っています。
ステップ
- サンプル列に時間軸を立てる: ヘッダーのサンプルレートでサンプル番号を時刻に変換し、概要を残します。
- しきい値を上げるとエッジが動く: rising_edgesを実装し、2つのしきい値での検出時刻の差を測ります。
- フレームデコーダーを書く: スタートエッジから(k + 0.5)ビット時間離れたサンプルを読むdecodeを実装します。
- 正常なキャプチャを読む: フレーミングエラー0で文を得ます。
- サンプルを間引いて判読が崩れる所を探す: 1・4・10・25・50の間隔で、デコードし直します。
- 公称ボーレートで読んだときの症状: ずれたキャプチャのフレーミングエラーと、最も短いパルスを測ります。
- ビット時間を測り直して文を復元する: 粗い値から始めて、長い基線で絞り込みます。
- パリティが見逃したものを数える: 静かに間違ったフレームが何個あるかを数えます。
参考
- 材料は、
/opt/fixtures/serialbus/uart/の下の3つです:hello-9600.csv(正常)、drift-8n1.csv、drift-8e1.csv。読み取り専用で、修正しないでください。形式の説明は、同じフォルダーの1つ上の/opt/fixtures/serialbus/README.mdにあります。 - キャプチャファイルを読む関数は、
capture.pyのようなファイル1つに置き、ステップごとに書き直さないでください。正解例もそうしています。 - ファイルの
n列は、サンプル番号です。時刻はn / sample_rate_hz秒で、ファイルに時刻の列はありません。 - よくあるミスの1つ目は、データのビットを上位から集めてしまうことです。UARTは下位からです。
- よくあるミスの2つ目は、ビットあたりのサンプル数を整数に丸めてしまうことです。9600 Bdを500kHzで取ると52.083で、丸めるとフレームの終わりでずれます。
- 追加のインストールもインターネットも必要ありません。python3だけを使います。
- 目安は70分なので、基本の60分が終わる前に、+時間で延長してください(最大180分)。セッションが終わると
/rootは消えるので、残したいものは別に保管してください。
サンプル列に時間軸を立てる
mkdir -p /root/bus-uartで作業フォルダーを作り、/opt/fixtures/serialbus/uart/hello-9600.csvを読んで、/root/bus-uart/survey.jsonに、sample_rate_hz(ヘッダーから読んだ値)、sample_count(サンプルの行数)、duration_us(サンプル数に1,000,000を掛けて、サンプルレートで割った値)、min_v、max_v、idle_level(最初のサンプルが1.65V以上なら"high"、そうでなければ"low")を保存してください。
ヘッダーは#で始まる行で、sample_rate_hzがそこにあります。その次の行が列名で、その下がサンプルです。時刻はファイルになく、サンプル番号とサンプルレートで計算します。
しきい値を上げるとエッジが動く
/root/bus-uart/edges.pyにrising_edges(volts, threshold_v)を実装してください。volts[i-1]がしきい値未満で、volts[i]がしきい値以上であるiを、昇順のリストで返します(iは1から)。そして、hello-9600.csvに、しきい値1.65Vと2.90Vをそれぞれ適用して、/root/bus-uart/threshold.jsonに、count_1v65、count_2v90、first_1v65、first_2v90、shift_samples(first_2v90からfirst_1v65を引いた値)、shift_usを保存してください。
エッジが有限の傾きを持つなら、高いしきい値は、より遅く超えます。2つのしきい値の個数が同じで、時刻だけが異なる点を確認してください。採点ツールは、キャプチャファイルとは無関係の短い表でも、この関数を試します。
フレームデコーダーを書く
/root/bus-uart/uart.pyにdecode(volts, sample_rate_hz, baud, threshold_v=1.65, parity=None)を実装してください。アイドル(ハイ)からスタートビットへ落ちるエッジiを探し、iから(k + 0.5)ビット時間離れたサンプルを、int()で切り捨てて、kビット目として読みます。データは8ビットで、下位からです。フレーム1つを、{"start": i, "value": バイト, "framing_ok": ストップビットが1か, "parity_ok": パリティが合っているか}として格納してリストで返し、ストップビットを読んだサンプルの直後から、次のエッジを探します。parityはNoneまたは"even"です。
ビットあたりのサンプル数は、sample_rate_hz / baudで、整数ではありません。parityがNoneなら、parity_okは常に真です。採点ツールは、手で作った試験表(正常なフレーム、ストップビットが0のフレーム、パリティビットだけを反転させたフレーム)で、先に確認します。
正常なキャプチャを読む
hello-9600.csvを9600 Bdでデコードして、/root/bus-uart/message.jsonに、frame_count、framing_errors、bytes(整数のリスト)、text(バイトをそのまま文字としてつなげた文字列)を保存してください。
フレーミングエラーが0であれば正常です。0でなければ、ビットあたりのサンプル数や、スタートエッジを探す条件を、もう一度見てください。
サンプルを間引いて判読が崩れる所を探す
hello-9600.csvのサンプル列を、1、4、10、25、50の間隔で間引き(volts[::k])、それぞれサンプルレートをrate // kとして、9600 Bdでデコードし直してください。/root/bus-uart/sampling.jsonにcasesを、この順番のまま5つ格納し、各項目に、decimate、sample_rate_hz、samples_per_bit、text_ok(間引いていない結果と文字列が同じか)を入れてください。そして、最初にtext_okが偽になる間隔を、first_failing_decimateに書いてください。
ビットあたりのサンプル数が1に近づくと、デコーダーは、エラーを出さずに、別の文字を出します。text_okはブール値でなければならず、文字列の"true"ではありません。
公称ボーレートで読んだときの症状
/opt/fixtures/serialbus/uart/drift-8n1.csvを9600 Bdでデコードして、/root/bus-uart/drift.jsonに、frame_count、framing_errors、bytesを保存してください。これに加えて、レベルが変わるサンプル番号を集め、隣り合う間隔の最小値を求めてshortest_pulse_usに、1,000,000 / 9600をnominal_bit_time_usに、両者の比をratioに書いてください。
最も短い間隔は、ビット1つです。その値が公称のビット時間と何%異なるかが、このキャプチャの診断です。フレーム数は正常なキャプチャと似ているのに、フレーミングエラーだけが増える点を確認してください。
ビット時間を測り直して文を復元する
drift-8n1.csvから、ビット時間を測り直してください。まず、エッジの間隔の最小値を粗い値としてつかみ、次に基線を延ばしながら絞り込みます。最初のエッジから(今の推定値×16)サンプルの中に入る最も遠いエッジを選び、その距離を推定値で割って丸めた整数で、あらためて割ります。倍数を4倍ずつ大きくして、最後のエッジまで繰り返します。結果を/root/bus-uart/measured.jsonに、bit_time_us、baud(1,000,000をbit_time_usで割った値)、そのボーレートでデコードし直したtext、framing_errorsとして保存してください。
一度に全体の長さで丸めてはいけません。粗い値の誤差にビットの個数を掛けた値が0.5ビットを超えると、見当違いの整数を選びます。粗い値だけでも文字は読めますが、採点は0.2%以内を要求するので、絞り込む段階を飛ばすことはできません。
パリティが見逃したものを数える
/opt/fixtures/serialbus/uart/drift-8e1.csvを、偶数パリティで2回デコードしてください。1回は測り直したビット時間で(こちらが真の値)、もう1回は9600 Bdで行います。/root/bus-uart/parity.jsonに、frames、parity_errors、framing_errors、flagged(どちらか一方でもかかったフレームの数)、wrong_bytes(真の値と値が異なるフレームの数)、silently_wrong(値が異なるのにパリティもフレーミングも通過したフレームの数)、text_at_measuredを書いてください。
パリティは、1の個数の偶奇しか見ません。偶数個が反転するとそのまま通過するので、flaggedがwrong_bytesより小さいことがあります。2つのデコード結果を、フレームの順番に組み合わせて、比べてください。