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 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);
}
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_list
einer 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.
_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.
__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);
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.
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.
Tut
Daniel
vgl.engr
_write()
Funktion das, was ich getan habe. Details in meiner Antwort unten.Josef Pigetti