33
\$\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\$
4
  • 7
    \$\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, 2015 at 12:29
  • \$\begingroup\$ Are you wanting to do printf debugging via semihosting (over the debugger) or just printf in general? \$\endgroup\$
    – Daniel
    Dec 22, 2015 at 2:10
  • \$\begingroup\$ As @Tut said, the _write() function is what I did. Details in my answer below. \$\endgroup\$
    – cp.engr
    Jan 12, 2017 at 22:37
  • \$\begingroup\$ this was a great tutorial: youtu.be/Oc58Ikj-lNI \$\endgroup\$ Oct 3, 2019 at 18:43

7 Answers 7

32
\$\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\$
3
  • \$\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, 2018 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, 2018 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, 2018 at 17:58
5
\$\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\$
2
  • \$\begingroup\$ you don't use va_arg, how do you step through the variable arguments? \$\endgroup\$
    – iouzzr
    Feb 28, 2019 at 15:36
  • 1
    \$\begingroup\$ @iouzzr: He delegates that work to vsprintf. \$\endgroup\$
    – Ben Voigt
    Jan 19 at 15:44
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\$
1
  • \$\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, 2015 at 12:42
4
\$\begingroup\$

If you want a different approach and don't want to overwrite functions or declare your own, you can simply use snprintf to archieve the same goal. But it's not as elegant.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
\$\endgroup\$
1
  • \$\begingroup\$ This is super simple and lets you re-use the buffer for tx dma. \$\endgroup\$
    – robsn
    Sep 2, 2020 at 12:57
2
\$\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\$
4
  • 4
    \$\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\$ Jan 13, 2017 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, 2017 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\$ Jan 13, 2017 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, 2017 at 13:02
2
\$\begingroup\$

For those struggling, add the following to 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);
}

Connect to your COM port via putty and you should be good.

\$\endgroup\$
0
\$\begingroup\$

You can use the SWV data console and SDO (meaning you don't need to use a UART and a Serial TTL level USB dongle which is what I used to do to get printf).

Include <stdio.h> in main.c

Add the following code where the IDE wont overwrite it (top of main.c?) (the function __io_putchar is what printf uses to send characters to, its weak so we can overwrite it by declaring the function).

int __io_putchar(int ch) {
          ITM_SendChar(ch);
          return(ch);
}

Use the cube MX to find your system frequency (not the input crystal frequency) Now using the drop down arrow by the green bug get "debug configurations..."

In the debug config window enable SWV and enter your system clock frequency (my STM32F446RE Nucleo seems to default to 84 MHz from STM32 IDE) enter image description here

Now debug the project. In the bottom right hand corner there are some small tool and red button icons.

enter image description here

Click on the tool icon (spanners) and you will get the SWV config window

enter image description here In the SWV window click the 0 bit in ITM stimulus ports and the 0 port byte. confirm the sys clk frequency is correct (as entered earlier in debug configurations)

Finally click on the small red button/icon to actually start it tracing (this shows almost imperceptibly it is selected with a subtle outline when active) prints should now appear in the "SWV ITM Data Console" tab at the bottom of eclipse. You can CNTRL-A and CNTRL-C these to paste them into data files to forward to gnuplot etc.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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