工学男子の日常

モノづくりが好きな男子の日記です。

STM32CubeIDEでprintfをシリアル通信に出す方法

タイトルのままです。

ロケット電装の基板の開発をしているのですがprintfあるとなしでは効率が大違いです。

今回の自作基板には電源供給兼Arduinoファームウェアを載せて使う用のUSBポートが実装してあるので、こちらを使えばUSBケーブルとSWDの書き込み線だけで開発できます。

GPSにも通信モジュールにも使う貴重なUARTをデバッグのために引き出すのを避けたいというのもありますが。

 

注意点としてUSBは非同期なので基幹部分に若干の変更が入るらしく、Windows側からCOMポートを開いてあげるまで停止するなど、通常と異なる動作をするようになるのでデバッグが済んだらUSBはオフにするといいかと思います。

 

またハードウェアに関してですが、基板を自作する場合DP(+)端子の配線を1.5kΩでプルアップしないとUSB機器としてPCが認識してくれないので注意してください。またUSBを使用する場合外付け水晶発振子によるHSEが必要ですので8MHzなどのクリスタルを実装してください。

 

1.CubeIDEでUSBにチェックを入れる

そのままです。

f:id:kogakudanshi:20210316195120p:plain

パラメーターの変更はありません。

2.USB_DEVICEからVPCを選択する

f:id:kogakudanshi:20210316195232p:plain

ここもパラメーターの変更はありません。

VCPとはVirtual Port COMの略で仮想COMポートのことです。

仮想なのでArduinoで必要だったボーレートをPC側で指定する必要がありません。

またCommunicationDeviceClassという呼び方もされていてライブラリ内ではCDCの略称で使われています。

3.クロックの設定をする

f:id:kogakudanshi:20210316195615p:plain

赤線のUSBのところが48MHzになるようにクロックの設定をします。

8MHzのクリスタルを使った場合PLLで9倍してUSBプリスケーラで1.5倍するといいかと思います。

4._write()関数を定義する

main.cなど目の届くところに"usb_device.h"をインクルードして以下の関数をどこでもいいので記述します。

int _write(int file, char *ptr, int len)
{
  while(CDC_Transmit_FS((uint8_t *)ptr,len) != USBD_OK);
  return len;
}

どこからこんなのきたんだ、宣言がないじゃないかと思うかもしれませんがここにあります。

f:id:kogakudanshi:20210316200930p:plain

__attribute__でコンパイラにこの関数をweak属性に指定して宣言しています。

これはマイコン等ではよく見ますが一般的なソースコードではあまり見ない機能です。

これによりweak属性がついていない先程書いたほうのコードが優先されてコンパイルされます。

 

関数の中でやっているのは見ればわかると思いますが、文字列ポインタと長さを指定して成功するまでCDCで送信を試みています。

ネット上ではwhileループしない方をよく見るのですが高速で出力を繰り返すと文字列が切れがちなのでUSBD_OKが帰るまで待機したほうが安心です。

 

実はこの_write()はprintf()やpuchar()などの標準入出力の出力関数です。

printf()でフォーマットやエスケープシーケンスの処理をした文字列がこの関数に流れてくるわけです。

そのためHAL_UART_Transmit()を使えばUARTに、HAL_SPI_Transmit()を使えばSPIに標準出力を流すことができます。

もちろん_read()もあるのですがあまり使わないのでここでは扱いません。

 

これでprintfした文字列がPCでシリアルモニタで見れるようになりました。

teratermなどでは文字化けする内容がArduinoIDEのモニタでは見れたりすることもあるので文字化けしたときは試してみてください。