【STM32H723ZG入門③】UART/USART編 | printfデバッグしてみた

スポンサーリンク

この記事でできること

  • STM32のUSARTをUART(非同期)として設定できる
  • printfをPC(TeraTerm)へシリアル出力できる
  • UARTとUSARTの違い(STM32の見え方)が分かる
  • よくあるトラブル(表示されない/文字化け等)を回避できる

関連記事:

【STM32H723ZG入門①】開発環境構築編 (CubeIDE/CubeMX)
この記事でできること STM32CubeIDEをインストールできる STM32CubeMXでプロジェクト生成できる ビルド〜書き込み〜実行までできる STM32開発の基本ワークフローを理解できるはじめに仕事はハードウェア開発でソフトウェア開...
【STM32H723ZG入門②】GPIO編|CubeMXでLチカ(LED点滅)してみる
この記事でできること CubeMXでGPIO設定できる 自動生成コードの意味が分かる HAL_GPIO_TogglePinが理解できる LEDが点滅する はじめにソフトウェア開発における組み込みC言語基礎力向上を図りたいgoldear@Ji...

はじめに

STM32開発ボードでLチカができたのでちょっと気分が良いgoldear@JiN_C125です。

前回はSTM32H723ZG Nucleo 開発ボードに実装されているUser LEDを点滅させることができましたLEDでの点灯/消灯で動作をデバッグする方法もありますが、いかんせん効率が悪いです。点灯パターンで特定のエラーと関連付けるのにも限界があります。

本記事では、STM32H723ZGでUSARTをUART(非同期)として使い、printfデバッグ環境を作っていきます。以降の開発(OLED、SD、FFTなど)で必須になるため、最初に土台を固めていきましょう。

開発環境

TeraTermは以下からダウンロードできます。

Releases · TeraTermProject/teraterm
Contribute to TeraTermProject/teraterm development by creating an account on GitHub.

全体の流れ

  1. CubeMXでUSARTをAsynchronous(UART)として有効化
  2. printfの出力先をUARTへリダイレクト(_write実装)
  3. TeraTermでログが表示されることを確認
  4. 内部(UART/USART、改行、ブロッキング送信)を理解
  5. ハマりポイント・注意点を確認

開発基板とターミナルを通じて通信できるとLチカとはまた違った達成感があるので、これも組み込み界のHelloWorldと思って実装していきましょう。

Step1. (CubeMX設定)

ユーザーマニュアル UM2407 7.6.5「Virtual COM port (VCP): LPUART/USART」よりSTLINKのUSARTは以下の通り分かります。前回紹介したように回路図から調べてもOKです。

  • USART3 TX → PD8
  • USART3 RX → PD9

MCUの PD8, PD9 をUSART3に設定します。

次にUSARTを有効化します。

設定手順:

  • Connectivity → USART3 → Asynchronous を選択
  • 通信パラメータ(ボーレート等)を設定
  • TX/RXのピン割り当てを確認

Mode Disable から Asynchronous に変更することで、UART(非同期)として動作し、通信設定が可能となります。合わせて PD8, PD9 ピンにUser Labelを付与しておきましょう。基本的にはSymbol Pinと同じ名前で良いかと。

次に Configuration -> Parameter Settings から通信パラメータを設定します。デフォルト設定のままで問題ありませんが、下記のパラメータになっていることを確認します。

 Baudrate : 115200
 Word : 8bit
 Parity : None
 Stopbit : 1
 (115200-8-N-1)

Nucleo は ST-LINK 経由で Virtual COM Port が出るため、USB接続だけでPCと通信できることが多いです。

NucleoをPCに接続すると、デバイスマネージャーが認識していることを確認できます。

Step2. (コード実装)

CubeMXでコード生成を行うと、USART3の初期化コードが自動生成されます。

MX_USART3_UART_Init();

次に printf の出力先をUSART3へ接続します。

STM32CubeIDE(newlib/newlib-nano等)の設定によって、printf の出力が __io_putchar() 経由になったり、_write() 経由になったりします。

実務的には どちらでも動くように両方実装しておくと、設定差に強くなります。

今回の実装ファイルは以下の通りです。配置場所はプロジェクト方針に合わせて調整してください。(ここでは plat 配下に置く例)

#ifndef PLAT_UART_STDIO_H_
#define PLAT_UART_STDIO_H_

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief printf の出力先を UART に接続する(将来拡張用)
 *
 * 現状は NOP でもOK。
 * DMA化/リングバッファ化する場合に、この関数で初期化を入れる想定。
 */
void plat_uart_stdio_init(void);

#ifdef __cplusplus
}
#endif

#endif /* PLAT_UART_STDIO_H_ */
[/text]

[text title="uart_stdio.c"]
#include "plat/uart_stdio.h"

#include "usart.h"              // huartX がここに extern 宣言される(CubeMX生成)
#include "stm32h7xx_hal.h"

#include <stdint.h>
#include <sys/unistd.h>         // STDOUT_FILENO / STDERR_FILENO

void plat_uart_stdio_init(void)
{
    // NOP(将来、リングバッファ/DMA化する場合にここで初期化)
}

/**
 * @brief 1文字送信(CubeIDE設定によって printf がここに来ることが多い)
 *
 * \n が来たら \r\n に変換して、TeraTerm等で改行が崩れないようにする
 */
int __io_putchar(int ch)
{
    uint8_t c = (uint8_t)ch;

    // 使用するUARTに合わせる(CubeMXで生成されたハンドル名)
    // 例:USART3を使っているなら huart3
    if (c == '\n') {
        uint8_t cr = '\r';
        (void)HAL_UART_Transmit(&huart3, &cr, 1, HAL_MAX_DELAY);
    }

    (void)HAL_UART_Transmit(&huart3, &c, 1, HAL_MAX_DELAY);
    return ch;
}

/**
 * @brief write() syscall(printfがこちらを使う構成もあるので保険で実装)
 *
 * newlib系では printf → _write() 経由になることがある
 * __io_putchar() と両方実装しておくと、プロジェクト設定差に強くなる
 */
int _write(int file, char *ptr, int len)
{
    if ((file != STDOUT_FILENO) && (file != STDERR_FILENO)) {
        return -1;
    }

    for (int i = 0; i < len; i++) {
        __io_putchar((unsigned char)ptr[i]);
    }
    return len;
}

上記の uart_stdio.c では、改行(\n)をCR+LF(\r\n)へ変換しています。
表示崩れ防止のためにこの変換を入れています

Step3. (動作確認)

main.c 側で uart_stdio を取り込み、printf を実行してみます。
以下は動作確認用の最小例です(差分イメージ)。

#include <stdio.h>
#include "plat/uart_stdio.h"

int main(void)
{
  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_USART3_UART_Init();

  plat_uart_stdio_init();

  printf("GN-beluga_fw boot!\n");

  while (1)
  {
    printf("Hello beluga!\n");
    HAL_Delay(1000);
  }
}

TeraTerm 側は下記の設定にします。

 ボーレート : 115200
 データ : 8bit
 パリティ : none
 ストップ : 1bit
 フロー制御 : none

表示例:

GN-beluga_fw boot!
Hello Beluga!
Hello Beluga!
Hello Beluga!

ここまで表示できれば成功です。
以降、OLEDやSDのデバッグでも printf が強力な武器になります。

Step4. (内部解説)

UARTとUSARTの違い

  • UART :非同期シリアル通信専用
  • USART:同期+非同期の両対応(UARTを含む上位互換)

STM32CubeMXで「USART」と表示されても、Asynchronous(非同期)modeで使っていれば UART として動作しています。

printfがUARTに流れる仕組み

printfは内部的に「文字を出力する関数」を呼びます。
その出口を __io_putchar() や _write() で用意することで、printfの出力をUARTから送信するように設定します。

printfの出力フローは以下のようなイメージです。
printf → (__io_putchar または _write) → HAL_UART_Transmit → UART送信 → PC表示

今回は動けばOKというスタンスで実装しましたが、実は printf の裏側では Cランタイムやリンカの仕組みが動いています。

このあたりもいずれ深掘りしてみようと思います。

改行はなぜ \r\n が必要?

使用するターミナルによって改行コードの解釈が微妙に違います。
\n だけでも動く環境もありますが、ログが1行に繋がったり、表示が崩れることがあります。

こういった環境依存トラブルを避けるため、
本記事では安全サイドに倒して \r\n(CR+LF)に統一しています。

HAL_UART_Transmitはブロッキング送信

今回の実装は分かりやすさ優先で「送信完了まで待つ(ブロッキング)」方式です。
送信中はCPUが待ち状態になるため、大量ログを出す用途では処理が重くなることがあります。

実運用ではリングバッファやDMA送信による非ブロッキング化を行うのが一般的です。
このあたりは次回以降の「ログ基盤」記事で扱う予定です。

Step5. (ハマりポイント・注意点)

今回は大きなトラブルはありませんでしたが、UARTまわりは環境依存でハマりやすいポイントが多いです。
よくある注意点をまとめておきます。

1) COMポートが出ない

  • USBがST-LINK側に挿さっているか確認
  • 充電専用ケーブルを疑う(通信不可のケーブルがある)
  • デバイスマネージャーでポートが認識されているか確認

2) 文字化けする

  • ボーレート不一致(CubeMXとTeraTermを一致させる)
  • 8-N-1(8bit/none/1bit)になっているか確認

3) 何も表示されない

  • USART初期化(MX_USART3_UART_Init)が実行されているか確認
  • huartの取り違え(huart1/2/3)を確認
  • 別のUSARTを設定していないか確認(TX/RXのピン含む)

4) 改行が崩れる

  • \n だけだと環境によっては改行にならない
  • 基本は \r\n を送る(今回の実装は \n を \r\n へ変換している)

5) printfを多用すると動作が重い

  • HAL_UART_Transmitはブロッキング送信(待ちが発生する)
  • 大量ログ時はDMA送信やバッファ化、ログレベル制御を検討する

まとめ

今回はUSARTをUART(非同期)として設定し、printfの出力をシリアル経由でPCに表示できるようにしました。マイコン内部の状態を文字として確認できるようになり、デバッグ効率が一気に上がります。

次回はこのprintfを発展させ、ログレベル付きのログ基盤へと作り込んでいきます。printfを直接使うのではなく、printf_info / printf_error / printf_fatal のようなログ基盤を作って運用しやすさを向上させます。

前回

【STM32H723ZG入門②】GPIO編|CubeMXでLチカ(LED点滅)してみる
この記事でできること CubeMXでGPIO設定できる 自動生成コードの意味が分かる HAL_GPIO_TogglePinが理解できる LEDが点滅する はじめにソフトウェア開発における組み込みC言語基礎力向上を図りたいgoldear@Ji...

次回: (準備中)

コメント