STM32でprintf関数を使用するにはどうすればよいですか?


19

printf関数を使用してシリアルポートに印刷する方法を見つけようとしています。

私の現在のセットアップは、STM32CubeMXで生成されたコードと、STM32F407ディスカバリーボードを備えたSystemWorkbench32 です。

stdio.hでは、printfプロトタイプが次のように定義されていることがわかります。

int _EXFUN(printf, (const char *__restrict, ...)
               _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

どういう意味ですか?この関数定義の正確な場所はどこですか?この種の関数を使用して出力する方法を見つける一般的なポイントは何ですか?


5
ここなどのシリアルポートに標準出力を送信するには、独自の「int _write(int file、char * ptr、int len)」を記述する必要があると思います。これは通常、「System Calls Remapping」を処理するSyscalls.cというファイルで行われると思います。グーグル「Syscalls.c」を試してください。
ツタンカーメン

1
これは電子機器の問題ではありません。コンパイラライブラリのマニュアルを参照してください。
オリンラスロップ

セミホスティング(デバッガー経由)または一般的なprintfを介してprintfデバッグを行いたいですか?
ダニエル

@Tutが言ったように、_write()関数は私がやったことです。以下の私の答えの詳細。
cp.engr

これは素晴らしいチュートリアルでした:youtu.be/Oc58Ikj-lNI
ジョセフ・ピゲッティ

回答:


19

STM32F072で動作するこのページから最初のメソッドを取得しました。

http://www.openstm32.org/forumthread1055

そこに述べられているように、

私は仕事にのprintf(および他のすべてのコンソール指向のstdio関数)を得た方法は、のような低レベルのI / O機能のカスタム実装作成することによってだった_read()とします_write()

GCC Cライブラリは、低レベルI / Oを実行するために次の関数を呼び出します。

int _read(int file, char *data, int len)
int _write(int file, char *data, int len)
int _close(int file)
int _lseek(int file, int ptr, int dir)
int _fstat(int file, struct stat *st)
int _isatty(int file)

これらの関数は、「弱い」リンケージを持つスタブルーチンとしてGCC Cライブラリに実装されます。上記の関数のいずれかの宣言が独自のコードに含まれている場合、代替ルーチンはライブラリ内の宣言をオーバーライドし、デフォルト(非機能)ルーチンの代わりに使用されます。

STM32CubeMXを使用して、USART1(huart1)をシリアルポートとしてセットアップしました。私が欲しかったので、私は次のように関数printf()を追加するだけでした_write()。これは通常、に含まれていsyscalls.cます。

#include  <errno.h>
#include  <sys/unistd.h> // STDOUT_FILENO, STDERR_FILENO

int _write(int file, char *data, int len)
{
   if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
   {
      errno = EBADF;
      return -1;
   }

   // arbitrary timeout 1000
   HAL_StatusTypeDef status =
      HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);

   // return # of bytes written - as best we can tell
   return (status == HAL_OK ? len : 0);
}

IDEではなくMakefileを使用するとどうなりますか?Makefileプロジェクトで動作するために、これから何かが欠けています...誰か助けてもらえますか?
テディ

@ Tedi、GCCを使用していますか?これはコンパイラ固有の回答です。何を試しましたか、結果はどうでしたか?
cp.engr

はい、GCCを使用しています。問題が見つかりました。_write()関数が呼び出されました。問題は、文字列を送信するためのコードにありました。SeggerのRTTライブラリを誤用しましたが、現在は正常に動作します。ありがとう。すべてが動作しています。
テディ

4

_EXFUNはマクロであり、おそらくprintf互換性があるかどうかformat-stringをチェックし、printfへの引数がformat stringと一致することを確認する必要があることをコンパイラに伝えるいくつかの興味深いディレクティブが含まれています。

printfの使用方法を学ぶには、manページともう少しグーグルをお勧めします。microでの使用に移行する前に、printfを使用する簡単なCプログラムをいくつか作成し、コンピューターで実行します。

興味深い質問は、「印刷されたテキストはどこに行きますか?」です。UNIXのようなシステムでは、「標準出力」になりますが、マイクロコントローラーにはそのようなものはありません。CMSISデバッグライブラリは、printfテキストをアームセミホスティングデバッグポート、つまりgdbまたはopenocdセッションに送信できますが、SystemWorkbench32が何をするのかわかりません。

デバッガで作業していない場合は、sprintfを使用して印刷する文字列をフォーマットし、それらの文字列をシリアルポートまたは接続しているディスプレイに送信する方が合理的です。

注意:printfとその関連コードは非常に大きいです。これはおそらく32F407ではそれほど重要ではありませんが、フラッシュの少ないデバイスでは実際の問題です。


IIRC _EXFUNは、エクスポートされる関数、つまりユーザーコードで使用される関数をマークし、呼び出し規約を定義します。ほとんどの(組み込み)プラットフォームでは、デフォルトの呼び出し規則を使用できます。ただし、動的ライブラリを備えたx86では、__cdeclエラーを回避するために関数を定義する必要があります。少なくともnewlib / cygwinでは、_EXFUNはsetにのみ使用され__cdeclます。
エレボス

4

@AdamHaunの答えはあなたが必要とするすべてでsprintf()、文字列を作成して送信するのは簡単です。しかし、本当にprintf()独自の関数が必要な場合は、可変引数関数(va_list)最適です。

va_listカスタム印刷機能を使用すると、次のようになります。

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void vprint(const char *fmt, va_list argp)
{
    char string[200];
    if(0 < vsprintf(string,fmt,argp)) // build string
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
    }
}

void my_printf(const char *fmt, ...) // custom printf() function
{
    va_list argp;
    va_start(argp, fmt);
    vprint(fmt, argp);
    va_end(argp);
}

使用例:

uint16_t year = 2015;
uint8_t month = 12;
uint8_t day   = 18;
char* date = "date";

// "Today's date: 2015-12-18"
my_printf("Today's %s: %d-%d-%d\r\n", date, year, month, day);

このソリューションは便利な機能を提供しますが、生データを送信したり、さらにはを使用するよりも遅いことに注意してくださいsprintf()。データレートが高いと、それだけでは不十分だと思います。


別のオプション、おそらくより良いオプションは、ST-Link、SWDデバッガーとST-Linkユーティリティを使用することです。そして使用のprintf経由SWOビューアを、ここでの手動でST-Linkのユーティリティは、関連部分は31ページを開始します。

SWOビューアーを介したPrintfは、SWOを介してターゲットから送信されたprintfデータを表示します。実行中のファームウェアに関する有用な情報を表示できます。


va_argを使用しない場合、変数引数をどのようにステップスルーしますか?
iouzzr

1

printf()は(通常)C標準ライブラリの一部です。ライブラリのバージョンにソースコードが付属している場合は、そこに実装を見つけることができます。

おそらく、sprintf()を使用して文字列を生成し、別の関数を使用して文字列をシリアルポート経由で送信する方が簡単でしょう。そうすれば、難しいフォーマットはすべてあなたのために行われ、標準ライブラリをハックする必要はありません。


2
printf()は通常、ライブラリの一部です。ただし、必要なハードウェアに出力する基本的な機能を誰かがセットアップするまで、何もできませ。それはライブラリを「ハッキング」するのではなく、意図したとおりに利用することです。
クリスストラットン

BenceとWilliamが回答で示唆したように、デバッガー接続を介してテキストを送信するように事前構成されている場合があります。(もちろん、質問者が望んだことではありません。)
アダムハウン

これは通常、ボードをサポートするためにリンクすることを選択したコードの結果としてのみ発生しますが、独自の出力関数を定義することでそれを変更します。あなたの提案の問題点は、それがライブラリがされる方法を理解に基づいていないということである意味あなたは、既存のポータブルの混乱を行いますとりわけ各ouputを、のための多段階のプロセスを提案していることをやるのではなく-使用します通常の方法でコードを実行します。
クリスストラットン

STM32は独立した環境です。コンパイラはstdio.hを提供する必要はありません-それは完全にオプションです。ただし、stdio.hを提供する場合は、すべてのライブラリを提供する必要があります。printfを実装する自立型システムコンパイラは、UART通信として実行する傾向があります。
ランディン

1

苦労している人のために、syscalls.cに次を追加します。

extern UART_HandleTypeDef huart1; // access huart1 instance
//extern int __io_putchar(int ch) __attribute__((weak)); // comment this out

__attribute__((weak)) int __io_putchar(int ch)
{
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return (status == HAL_OK ? ch : 0);
}

パテ経由でCOMポートに接続すれば、うまくいくはずです。


1

別のアプローチが必要で、関数を上書きしたり独自に宣言したくない場合は、単にsnprintf同じ目標を達成するために使用できます。しかし、それほどエレガントではありません。

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.