Wie verwende ich die printf-Funktion bei STM32?

19

Ich versuche herauszufinden, wie die printf-Funktion zum Drucken über die serielle Schnittstelle verwendet wird.

Mein aktuelles Setup ist STM32CubeMX- generierter Code und SystemWorkbench32 mit der STM32F407-Erkennungskarte .

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 genau befindet sich diese Funktionsdefinition? Was wäre ein allgemeiner Punkt, um herauszufinden, wie diese Art von Funktion zur Ausgabe verwendet werden kann?

user505160
quelle
5
Ich denke, Sie müssen Ihr eigenes "int _write (int file, char * ptr, int len)" schreiben, um Ihre Standardausgabe an Ihre serielle Schnittstelle wie hier zu senden . Ich glaube, dies geschieht normalerweise in einer Datei namens Syscalls.c, die "System Calls Remapping" behandelt. Versuchen Sie, "Syscalls.c" zu googeln.
Tut
1
Dies ist kein Elektronikproblem. Lesen Sie das Handbuch für Ihre Compiler-Bibliothek.
Olin Lathrop
Möchten Sie das printf-Debugging über Semihosting (über den Debugger) oder nur printf im Allgemeinen ausführen?
Daniel
Wie @Tut sagte, ist die _write()Funktion das, was ich getan habe. Details in meiner Antwort unten.
cp.engr
Dies war ein großartiges Tutorial: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Antworten:

19

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 Funktionieren gebracht, indem ich benutzerdefinierte Implementierungen der einfachen E / A-Funktionen wie _read()und erstellt habe _write().

Die GCC C-Bibliothek ruft die folgenden Funktionen auf, um E / A auf niedriger Ebene 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 in der GCC C-Bibliothek als Stub-Routinen mit "schwacher" Verknüpfung implementiert. Wenn eine Deklaration einer der oben genannten Funktionen in Ihrem eigenen Code vorkommt, überschreibt Ihre Ersatzroutine die Deklaration in der Bibliothek und wird anstelle der Standardroutine (nicht funktionsfähig) 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 herkömmlicherweise 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);
}
cp.engr
quelle
Was ist, wenn ich ein Makefile und keine IDE verwende? Darin fehlt etwas, um in meinem Makefile-Projekt zu funktionieren ... Kann mir jemand helfen?
Tedi
@ Tedi, verwenden Sie GCC? Dies ist eine compilerspezifische Antwort. Was haben Sie versucht und was waren die Ergebnisse?
cp.engr
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 Seggers RTT-Bibliothek missbraucht, aber jetzt funktioniert es einwandfrei. Vielen Dank. Alles funktioniert jetzt.
Tedi
4

_EXFUN ist ein Makro, das wahrscheinlich einige interessante Anweisungen enthält, die dem Compiler mitteilen, dass er die Formatzeichenfolge auf PrintF-Kompatibilität prüfen und sicherstellen soll, dass die Argumente für PrintF mit der Formatzeichenfolge übereinstimmen.

Um zu lernen, wie man printf benutzt, schlage ich die Manpage und etwas mehr Googeln vor. 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 lautet: "Wohin geht der gedruckte Text?". Auf einem Unix-ähnlichen System geht es nach "Standard Out", aber ein Mikrocontroller hat so etwas nicht. Die CMSIS-Debug-Bibliotheken können printf-Text an den Debug-Port von arm semihosting 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 zum Formatieren der zu druckenden Zeichenfolgen zu verwenden und diese Zeichenfolgen dann über eine serielle Schnittstelle oder an eine von Ihnen angehängte Anzeige zu senden.

Achtung: printf und der zugehörige Code sind sehr groß. Auf einem 32F407 spielt das wahrscheinlich keine große Rolle, aber auf Geräten mit wenig Blitz ist es ein echtes Problem.

William Brodie-Tyrrell
quelle
IIRC _EXFUN markiert eine zu exportierende, dh im Benutzercode zu verwendende Funktion und definiert die Aufrufkonvention. Auf den meisten (eingebetteten) Plattformen kann die Standardaufrufkonvention verwendet werden. Bei x86 mit dynamischen Bibliotheken müssen Sie möglicherweise die Funktion definieren __cdecl, um Fehler zu vermeiden. Zumindest auf newlib / cygwin wird _EXFUN nur zum Setzen verwendet __cdecl.
Erebos
4

@ AdamHauns Antwort ist alles, was Sie brauchen. sprintf()Es ist einfach, einen String zu erstellen und ihn dann zu senden. Aber wenn Sie wirklich eine eigene printf()Funktion wollen , dann sind variable Argumentfunktionen (va_list) der richtige Weg.

Mit va_listeiner benutzerdefinierten Druckfunktion sieht das folgendermaßen 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 Sie mit dieser Lösung zwar bequeme Funktionen verwenden können, diese jedoch langsamer sind als das Senden von Rohdaten oder die Verwendung von Even sprintf(). Mit hohen Datenraten denke ich, wird es nicht ausreichen.


Eine weitere und wahrscheinlich bessere Option ist die Verwendung von ST-Link, dem SWD-Debugger, zusammen mit dem ST-Link-Dienstprogramm. Verwenden Sie Printf über den SWO-Viewer . Hier finden Sie das Handbuch des ST-Link-Dienstprogramms . Der entsprechende Teil beginnt auf Seite 31.

Der Printf via SWO Viewer zeigt die Printf-Daten an, die vom Ziel über SWO gesendet wurden. Hier können einige nützliche Informationen zur laufenden Firmware angezeigt werden.

Bence Kaulics
quelle
Sie verwenden va_arg nicht. Wie gehen Sie durch die Variablenargumente?
iouzzr
1

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, mit sprintf () eine Zeichenfolge zu generieren und dann mit einer anderen Funktion die Zeichenfolge über den seriellen Anschluss zu senden. Auf diese Weise wird die gesamte schwierige Formatierung für Sie erledigt und Sie müssen die Standardbibliothek nicht hacken.

Adam Haun
quelle
2
printf () ist normalerweise Teil der Bibliothek, aber es kann nichts tun , bis jemand die zugrunde liegende Fähigkeit zur Ausgabe auf die gewünschte Hardware eingerichtet hat. Das heißt nicht, die Bibliothek zu "hacken", sondern sie bestimmungsgemäß zu nutzen.
Chris Stratton
Möglicherweise ist es vorkonfiguriert, Text über die Debugger-Verbindung zu senden, wie Bence und William in ihren Antworten vorgeschlagen haben. (Das wollte der Fragesteller natürlich nicht.)
Adam Haun
Dies geschieht normalerweise nur aufgrund des Codes, den Sie zur Unterstützung Ihres Boards einbinden. Sie können dies jedoch ändern, indem Sie Ihre eigene Ausgabefunktion definieren. Das Problem mit dem Vorschlag ist , dass es nicht basiert auf dem Verständnis , wie die Bibliothek soll verwendet werden - anstatt zu tun , dass Sie einen mehrstufigen Prozess für jede ouput sind vorzuschlagen, die unter anderem ein Durcheinander von jedem vorhandenen tragbar Code, der die Dinge auf die normale Weise macht.
Chris Stratton
STM32 ist eine freistehende Umgebung. Der Compiler muss nicht stdio.h bereitstellen - es ist völlig optional. Wenn jedoch stdio.h bereitgestellt wird , muss die gesamte Bibliothek bereitgestellt werden. Die freistehenden Systemcompiler, die printf implementieren, tendieren dazu, dies als UART-Kommunikation zu tun.
Lundin
1

Fügen Sie für diejenigen, die Probleme haben, 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 mit Ihrem COM-Port über Kitt und Sie sollten gut sein.

Michael Brown
quelle
1

Wenn Sie einen anderen Ansatz snprintfverfolgen und keine Funktionen überschreiben oder eigene deklarieren möchten, können Sie einfach verwenden , 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);
LeoDJ
quelle