Come si usa la funzione printf su STM32?


19

Sto cercando di capire come utilizzare la funzione printf per stampare sulla porta seriale.

La mia configurazione attuale è il codice generato da STM32CubeMX e SystemWorkbench32 con la scheda di rilevamento STM32F407 .

Vedo in stdio.h che il prototipo di printf è definito come:

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

Cosa significa? Dov'è la posizione esatta di questa definizione di funzione? Quale sarebbe il punto generale di scoprire come utilizzare questo tipo di funzione per l'output?


5
Penso che sia necessario scrivere il proprio "int _write (int file, char * ptr, int len)" per inviare l'output standard alla porta seriale come qui . Credo che ciò avvenga normalmente in un file chiamato Syscalls.c che gestisce "Rimappare chiamate di sistema". Prova a cercare su Google "Syscalls.c".
Tut

1
Questo non è un problema di elettronica. Vai a consultare il manuale della libreria del compilatore.
Olin Lathrop,

Vuoi eseguire il debug di printf tramite semihosting (tramite il debugger) o semplicemente printf in generale?
Daniel,

Come ha detto @Tut, la _write()funzione è quella che ho fatto. Dettagli nella mia risposta di seguito.
cp.engr,

questo è stato un ottimo tutorial: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Risposte:


19

Ho ottenuto il primo metodo da questa pagina lavorando sul mio STM32F072.

http://www.openstm32.org/forumthread1055

Come affermato lì,

Il modo in cui ho fatto funzionare printf (e tutte le altre funzioni stdio orientate alla console) è stato creando implementazioni personalizzate delle funzioni I / O di basso livello come _read()e _write().

La libreria GCC C chiama le seguenti funzioni per eseguire operazioni di I / O di basso livello:

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)

Queste funzioni sono implementate con la libreria GCC C come routine di stub con collegamento "debole". Se nel proprio codice appare una dichiarazione di una delle funzioni sopra indicate, la routine sostitutiva sovrascriverà la dichiarazione nella libreria e verrà utilizzata al posto della routine predefinita (non funzionale).

Ho usato STM32CubeMX per configurare USART1 ( huart1) come porta seriale. Dal momento che volevo solo printf(), avevo solo bisogno di popolare la _write()funzione, cosa che ho fatto come segue. Questo è convenzionalmente contenuto in 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);
}

Cosa succede se utilizzo un Makefile e non un IDE? Manca qualcosa per funzionare nel mio progetto Makefile ... Qualcuno potrebbe aiutare?
Tedi

@Tedi, stai usando GCC? Questa è una risposta specifica per il compilatore. Cosa hai provato e quali sono stati i risultati?
cp.engr

Sì, utilizzo GCC. Ho trovato il problema. È stata chiamata la funzione _write (). Il problema era nel mio codice per l'invio della stringa. Ho usato in modo improprio la libreria RTT di Segger ma ora funziona bene. Grazie. Funzionano tutti adesso.
Tedi,

4

_EXFUN è una macro, probabilmente contenente alcune direttive interessanti che dicono al compilatore che dovrebbe controllare la stringa di formato per essere compatibile con printf e assicurarsi che gli argomenti per printf corrispondano alla stringa di formato.

Per imparare a usare printf, suggerisco la pagina man e un po 'più googling. Scrivi alcuni semplici programmi C che utilizzano printf ed eseguili sul tuo computer prima di provare a passare al suo utilizzo sul micro.

La domanda interessante sarà "dove va il testo stampato?". Su un sistema simile a unix, va "fuori standard", ma un microcontrollore non ha nulla del genere. Le librerie di debug CMSIS possono inviare il testo printf alla porta di debug del semihosting arm, ovvero nella sessione gdb o openocd, ma non ho idea di cosa farà SystemWorkbench32.

Se non stai lavorando in un debugger, potrebbe essere più sensato utilizzare sprintf per formattare le stringhe che desideri stampare e quindi inviare quelle stringhe su una porta seriale o su qualsiasi schermo che potresti aver collegato.

Attenzione: printf e il relativo codice sono molto grandi. Questo probabilmente non ha molta importanza su un 32F407, ma è un vero problema su dispositivi con poco flash.


IIRC _EXFUN contrassegna una funzione da esportare, ovvero da utilizzare nel codice utente, e definisce la convenzione di chiamata. Sulla maggior parte delle piattaforme (incorporate) è possibile utilizzare la convenzione di chiamata predefinita. Ma su x86 con librerie dinamiche, potrebbe essere necessario definire la funzione __cdeclper evitare errori. Almeno su newlib / cygwin, _EXFUN viene utilizzato solo per impostare __cdecl.
erebos,

4

@La risposta di AdamHaun è tutto ciò di cui hai bisogno, con sprintf()cui è facile creare una stringa e inviarla. Ma se vuoi davvero una printf()tua funzione, allora Variable Argument Functions (va_list) è la strada giusta .

Con va_listuna funzione di stampa personalizzata è simile al seguente:

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

Esempio di utilizzo:

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

Si noti che mentre questa soluzione offre una comoda funzione da utilizzare, ma è più lenta rispetto all'invio di dati non elaborati o all'utilizzo uniforme sprintf(). Con alti datarati penso che non sarà sufficiente.


Un'altra opzione, e probabilmente migliore, è quella di utilizzare ST-Link, debugger SWD insieme a ST-Link Utility. E utilizzare Printf tramite il visualizzatore SWO , ecco il manuale dell'utilità ST-Link , la parte pertinente inizia a pagina 31.

Printf tramite SWO Viewer visualizza i dati printf inviati dalla destinazione tramite SWO. Permette di visualizzare alcune informazioni utili sul firmware in esecuzione.


non usi va_arg, come passi attraverso gli argomenti variabili?
iouzzr,

1

printf () fa parte (di solito) della libreria standard C. Se la tua versione della libreria viene fornita con il codice sorgente, potresti trovare un'implementazione lì.

Probabilmente sarebbe più facile usare sprintf () per generare una stringa, quindi usare un'altra funzione per inviare la stringa attraverso la porta seriale. In questo modo tutta la formattazione difficile viene eseguita per te e non devi hackerare la libreria standard.


2
printf () di solito fa parte della libreria, sì, ma non può fare nulla fino a quando qualcuno non imposta la capacità sottostante per l'output sull'hardware desiderato. Non significa "hackerare" la libreria, ma utilizzarla come previsto.
Chris Stratton,

Potrebbe essere preconfigurato per inviare testo tramite la connessione del debugger, come suggerito da Bence e William nelle loro risposte. (Non è quello che voleva l'interrogante, ovviamente.)
Adam Haun,

Ciò si verifica in genere solo a seguito del codice che si sceglie di collegare per supportare la scheda, ma indipendentemente da ciò che si modifica definendo la propria funzione di output. Il problema con la vostra proposta è che non è basato sulla comprensione di come la biblioteca è inteso per essere utilizzato - piuttosto che fare che si sta proponendo un processo a più fasi per ogni output, che tra l'altro farà un pasticcio di qualsiasi portatile esistente codice che fa le cose normalmente.
Chris Stratton,

STM32 è un ambiente indipendente. Il compilatore non deve fornire stdio.h: è del tutto facoltativo. Ma se fornisce stdio.h, deve fornire tutta la libreria. I compilatori di sistemi indipendenti che implementano printf tendono a farlo come comunicazione UART.
Lundin,

1

Per coloro che lottano, aggiungi quanto segue a 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);
}

Connettiti alla tua porta COM tramite putty e dovresti essere bravo.


1

Se si desidera un approccio diverso e non si desidera sovrascrivere le funzioni o dichiarare le proprie, è possibile utilizzare semplicemente snprintfper archiviare lo stesso obiettivo. Ma non è così elegante.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.