STM32+HALのUSARTでprintf(float),scanf, CMSISを有効可

2020年6月11日

STM32マイコンのペリフェラル関連記事を一覧にまとめました。

stm32 + makefileでのprintf,scanf,CMSIS(FPU)の設定の仕方をやっていきたいと思います。

私が使用するマイコンはSTM32F405RGTです。FPUの設定は、makefileで使用する設定が別途されています。この記事では、FPUを使用したarmのmathライブラリの使用方法を書いています。

 




STM32CubeMxの設定

 

USARTの設定

Project Managerから.c,.hファイルに分けるというところにチェックを入れたのちコード生成をしてもらいましょう。USARTのボーレートはTeraTermなどのターミナルアプリケーションと同じ速度(bit/sec)に設定する必要があるので、各自の環境にあった値を設定してください。

プロジェクト名などを決めたらコードを生成してもらいましょう。

続いて、FPUの設定を行っていきます。

 

CMSISの設定

Float Processing Unit:浮動小数点ユニットのことです。

今回使用するstm32F4シリーズには上記のFPUが搭載されており、機能を使用して計算するのとしないのとでは速度が圧倒的に速くなるそうなので有効にしたいですね。

CMSISを有効にすることで"arm_math.h"に定義されている関数や数値を使用することができるようになります。

はじめに、プロジェクトの中の以下のディレクトリに

/Driver/CMSIS/Lib/GCC/libarm_cortexM4lf_math.a

があるかどうかを確認してください。

ない場合は、stm32cubemxのファームウェアがダウンロードされているディレクトリに移動して"libarm_coretexM4lf_math.a"をコピーしにいきます。stm32cubemxのファームウェア直下のマイコンのシリーズのファームのディレクトリ内にありました。

私の環境では、以下のディレクトリにありました。

/mnt/C/Users/alcne/STM32Cube/Repository/STM32Cube_FW_F4_V1.22.0/Drivers/CMSIS/Lib/GCC

続いて、makefileを変更します。

# CFLAGS内の最後の行に-DARM_MATH_CM4 を追加
# C defines
C_DEFS =  
-DUSE_HAL_DRIVER 
-DSTM32F405xx 
-DARM_MATH_CM4 

#LDFLAGS内を以下のように変更
# libraries
LIBS = -lc -lm -lnosys -larm_cortexM4lf_math
LIBDIR = -L Drivers/CMSIS/Lib/GCC/

libarm_cortexM4lf_math.aは静的ライブラリなので、プログラムにリンクする必要があるため、リンカーフラグに追加することでリンクすることができました。

これで、コンパイルをして無事コンパイルができればCMSIS(FPU)を有効化できたと思います。

 

printfの設定

printfを表示するためには、syscalls.cというc言語ファイルが必要になります。STM32のサンプルコードのディレクトリ内にあるので探して、コピーをして持ってきてください。

syscalls.cをSrc直下に置き、makefileのC_SOURCESの一番したに追加してコンパイル対象のファイルとして追加してください。printfのfloat型を表示できるようにリンカーフラグに-u _printf_floatを追加する必要があるので追加も一緒にします。

#省略
C_SOURCES =  
Src/~.c 
Src/syscalls.c #一番下に追加

#LDFLAGS内LDFLAGSに追加
 LDFLAGS = 省略 -u _printf_float

 

syscalls.cの中身を見ていきます。printfはwriteシステムコールを使用するそうです。

writeの実装がどうなっているのか確認すると、

int _write(int file, char *ptr, int len)
{
	int DataIdx;

	for (DataIdx = 0; DataIdx < len; DataIdx++)
	{
		__io_putchar(*ptr++);
	}
	return len;
}

__io_putcharという関数が使われていることがわかります。

これは、ファイルの上部で

extern int __io_putchar(int ch) __attribute__((weak));

とありますが実体がないので実体の定義をしたいと思います。

定義内容は、_write内で文字を一文字ずつ送っているということも読み取れるので、UARTで文字を1文字ずつで送るプログラムを書けばいいということがわかります。

HALドライバでUARTを一文字ずつ送ることは、HAL_UART_Transmitという関数で可能です。

上記のことを踏まえてmain.cのuser code begin includeとendの間にソースを追加していきます。

#include <stdio.h>
#include <stdint.h>

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
void __io_putchar(uint8_t ch) {
HAL_UART_Transmit(&huart3/*使用しているusartに変更すること*/, &ch, 1, 1);
}

printfを使用するために、標準入出力ファイルをインクルードして、マクロ定義でコンパイラごとにPUTCHAR_PROTOTYPEの読み替えが聞くようになってます。

理由としては、stm32のgccの定義がGNUCと違うためです。

printfのサンプルコードは以下のようになりました。

// includeに追加
#include "arm_math.h"

// initを省略

  /* USER CODE BEGIN 2 */
  setbuf(stdout, NULL ); // printfを使用する前にバッファのリセットが必要
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  while (1)
  {
    /* USER CODE END WHILE */
    printf("pi = %fr", PI ); // arm_math.hに定義されているPIを使用
  }
  /* USER CODE END 3 */

 

 

scanfの実装

scanfはシステムコールreadを使用するそうなのでreadの実装を見てみると

int _read(int file, char *ptr, int len)
{
	int DataIdx;

	for (DataIdx = 0; DataIdx < len; DataIdx++)
	{
		*ptr++ = __io_getchar();
	}

	return len;
}

となっているので、printfと同様に__io_getchar()を定義しなおして使用することができるようになります。

scanfも使えるようにmain.cに追加でコードを書くと次のようになりました。

 

scanfでfloat型を読み込みたいときはリンカーフラグに-u scanf_floatを追加することでできました。

サンプルコード

  // main.c initのあとに実装
 /* USER CODE BEGIN 2 */

 setbuf( stdout, NULL );
  setbuf( stdin, NULL );

  float a = 0.0f;
  printf("hello world! , pi = %frn", PI );

  printf("input : r" );
  scanf( "%f",&a );

  printf( "rninput a = %frn", a );

 /* USER CODE END 2 */
サンプルコードの実行結果

 

さいごに

マイコンで標準入出力の関数が使えるっていいですね。

PCと同じ感覚でプログラムを書くことができてとても便利です。

参考文献

UM1725 User Manual