¿Cómo uso la función printf en STM32?

19

Estoy tratando de descubrir cómo usar la función printf para imprimir en el puerto serie.

Mi configuración actual es código generado por STM32CubeMX y SystemWorkbench32 con la placa de descubrimiento STM32F407 .

Veo en stdio.h que el prototipo printf se define como:

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

Qué significa eso? ¿Dónde está la ubicación exacta de esta definición de función? ¿Cuál sería el punto general de descubrir cómo usar este tipo de función para generar?

usuario505160
fuente
55
Creo que necesita escribir su propio "int _write (int file, char * ptr, int len)" para enviar su salida estándar a su puerto serie como aquí . Creo que esto normalmente se hace en un archivo llamado Syscalls.c que maneja "Reasignación de llamadas del sistema". Intenta buscar en Google "Syscalls.c".
Tut
1
Esto no es un problema electrónico. Ve a ver el manual de tu biblioteca compiladora.
Olin Lathrop
¿Desea realizar la depuración de printf mediante semihosting (sobre el depurador) o simplemente printf en general?
Daniel
Como dijo @Tut, la _write()función es lo que hice. Detalles en mi respuesta a continuación.
cp.engr
este fue un gran tutorial: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Respuestas:

19

Obtuve el primer método de esta página trabajando en mi STM32F072.

http://www.openstm32.org/forumthread1055

Como se indica allí,

La forma en que hice que printf (y todas las demás funciones estándar orientadas a la consola) funcionara fue creando implementaciones personalizadas de las funciones de E / S de bajo nivel como _read()y _write().

La biblioteca GCC C realiza llamadas a las siguientes funciones para realizar E / S de bajo nivel:

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)

Estas funciones se implementan dentro de la biblioteca GCC C como rutinas de código auxiliar con enlace "débil". Si aparece una declaración de cualquiera de las funciones anteriores en su propio código, su rutina sustituta anulará la declaración en la biblioteca y se utilizará en lugar de la rutina predeterminada (no funcional).

Utilicé STM32CubeMX para configurar USART1 ( huart1) como puerto serie. Como solo quería printf(), solo necesitaba llenar la _write()función, lo que hice de la siguiente manera. Esto está contenido convencionalmente en 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
fuente
¿Qué sucede si uso un Makefile y no un IDE? Algo le falta a esto para trabajar en mi proyecto Makefile ... ¿Alguien podría ayudarme?
Tedi
@Tedi, ¿estás usando GCC? Esta es una respuesta específica del compilador. ¿Qué has intentado y cuáles fueron los resultados?
cp.engr
Sí, uso GCC. Encontré el problema. Se llamó a la función _write (). El problema estaba en mi código para enviar la cadena. Utilicé mal la biblioteca RTT de Segger, pero ahora funciona bien. Gracias. Todos trabajando ahora.
Tedi
4

_EXFUN es una macro, que probablemente contenga algunas directivas interesantes que le dicen al compilador que debe verificar que la cadena de formato sea compatible con printf y garantizar que los argumentos para printf coincidan con la cadena de formato.

Para aprender a usar printf, sugiero la página de manual y un poco más de google. Escriba algunos programas C simples que usen printf y ejecútelos en su computadora antes de intentar hacer la transición para usarlos en el micro.

La pregunta interesante será "¿a dónde va el texto impreso?". En un sistema similar a Unix, pasa a "salida estándar", pero un microcontrolador no tiene tal cosa. Las bibliotecas de depuración CMSIS pueden enviar texto printf al puerto de depuración semihosting del brazo, es decir, a su sesión gdb u openocd, pero no tengo idea de lo que hará SystemWorkbench32.

Si no está trabajando en un depurador, podría tener más sentido usar sprintf para formatear las cadenas que desea imprimir y luego enviar esas cadenas a través de un puerto serie o a cualquier pantalla que haya conectado.

Cuidado: printf y su código relacionado son muy grandes. Eso probablemente no importa mucho en un 32F407, pero es un problema real en dispositivos con poco flash.

William Brodie-Tyrrell
fuente
IIRC _EXFUN marca una función para exportar, es decir, para usarse en el código de usuario, y define la convención de llamada. En la mayoría de las plataformas (integradas) se puede usar la convención de llamada predeterminada. Pero en x86 con bibliotecas dinámicas, es posible que deba definir la función __cdeclpara evitar errores. Al menos en newlib / cygwin, _EXFUN solo se usa para configurar __cdecl.
erebos
4

La respuesta de @ AdamHaun es todo lo que necesita, ya sprintf()que es fácil crear una cadena y luego enviarla. Pero si realmente quieres una printf()función propia, entonces las Funciones de Argumento Variable (va_list) son el camino.

Con va_listuna función de impresión personalizada se ve así:

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

Ejemplo de uso:

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

Tenga en cuenta que, si bien esta solución le brinda una función conveniente para usar, es más lenta que enviar datos sin procesar o incluso usar sprintf(). Con altas tasas de datos, creo que no será suficiente.


Otra opción, y probablemente una mejor opción, es utilizar ST-Link, depurador de SWD junto con ST-Link Utility. Y use Printf a través del visor SWO , aquí está el manual de la utilidad ST-Link , la parte relevante comienza en la página 31.

Printf via SWO Viewer muestra los datos de printf enviados desde el objetivo a través de SWO. Permite mostrar información útil sobre el firmware en ejecución.

Bence Kaulics
fuente
no usas va_arg, ¿cómo pasas por los argumentos variables?
iouzzr
1

printf () es (generalmente) parte de la biblioteca estándar de C. Si su versión de la biblioteca viene con código fuente, es posible que encuentre una implementación allí.

Probablemente sería más fácil usar sprintf () para generar una cadena, luego usar otra función para enviar la cadena a través del puerto serie. De esa manera, todo el formato difícil se realiza por usted y no tiene que piratear la biblioteca estándar.

Adam Haun
fuente
2
printf () generalmente es parte de la biblioteca, sí, pero no puede hacer nada hasta que alguien configure la capacidad subyacente para generar el hardware deseado. Eso no es "piratear" la biblioteca, sino utilizarla según lo previsto.
Chris Stratton el
Podría estar preconfigurado para enviar mensajes de texto a través de la conexión del depurador, como Bence y William sugirieron en sus respuestas. (Eso no es lo que el interrogador quería, por supuesto.)
Adam Haun
Por lo general, eso solo sucedería como resultado del código que elija vincular para admitir su placa, pero independientemente de que cambie eso al definir su propia función de salida. El problema con su propuesta es que no se basa en la comprensión de cómo está la biblioteca destinado a ser utilizado - en lugar de hacer que usted está proponiendo un proceso de múltiples pasos para cada ouput, que entre otras cosas le hará un lío de cualquier portátil existente código que hace las cosas de la manera normal.
Chris Stratton el
STM32 es un entorno independiente. El compilador no necesita proporcionar stdio.h, es completamente opcional. Pero si proporciona stdio.h, debe proporcionar toda la biblioteca. Los compiladores de sistemas independientes que implementan printf tienden a hacerlo como comunicación UART.
Lundin
1

Para aquellos que luchan, agregue lo siguiente 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);
}

Conéctese a su puerto COM a través de masilla y debería ser bueno.

Michael Brown
fuente
1

Si desea un enfoque diferente y no desea sobrescribir las funciones o declarar las suyas, simplemente puede usar snprintfpara archivar el mismo objetivo. Pero no es tan elegante.

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