【組み込み】UARTデバッグ入門 printfだけで終わらせないログ設計と詰まりどころ

  • URLをコピーしました!

組み込み開発で不具合を追うとき、最初に頼りになるのがUART出力です。
とりあえずprintfを仕込んで様子を見る、という流れは多くの現場で行われています。

ただ、UARTデバッグは文字を出せば終わりではありません。
出力の仕方が雑だと、肝心な情報が欠けたり、タイミングが変わって不具合が見えなくなったりします。

この記事では、UARTデバッグの基本を整理しつつ、実務で役立つログ設計の考え方と、よく詰まりやすいポイントを解説します。
「とりあえずprintf」から一歩進んで、読みやすく役に立つログを残すための入門記事として読んでみてください。

目次

UARTデバッグとは

UARTデバッグとは、マイコンやSoCの動作状況をシリアル通信経由で文字列として出力し、観察する方法です。
ブレークポイントが使いにくい初期化処理や、RTOS上の非同期動作、現場機器上でしか再現しない不具合の調査でよく使われます。

UARTデバッグの良いところは、主に次の3つです。

  • 実装が比較的簡単
  • 追加の高価なデバッガがなくても始めやすい
  • 実機の動きを時系列で追いやすい

一方で、万能ではありません。
ログを出しすぎると処理が重くなりますし、割り込みや通信処理に影響して、ログを入れたせいで現象が変わることもあります。

そのため、UARTデバッグでは「何を、どの粒度で、どの形式で出すか」が重要になります。

printfだけに頼ると何が起きるのか

単純にprintf("here\n");を増やしていく方法は、最初の切り分けには有効です。
ただし、規模が大きくなると次のような問題が出てきます。

出力が読みにくくなる

たとえば、複数タスクや割り込みから同時にログが出ると、行が混ざって読めなくなることがあります。
「どの処理のログなのか」「どの時刻のものか」が分からないと、調査効率はかなり落ちます。

タイミング依存バグを壊してしまう

UART送信は思ったより重い処理です。
特に低いボーレートでは、数十文字出すだけでも無視できない時間がかかります。
その結果、本来発生していた競合やタイムアウトが消えてしまうことがあります。

必要な情報が足りない

printf("error\n");のようなログでは、エラーが起きたことしか分かりません。
どのモジュールで、どの条件で、直前に何が起きていたのかまで出しておかないと、原因調査に進めません。

ログ設計で最低限決めておきたいこと

UARTログを実務で使うなら、最初にフォーマットをある程度決めておくのがおすすめです。

1. ログレベルを分ける

少なくとも、以下のようなレベルを用意すると使いやすくなります。

  • ERROR: 異常や失敗
  • WARN: 異常ではないが注意が必要
  • INFO: 状態変化や重要イベント
  • DEBUG: 詳細な追跡用

たとえば、普段はERRORWARNだけ出し、調査時だけDEBUGを有効にします。
これだけでも、ログ量をかなり制御しやすくなります。

2. モジュール名を付ける

どの機能のログか分かるように、タグを付けます。

モジュール名があるだけで、後から追うときの負担が大きく減ります。

3. 時刻またはカウンタを入れる

ログの前にタイムスタンプやtick値を入れると、順序や遅延を把握しやすくなります。

絶対時刻でなくても問題ありません。
重要なのは、前後関係が追えることです。

実践で使いやすいログの出し方

ログは「人が読むためのもの」ですが、同時に「あとで絞り込めるもの」でもあるべきです。

状態変化を中心に出す

毎ループで値を垂れ流すより、状態が変わった瞬間を出すほうが有効です。

悪い例:

printf("temp=%d\n", temp);

良い例:

if (prev_state != state) {
    printf("[INFO][CTRL] state %d -> %d\n", prev_state, state);
}

これならログ量を抑えつつ、重要な変化を見逃しにくくなります。

エラー時は周辺情報も一緒に出す

異常だけを出すのではなく、原因の手がかりになる値も添えます。

printf("[ERROR][I2C] write failed addr=0x%02X reg=0x%02X ret=%d\n",
       addr, reg, ret);

このようにしておくと、再現試験や他メンバーへの共有もしやすくなります。

重要イベントには識別子を入れる

パケット番号、要求ID、フレーム番号などを入れると、複数処理の流れを追いやすくなります。

printf("[INFO][COMM] req_id=%u send start\n", req_id);
printf("[INFO][COMM] req_id=%u recv ok len=%u\n", req_id, len);

UARTデバッグでよく詰まるポイント

改行がなくてログが見づらい

端末によっては\nだけでは崩れることがあります。
環境によっては\r\nを使うほうが安定します。
見た目の問題に見えますが、ログ解析のしやすさに直結します。

バッファあふれでログが欠ける

送信リングバッファが小さいと、ログが多い場面で欠落します。
「肝心な直前ログだけ消える」ことも珍しくありません。
高頻度区間では抑制を入れる、バッファサイズを見直す、といった対策が必要です。

割り込み内で重いログを出してしまう

割り込みハンドラの中でそのままprintfすると、レイテンシ悪化やデッドロックの原因になります。
割り込みでは最小限のイベントだけ記録し、詳細出力は後段のタスク側で行うほうが安全です。

ボーレートや配線の問題を見落とす

文字化けや途切れが出る場合、ソフトだけでなく通信条件も確認が必要です。

  • ボーレート設定は一致しているか
  • 電圧レベルは合っているか
  • GNDは共通か
  • USB-UART変換器の品質に問題はないか

ログ内容ばかり見ていると、こうした基本要因を意外と見落とします。

まとめ

UARTデバッグは、組み込み開発で非常に強力な基本手段です。
ただし、printfを増やすだけでは、調査しやすいログにはなりません。

今回のポイントをまとめると、次の通りです。

  • UARTデバッグは手軽だが、処理への影響もある
  • ログレベル、モジュール名、時刻を揃えると読みやすくなる
  • 状態変化やエラー周辺情報を中心に出すと実用性が高い
  • 割り込み、バッファ、通信条件は詰まりどころになりやすい

最初から完璧な仕組みを作る必要はありません。
まずは読みやすい形式を決めること、次に出しすぎないことを意識するだけでも、UARTログの価値はかなり上がります。
「見える化のためのprintf」から、「原因調査のためのログ設計」へ進めると、デバッグの質は大きく変わってきます。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

エンジニア。20代。組み込みエンジニアとして働き始めるも、働き方や業務内容に限界を感じ、 AI,Web3エンジニアを目指して勉強中。 エンジニアとして思うことや、学んだことを発信します。

目次