組み込みシステムとのインターフェースには様々なアプローチがありますが、最もシンプルで汎用性が高いのが、シリアルコンソールからのユーザー入力によるものです。受信したASCII文字を手動で解析して適切なデータ形式に変換するよりも、scanf()
関数を使用して自動的に変換することがよく行われています。しかし、printf()
関数と同様に、このstdio
ライブラリ関数を利用するには、いくつかの追加コードを提供する必要があります。この記事は、「STM32でprintfを簡単に使う」の関連記事として、UART周辺機器をscanf()
関数にマッピングすることで、さらに一歩踏み込んだ内容になっています。
手順
以下の手順は、すべてのSTM32CubeIDEプロジェクト(執筆時点ではバージョン1.9.0)にそのまま適用することが可能です。他の開発環境を希望される場合は、この手順を少し変更して対応する必要があります。
0. UARTインスタンスを確立する
事前準備として、UART(またはUSART)周辺機器が、仮想COMポートにデータを送受信するように適切に設定されていることを確認します。ST開発ボードでは、ST-LINKプログラマ/デバッガに接続されたUARTのRXおよびTXラインを選択することになります。詳しくは、「STM32でprintfを簡単に使う」の該当手順をご覧ください。
1. scanf()をUARTインスタンスにリダイレクトする
a.main.c
ファイルのPrivate Includesセクションに、図1に示すようにstdio
ライブラリのIncludeディレクティブを追加してください。
図1:stdio
ライブラリのインクルード
b.scanf()
関数だけをリダイレクトするには、リスト1で提供されるコードをコピーして、main.c
のPrivate Function Prototypesセクションに貼り付けてください。(ほとんどのアプリケーションで必要とされるように)scanf()
とprinf()
関数の両方をリダイレクトするには、代わりにリスト2のコードをコピーしてください。
リスト1:scanf()
をリダイレクトするための簡単なコード
#ifdef __GNUC__
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif
GETCHAR_PROTOTYPE
{
uint8_t ch = 0;
/* Clear the Overrun flag just before receiving the first character */
__HAL_UART_CLEAR_OREFLAG(&huart?);
/* Wait for reception of a character on the USART RX line and echo this
character on console */
HAL_UART_Receive(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
リスト2:printf()
とscanf()
の両方をリダイレクトする簡単なコード
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
GETCHAR_PROTOTYPE
{
uint8_t ch = 0;
/* Clear the Overrun flag just before receiving the first character */
__HAL_UART_CLEAR_OREFLAG(&huart?);
/* Wait for reception of a character on the USART RX line and echo this
character on console */
HAL_UART_Receive(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
上記リストで使用したダミーUARTハンドルは、使用する UART周辺機器のハンドルに必ず変更してください。例えば、図2はhuart2
がターゲットUARTインスタンスとして利用されていることを示しています。
c.残念ながら、STM32CubeIDEによって自動的に生成されるデフォルトのsyscalls.c
ファイルは、入力ストリームの内部バッファリングを有効にした場合、予期しない動作を起こします。 この問題の最も簡単な解決策は、scanf()
の呼び出しが行われる前にバッファリングを無効にすることです。リスト3のコード行をコピーして、main()
関数の初期化セクションに貼り付けてください。
リスト3:入力ストリームの内部バッファリングを無効にする
setvbuf(stdin, NULL, _IONBF, 0);
これで、scanf()
関数は、浮動小数点フォーマット指定子以外のすべてについて正しく機能するはずです。これらを有効にするには、次のステップに進みます。
2. 浮動小数点演算のサポートを有効にする(オプション)
printf()
関数と同様に、アプリケーションがシリアル入力から浮動小数点数を読み取ることを要求する場合、scanf()
で浮動小数点演算のサポートを明示的に有効にする必要があります。そうしないと、フォーマット文字列に浮動小数点指定子を含むscanf()
の呼び出しは、予期せぬ動作をすることになります。
a.プロジェクトエクスプローラでプロジェクト名を右クリックし、プロパティを選択します。「C/C++ Build」カテゴリの「Settings(設定)」を選択し、「Tool Settings(ツール設定)」タブの「MCU Settings(MCU設定)」を選択します。以下の図3に示すように、「Use float with scanf from newlib-nano」の隣にあるチェックボックスをオンにします。Apply and Close をクリックします。
結果
scanf()
のリファレンスページにあるサンプルコード(リスト4参照)を使って、ユーザー入力が適切に読み込まれ、フォーマットされていることを図4で確認することができます。
リスト4:テスト用のサンプルコード
char str[80]; int i;
printf(“Enter your family name: “);
scanf(”%79s”, str);
printf(“Enter your age: “);
scanf(”%d”, &i);
printf(“Mr. %s, %d years old.\n”, str, i);
printf(“Enter a hexadecimal number: “);
scanf(”%x”, &i);
printf(“You have entered %#x (%d).\n”, i, i);
図4:サンプルコードの出力図