Comment utiliser la fonction printf sur STM32?

19

J'essaie de comprendre comment utiliser la fonction printf pour imprimer sur le port série.

Ma configuration actuelle est le code généré par STM32CubeMX et SystemWorkbench32 avec la carte de découverte STM32F407 .

Je vois dans stdio.h que le prototype printf est défini comme:

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

Qu'est-ce que ça veut dire? Où est l'emplacement exact de cette définition de fonction? Quel serait l'intérêt général de savoir comment utiliser ce type de fonction pour sortir?

user505160
la source
5
Je pense que vous devez écrire votre propre "int _write (fichier int, char * ptr, int len)" pour envoyer votre sortie standard à votre port série comme ici . Je crois que cela se fait normalement dans un fichier appelé Syscalls.c qui gère le "remappage des appels système". Essayez de googler "Syscalls.c".
Tut
1
Ce n'est pas un problème électronique. Allez voir le manuel de votre bibliothèque de compilateur.
Olin Lathrop du
Voulez-vous faire le débogage printf via semihosting (sur le débogueur) ou simplement printf en général?
Daniel
Comme l'a dit @Tut, la _write()fonction est ce que j'ai fait. Détails dans ma réponse ci-dessous.
cp.engr
ce fut un excellent tutoriel: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Réponses:

19

J'ai obtenu la première méthode de cette page sur mon STM32F072.

http://www.openstm32.org/forumthread1055

Comme indiqué ici,

La façon dont j'ai fait fonctionner printf (et toutes les autres fonctions stdio orientées console) était de créer des implémentations personnalisées des fonctions d'E / S de bas niveau comme _read()et _write().

La bibliothèque GCC C appelle les fonctions suivantes pour effectuer des E / S de bas niveau:

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)

Ces fonctions sont implémentées dans la bibliothèque GCC C en tant que routines tronquées avec une liaison "faible". Si une déclaration de l'une des fonctions ci-dessus apparaît dans votre propre code, votre routine de remplacement remplacera la déclaration dans la bibliothèque et sera utilisée à la place de la routine par défaut (non fonctionnelle).

J'ai utilisé STM32CubeMX pour configurer USART1 ( huart1) comme port série. Comme je voulais seulement printf(), je n'avais qu'à remplir la _write()fonction, ce que j'ai fait comme suit. Ceci est classiquement contenu dans 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
la source
Et si j'utilise un Makefile et non un IDE? Quelque chose manque à cela pour fonctionner dans mon projet Makefile ... Quelqu'un pourrait-il aider?
Tedi
@Tedi, utilisez-vous GCC? Il s'agit d'une réponse spécifique au compilateur. Qu'avez-vous essayé et quels ont été les résultats?
cp.engr
Oui j'utilise GCC. J'ai trouvé le problème. La fonction _write () a été appelée. Le problème était dans mon code pour l'envoi de la chaîne. J'ai mal utilisé la bibliothèque RTT de Segger mais fonctionne maintenant très bien. Merci. Tous travaillent maintenant.
Tedi
4

_EXFUN est une macro, contenant probablement des directives intéressantes qui indiquent au compilateur qu'il doit vérifier que la chaîne de format est compatible avec printf et s'assurer que les arguments de printf correspondent à la chaîne de format.

Pour apprendre à utiliser printf, je suggère la page de manuel et un peu plus de recherche sur Google. Écrivez quelques programmes C simples qui utilisent printf et exécutez-les sur votre ordinateur avant d'essayer de passer à l'utilisation sur le micro.

La question intéressante sera "où va le texte imprimé?". Sur un système de type Unix, il passe en "sortie standard", mais un microcontrôleur n'a rien de tel. Les bibliothèques de débogage CMSIS peuvent envoyer du texte printf au port de débogage de semihosting arm, c'est-à-dire dans votre session gdb ou openocd mais je n'ai aucune idée de ce que SystemWorkbench32 fera.

Si vous ne travaillez pas dans un débogueur, il peut être plus judicieux d'utiliser sprintf pour formater les chaînes que vous souhaitez imprimer, puis envoyer ces chaînes sur un port série ou sur tout affichage que vous pourriez avoir connecté.

Attention: printf et son code associé sont très volumineux. Cela n'a probablement pas beaucoup d'importance sur un 32F407, mais c'est un vrai problème sur les appareils avec peu de flash.

William Brodie-Tyrrell
la source
IIRC _EXFUN marque une fonction à exporter, c'est-à-dire à utiliser dans le code utilisateur, et définit la convention d'appel. Sur la plupart des plates-formes (intégrées), la convention d'appel par défaut peut être utilisée. Mais sur x86 avec des bibliothèques dynamiques, vous devrez peut-être définir la fonction __cdeclpour éviter les erreurs. Au moins sur newlib / cygwin, _EXFUN n'est utilisé que pour définir __cdecl.
erebos
4

@ La réponse d'AdamHaun est tout ce dont vous avez besoin, sprintf()il est facile de créer une chaîne puis de l'envoyer. Mais si vous voulez vraiment une printf()fonction de votre choix, alors les fonctions d'argument variable (va_list) sont la solution.

Avec va_listune fonction d'impression personnalisée ressemble à ceci:

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

Exemple d'utilisation:

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

Notez que bien que cette solution vous offre une fonction pratique à utiliser, elle est plus lente que l'envoi de données brutes ou l'utilisation de even sprintf(). Avec des taux de données élevés, je pense que ce ne sera pas suffisant.


Une autre option, et probablement meilleure, consiste à utiliser ST-Link, le débogueur SWD avec l'utilitaire ST-Link. Et utilisez Printf via la visionneuse SWO , voici le manuel de l' utilitaire ST-Link , la partie pertinente commence à la page 31.

Printf via SWO Viewer affiche les données printf envoyées depuis la cible via SWO. Il permet d'afficher des informations utiles sur le firmware en cours d'exécution.

Bence Kaulics
la source
vous n'utilisez pas va_arg, comment parcourez-vous les arguments variables?
iouzzr
1

printf () fait (généralement) partie de la bibliothèque standard C. Si votre version de la bibliothèque est livrée avec du code source, vous pouvez y trouver une implémentation.

Il serait probablement plus facile d'utiliser sprintf () pour générer une chaîne, puis d'utiliser une autre fonction pour envoyer la chaîne via le port série. De cette façon, tout le formatage difficile est fait pour vous et vous n'avez pas à pirater la bibliothèque standard.

Adam Haun
la source
2
printf () fait généralement partie de la bibliothèque, oui, mais il ne peut rien faire jusqu'à ce que quelqu'un configure la capacité sous-jacente de sortie vers le matériel souhaité. Ce n'est pas "pirater" la bibliothèque, mais l'utiliser comme prévu.
Chris Stratton
Il pourrait être préconfiguré d'envoyer du texte via la connexion du débogueur, comme l'ont suggéré Bence et William dans leurs réponses. (Ce n'est pas ce que le questionneur voulait, bien sûr.)
Adam Haun
Cela ne se produit généralement qu'en raison du code que vous choisissez de lier pour prendre en charge votre carte, mais peu importe que vous modifiez cela en définissant votre propre fonction de sortie. Le problème avec votre proposition est qu'elle n'est pas basée sur la compréhension de la façon dont la bibliothèque est destinée à être utilisée - plutôt que de le faire, vous proposez un processus en plusieurs étapes pour chaque sortie, ce qui, entre autres choses, gâchera tout portable existant code qui fait les choses de façon normale.
Chris Stratton
STM32 est un environnement autonome. Le compilateur n'a pas besoin de fournir stdio.h - il est entièrement facultatif. Mais s'il fournit stdio.h, il doit fournir toute la bibliothèque. Les compilateurs de systèmes autonomes qui implémentent printf ont tendance à le faire en tant que communication UART.
Lundin
1

Pour ceux qui éprouvent des difficultés, ajoutez ce qui suit à 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);
}

Connectez-vous à votre port COM via du mastic et vous devriez être bon.

Michael Brown
la source
1

Si vous voulez une approche différente et ne voulez pas écraser les fonctions ou déclarer la vôtre, vous pouvez simplement utiliser snprintfpour archiver le même objectif. Mais ce n'est pas aussi élégant.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
LeoDJ
la source