Wie verwende ich die printf-Funktion auf STM32?

Ich versuche herauszufinden, wie man die printf-Funktion verwendet, um an die serielle Schnittstelle zu drucken.

Mein aktuelles Setup ist STM32CubeMX generierter Code und SystemWorkbench32 mit dem STM32F407 Discovery Board .

Ich sehe in stdio.h, dass der printf-Prototyp wie folgt definiert ist:

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

Was bedeutet es? Wo ist die genaue Position dieser Funktionsdefinition? Was wäre der allgemeine Punkt, um herauszufinden, wie man diese Art von Funktion zur Ausgabe verwendet?

Ich denke, Sie müssen Ihr eigenes "int _write( int file, char *ptr, int len ​​)" schreiben, um Ihre Standardausgabe an Ihre serielle Schnittstelle zu senden, wie hier . Ich glaube, dies geschieht normalerweise in einer Datei namens Syscalls.c, die "System Calls Remapping" behandelt. Versuchen Sie, "Syscalls.c" zu googeln.
Möchten Sie Printf-Debugging über Semihosting (über den Debugger) oder einfach nur Printf im Allgemeinen durchführen?
Wie @Tut sagte, ist die _write()Funktion das, was ich getan habe. Details in meiner Antwort unten.
das war ein tolles Tutorial: youtu.be/Oc58Ikj-lNI

Antworten (6)

Ich habe die erste Methode von dieser Seite erhalten, die an meinem STM32F072 arbeitet.

http://www.openstm32.org/forumthread1055

Wie dort angegeben,

Ich habe printf (und alle anderen konsolenorientierten stdio-Funktionen) zum Laufen gebracht, indem ich benutzerdefinierte Implementierungen der Low-Level-I/O-Funktionen wie _read()und erstellt habe _write().

Die GCC-C-Bibliothek ruft die folgenden Funktionen auf, um Low-Level-E/A auszuführen:

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)

Diese Funktionen sind innerhalb der GCC-C-Bibliothek als Stub-Routinen mit "schwacher" Verknüpfung implementiert. Wenn eine Deklaration einer der oben genannten Funktionen in Ihrem eigenen Code erscheint, überschreibt Ihre Ersatzroutine die Deklaration in der Bibliothek und wird anstelle der standardmäßigen (nicht funktionalen) Routine verwendet.

Ich habe STM32CubeMX verwendet, um USART1 ( huart1) als serielle Schnittstelle einzurichten. Da ich nur wollte printf(), musste ich nur die _write()Funktion füllen, was ich wie folgt tat. Dies ist üblicherweise in enthalten 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);
}
Was ist, wenn ich ein Makefile und keine IDE verwende? Irgendetwas fehlt, um in meinem Makefile-Projekt zu funktionieren ... Könnte jemand helfen?
@Tedi, verwendest du GCC? Dies ist eine Compiler-spezifische Antwort. Was haben Sie versucht, und was waren die Ergebnisse?
Ja, ich benutze GCC. Ich habe das Problem gefunden. Die Funktion _write() wurde aufgerufen. Das Problem lag in meinem Code zum Senden der Zeichenfolge. Ich habe die RTT-Bibliothek von Segger missbraucht, aber jetzt funktioniert sie einwandfrei. Vielen Dank. Alle arbeiten jetzt.

Die Antwort von @ AdamHaun ist alles, was Sie brauchen. sprintf()Es ist einfach, eine Zeichenfolge zu erstellen und sie dann zu senden. Aber wenn Sie wirklich eine printf()eigene Funktion wollen, dann sind Variable Argument Functions (va_list) der richtige Weg.

Mit va_listeiner benutzerdefinierten Druckfunktion sieht das wie folgt aus:

#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);
}

Anwendungsbeispiel:

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);

Beachten Sie, dass diese Lösung Ihnen zwar praktische Funktionen bietet, aber langsamer ist als das Senden von Rohdaten oder die Verwendung von sogar sprintf(). Bei hohen Datenraten wird es meiner Meinung nach nicht ausreichen.


Eine andere und wahrscheinlich bessere Option ist die Verwendung von ST-Link, SWD-Debugger zusammen mit dem ST-Link-Dienstprogramm. Und verwenden Sie Printf über den SWO-Viewer , hier ist das Handbuch des ST-Link-Dienstprogramms , der relevante Teil beginnt auf Seite 31.

Der Printf via SWO Viewer zeigt die vom Ziel durch SWO gesendeten printf-Daten an. Es ermöglicht die Anzeige einiger nützlicher Informationen über die laufende Firmware.

Sie verwenden va_arg nicht, wie gehen Sie schrittweise durch die Variablenargumente?

_EXFUN ist ein Makro, das wahrscheinlich einige interessante Direktiven enthält, die dem Compiler mitteilen, dass er den Format-String auf printf-Kompatibilität prüfen und sicherstellen soll, dass die Argumente für printf mit dem Format-String übereinstimmen.

Um zu lernen, wie man printf verwendet, empfehle ich die Manpage und etwas mehr Googeln. Schreiben Sie einige einfache C-Programme, die printf verwenden, und führen Sie sie auf Ihrem Computer aus, bevor Sie versuchen, es auf dem Mikro zu verwenden.

Die interessante Frage wird lauten: „Wohin kommt der gedruckte Text?“. Auf einem Unix-ähnlichen System geht es auf "Standard out", aber ein Mikrocontroller hat so etwas nicht. Die CMSIS-Debug-Bibliotheken können printf-Text an den Arm-Semihosting-Debug-Port senden, dh in Ihre gdb- oder openocd-Sitzung, aber ich habe keine Ahnung, was SystemWorkbench32 tun wird.

Wenn Sie nicht in einem Debugger arbeiten, ist es möglicherweise sinnvoller, sprintf zu verwenden, um die Zeichenfolgen zu formatieren, die Sie drucken möchten, und diese Zeichenfolgen dann über eine serielle Schnittstelle oder an eine beliebige Anzeige zu senden, die Sie möglicherweise angeschlossen haben.

Achtung: printf und der zugehörige Code sind sehr groß. Das macht bei einem 32F407 wahrscheinlich nicht viel aus, ist aber bei Geräten mit wenig Blitz ein echtes Problem.

IIRC _EXFUN markiert eine Funktion, die exportiert werden soll, dh in Benutzercode verwendet werden soll, und definiert die Aufrufkonvention. Auf den meisten (eingebetteten) Plattformen kann die Standard-Aufrufkonvention verwendet werden. Aber auf x86 mit dynamischen Bibliotheken müssen Sie die Funktion möglicherweise so definieren __cdecl, um Fehler zu vermeiden. Zumindest auf newlib/cygwin wird _EXFUN nur verwendet, um __cdecl.

Wenn Sie einen anderen Ansatz wünschen und keine Funktionen überschreiben oder eigene deklarieren möchten, können Sie einfach verwenden snprintf, um dasselbe Ziel zu erreichen. Aber es ist nicht so elegant.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
Dies ist super einfach und ermöglicht es Ihnen, den Puffer für tx dma wiederzuverwenden.

printf() ist (normalerweise) Teil der C-Standardbibliothek. Wenn Ihre Version der Bibliothek mit Quellcode geliefert wird, finden Sie dort möglicherweise eine Implementierung.

Es wäre wahrscheinlich einfacher, sprintf() zu verwenden, um einen String zu generieren, und dann eine andere Funktion zu verwenden, um den String über die serielle Schnittstelle zu senden. Auf diese Weise wird die ganze schwierige Formatierung für Sie erledigt und Sie müssen die Standardbibliothek nicht hacken.

printf() ist normalerweise Teil der Bibliothek, ja, aber es kann nichts tun , bis jemand die zugrunde liegende Fähigkeit zur Ausgabe an die gewünschte Hardware einrichtet. Das bedeutet nicht, die Bibliothek zu „hacken“, sondern sie bestimmungsgemäß zu nutzen.
Es könnte vorkonfiguriert sein, Text über die Debugger-Verbindung zu senden, wie Bence und William in ihren Antworten vorgeschlagen haben. (Das wollte der Fragesteller natürlich nicht.)
Das würde normalerweise nur als Ergebnis von Code passieren, den Sie zur Unterstützung Ihres Boards verlinken, aber unabhängig davon ändern Sie dies, indem Sie Ihre eigene Ausgabefunktion definieren. Das Problem mit Ihrem Vorschlag besteht darin, dass er nicht darauf basiert, zu verstehen, wie die Bibliothek verwendet werden soll - anstatt dass Sie einen mehrstufigen Prozess für jede Ausgabe vorschlagen, der unter anderem alle vorhandenen tragbaren Geräte durcheinander bringt Code, der die Dinge auf die normale Weise erledigt.
STM32 ist eine freistehende Umgebung. Der Compiler muss stdio.h nicht bereitstellen – es ist völlig optional. Aber wenn es stdio.h bereitstellt, muss es die gesamte Bibliothek bereitstellen. Die Compiler für freistehende Systeme, die printf implementieren, tun dies in der Regel als UART-Kommunikation.

Für diejenigen, die Probleme haben, fügen Sie Folgendes zu syscalls.c hinzu:

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);
}

Verbinden Sie sich über Putty mit Ihrem COM-Port und Sie sollten gut sein.