17
\$\begingroup\$

I am trying to figure out how to use the printf function to print to the serial port.

My current setup is STM32CubeMX generated code and SystemWorkbench32 with the STM32F407 discovery board.

I see in stdio.h that the printf prototype is defined as:

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

What does it mean? Where is the exact location of this function definition? What would be general point of finding out how to use this kind of function to output?

\$\endgroup\$
  • 5
    \$\begingroup\$ I think you need to write your own "int _write( int file, char *ptr, int len )" to send your standard output to your serial port such as here. I believe this is normally done in a file called Syscalls.c which handles "System Calls Remapping". Try Googling "Syscalls.c". \$\endgroup\$ – Tut Dec 15 '15 at 12:29
  • 1
    \$\begingroup\$ This is not a electronics problem. Go see the manual for your compiler library. \$\endgroup\$ – Olin Lathrop Dec 18 '15 at 12:10
  • \$\begingroup\$ Are you wanting to do printf debugging via semihosting (over the debugger) or just printf in general? \$\endgroup\$ – Daniel Dec 22 '15 at 2:10
  • \$\begingroup\$ As @Tut said, the _write() function is what I did. Details in my answer below. \$\endgroup\$ – cp.engr Jan 12 '17 at 22:37
17
\$\begingroup\$

I got the first method from this page working on my STM32F072.

http://www.openstm32.org/forumthread1055

As stated there,

The way I got printf (and all other console-oriented stdio functions) to work was by creating custom implementations of the low-level I/O functions like _read() and _write().

The GCC C library makes calls to the following functions to perform low-level I/O :

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)

These functions are implemented witihin the GCC C library as stub routines with "weak" linkage. If a declaration of any of the above functions appears in your own code, your substitute routine will override the declaration in the library and be used instead of the default (non-functional) routine.

I used STM32CubeMX to setup USART1 (huart1) as a serial port. Since I only wanted printf(), I only needed to populate the _write() function, which I did as follows. This is conventionally contained 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);
}
\$\endgroup\$
  • \$\begingroup\$ What if I use a Makefile and not an IDE? Something is missing from this to work in my Makefile project... Could anyone help? \$\endgroup\$ – Tedi Jan 14 '18 at 16:43
  • \$\begingroup\$ @Tedi, are you using GCC? This is a compiler-specific answer. What have you tried, and what were the results? \$\endgroup\$ – cp.engr Jan 15 '18 at 2:27
  • \$\begingroup\$ Yes I use GCC. I found the problem. The _write() function was called. The problem was in my code for sending the string. I misused Segger's RTT library but now works fine. Thanks. All working now. \$\endgroup\$ – Tedi Jan 15 '18 at 17:58
4
\$\begingroup\$

_EXFUN is a macro, probably containing some interesting directives that tell the compiler that it should check the format-string for being printf-compatible and ensuring that the arguments to printf match the format string.

To learn how to use printf, I suggest the man page and a bit more googling. Write some simple C programs that use printf and run them on your computer before you try transitioning to using it on the micro.

The interesting question will be "where does the printed text go?". On a unix-like system, it goes to "standard out", but a microcontroller has no such thing. The CMSIS debug libraries can send printf text to the arm semihosting debug port, i.e. into your gdb or openocd session but I've no idea what SystemWorkbench32 will do.

If you're not working in a debugger, it might make more sense to use sprintf to format the strings you want to print and then send those strings over a serial port or to whatever display you might have attached.

Beware: printf and its related code are very large. That probably doesn't matter much on a 32F407, but it is a real problem on devices with little flash.

\$\endgroup\$
  • \$\begingroup\$ IIRC _EXFUN marks a function to be exported, i.e. to be used in user code, and defines the calling convention. On most (embedded) platforms the default calling convention can be used. But on x86 with dynamic libraries, you might need to define the function as __cdecl to avoid errors. At least on newlib/cygwin, _EXFUN is only used to set __cdecl. \$\endgroup\$ – erebos Dec 15 '15 at 12:42
3
\$\begingroup\$

@AdamHaun's answer is all you need, with sprintf() it is easy to create a string and then send it. But if you really want a printf() function of your own, then Variable Argument Functions (va_list) is the way.

With va_list a custom print function looks like the following:

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

Usage example:

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

Note that while this solution gives you convenient function to use, but it is slower than sending raw data or using even sprintf(). With high datarates I think it won't be sufficient.


Another option, and probably better option is to use ST-Link, SWD debugger along with ST-Link Utility. And use Printf via SWO viewer, here is the manual of ST-Link Utility, relevant part starts on page 31.

The Printf via SWO Viewer displays the printf data sent from the target through SWO. It allows to display some useful information on the running firmware.

\$\endgroup\$
  • \$\begingroup\$ you don't use va_arg, how do you step through the variable arguments? \$\endgroup\$ – iouzzr Feb 28 at 15:36
1
\$\begingroup\$

printf() is (usually) part of the C standard library. If your version of the library comes with source code, you might find an implementation there.

It would probably be easier to use sprintf() to generate a string, then use another function to send the string through the serial port. That way all the difficult formatting is done for you and you don't have to hack up the standard library.

\$\endgroup\$
  • 2
    \$\begingroup\$ printf() is usually part of the library, yes, but it can't do anything until someone sets up the underlying capability to output to the desired hardware. That's not "hacking" the library, but utilizing it as intended. \$\endgroup\$ – Chris Stratton Jan 13 '17 at 3:24
  • \$\begingroup\$ It might be preconfigured to send text out through the debugger connection, as Bence and William suggested in their answers. (That's not what the questioner wanted, of course.) \$\endgroup\$ – Adam Haun Jan 13 '17 at 14:24
  • \$\begingroup\$ That would typically only happen as a result of code you choose to link in to support your board, but regardless you change that by defining your own output function. The problem with your proposal is that it's not based on understanding how the library is meant to be used - rather than do that you're proposing a multi-step process for each ouput, which among other things will make a mess of any existing portable code that does things the normal way. \$\endgroup\$ – Chris Stratton Jan 13 '17 at 15:43
  • \$\begingroup\$ STM32 is a freestanding environment. The compiler does not need to provide stdio.h - it is entirely optional. But if it does provide stdio.h, it must provide all of the library. The freestanding systems compilers that do implement printf tend to do it as UART communication. \$\endgroup\$ – Lundin Aug 18 '17 at 13:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.