Affiliate Disclosure: Some links on this blog are affiliate links, meaning I earn a commission at no extra cost to you. I only recommend products and services I trust and use myself.

By default, most microcontrollers have no concept of a console, so you have to help them out a bit. When it comes to debugging, outputting information to some kind of console can be extremely useful. One option is to use semihosting with STM32CubeIDE.

However, semihosting can be extremely slow. Another good option is to output debug information over the serial port (UART). We can call the STM32 HAL functions (e.g. HAL_UART_Transmit), but sometimes it’s easier to use the standard C library functions printf, scanf, and so on. To do that, we need to re-write the underlying functions.

Note: the code for this section is taken from Carmine Noviello’s Mastering STM32 book. I am simply updating the process for how to get retargeting working in STM32CubeIDE. All credit goes to Carmine Noviello for his code. You are welcome to write your own retarget.h and retarget.c files, if you wish.

Start a new STM32 project with all the defaults. I am using a Nucleo-L476RG for this example. Enable UART, if needed. For almost all the Nucleo boards, UART2 is tied to the ST-LINK microcontroller, which gives us a virtual COM port over USB. Save and generate code.

The first thing we need to do is disable syscalls.c. This file defines many of the same functions that we are trying to make. If you do not disable it, you will likely get a “multiple definition” error when you try to compile and link. For example:

multiple definition of `_isatty'

Right-click on the syscalls.c file and select Properties. Under C/C++ Build > Settings, check Exclude resource from build.

Exclude files from STM32CubeIDE build

Click Apply and Close. The file name in the project browser should now be grayed out.

This will allow us to not include a file (or folder) when we compile and link in STM32CubeIDE. The process is similar for most other Eclipse-based IDEs.

Create a new file in the Inc directory called retarget.h. Copy the following code into this file:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/include/retarget/retarget.h

#ifndef _RETARGET_H__
#define _RETARGET_H__

#include "stm32l4xx_hal.h"
#include <sys/stat.h>

void RetargetInit(UART_HandleTypeDef *huart);
int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);

#endif //#ifndef _RETARGET_H__

Save this file.

Create a new file in the Src directory called retarget.c. Copy the following code into this file:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/src/retarget/retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>

#if !defined(OS_USE_SEMIHOSTING)

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

void RetargetInit(UART_HandleTypeDef *huart) {
  gHuart = huart;

  /* Disable I/O buffering for STDOUT stream, so that
   * chars are sent out as soon as they are printed. */
  setvbuf(stdout, NULL, _IONBF, 0);
}

int _isatty(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 1;

  errno = EBADF;
  return 0;
}

int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
    hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

int _close(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 0;

  errno = EBADF;
  return -1;
}

int _lseek(int fd, int ptr, int dir) {
  (void) fd;
  (void) ptr;
  (void) dir;

  errno = EBADF;
  return -1;
}

int _read(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == STDIN_FILENO) {
    hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return 1;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

int _fstat(int fd, struct stat* st) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
    st->st_mode = S_IFCHR;
    return 0;
  }

  errno = EBADF;
  return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

Save this file.

Your project directory structure should look like the following:

Adding files to STM32CubeIDE directory structureNotice that we’ve added retarget.h and retarget.c. We also removed syscalls.c from the build, but the file still exists in our project directory.

Include stdio.h and retarget.h in your main.c file. You can now use printf and scanf, as shown here:

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "retarget.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  char buf[100];
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  RetargetInit(&huart2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("\r\nYour name: ");
    scanf("%s", buf);
    printf("\r\nHello, %s!\r\n", buf);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

// ***REST OF INITIALIZATION CODE NOT SHOWN***

Build this project and open a debugging session in STM32CubeIDE. Open your favorite serial terminal program and connect to the Nucleo’s COM port (baud rate of 115200, 8-N-1). You should be greeted by a query. Type out some answer and press enter. Note that you will not be able to see what you typed. However, the program should append your entered text to a “Hello” message.

Using printf and scanf on STM32

Try not to overflow your assigned character buffer. Bad things happen if you do. Or you could write some kind of bounds checking in your code. Anyway, this is just a starting point to hopefully help you with debugging on your STM32 project.

 

 

35 thoughts on “How to Use printf on STM32

    1. Arxks on March 19, 2022 at 3:35 pm Reply

      how it is best alternative ? Did you understand what it is written correctly ?

  1. R on January 1, 2020 at 11:13 pm Reply

    Another alternative: “debugging using either a UART or the SWO trace cell” https://ralimtek.com/stm32_debugging/

  2. Adam Insanoff on August 16, 2020 at 1:16 pm Reply

    And how to un”Exclude resource from build”?

    1. ShawnHymel on August 19, 2020 at 3:45 pm Reply

      If you’re looking to include a source file, you can add the header file in Project > Properties > C/C++ General > Paths and Symbols > Includes > GNU C. To add the .c (or other source), click on the Source Location tab in Paths and Symbols.

  3. Kurt on September 16, 2020 at 9:28 pm Reply

    Hi Shawn, thanks for this blog! I am having trouble getting this to work with picocom or mincom on my ubuntu. I have a l432 hooked up on USB. I am wondering if you have any insight onto why this isnt working?
    Thanks

    1. Kurt on September 16, 2020 at 10:05 pm Reply

      I think the issue is that UART isnt multiplexed to my pinout on the board….

      1. ShawnHymel on September 17, 2020 at 2:41 pm Reply

        Do you have an oscilloscope or logic analyzer that you could use to verify that the UART is communicating?

  4. P on November 9, 2020 at 5:47 am Reply

    How can I use printf and scanf with float or integer value? It always returns 0 when I input the value.

  5. Issam on December 30, 2020 at 8:06 pm Reply

    Hello,
    how can we write sensor data into a txt file

    1. ShawnHymel on December 30, 2020 at 9:29 pm Reply

      You either need to write a program on your computer that captures incoming serial data and stores it to a txt file (e.g. using something like PySerial) or you need to add an SD card to your microcontroller project and use something like FATFS to write data directly to a txt file on the SD card.

      Edit: Alternatively, you could also spit your sensor data out over serial to a device like this: SparkFun OpenLog.

  6. Seyed on January 26, 2021 at 5:17 am Reply

    Hello shawn and thank you for this
    i’ve got the printf “your name”, but when i try to send data, it doesn’t do anything and the code stucks
    using this on discovery f407

    1. ShawnHymel on January 30, 2021 at 7:18 pm Reply

      What serial terminal program are you using? If you can, try having it send both \r and \n characters when you press the enter key to see if that helps.

  7. Richard on March 26, 2021 at 10:17 am Reply

    Thanks, Shawn!
    Works like a dream, straight from the page.
    Had to modify slightly – the Nucleo 32f429 board uses USART3 for the ST-Link – but it just works beautifully. As ever, well written and deceptively simple to implement.
    Major advance in debugging!
    Cheers!
    Richard

  8. Hien on June 5, 2021 at 1:47 pm Reply

    I changed from STM32 F334 to L562 and I was stuck with printf. This worked wonderful for me. This is great, you are awesome! Thank you so much.

  9. Furkan Karagoz on July 30, 2021 at 11:24 am Reply

    Hi, thanks for the perfect guide. I have applied this method for my code running on STM32EWB55. At first I couldn’t get the print function working on the serial monitor but the scan was working when I have checked the buffer expression. So, I have used the oscilloscope in the RS232 mode to verify the issue. Oscilloscope got the print data. After rebooting the serial monitor app I was able to see printed messages as well. %100 success.

  10. Abhi on August 13, 2021 at 9:57 am Reply

    Hi Shawn,
    how to use HAL_UART_Trasnmit_IT() or HAL_UART_Receive_IT() fucntion in this case.

    1. ShawnHymel on August 13, 2021 at 2:45 pm Reply

      I think all you need to do is enable the USART global interrupt in the NVIC Settings (in CubeMX) and use the appropriate _IT() function (https://www.waveshare.com/wiki/STM32CubeMX_Tutorial_Series:_USART). I don’t think printf() works with receiving, though. I have not tried it, so I could be wrong.

  11. Akil on August 16, 2021 at 10:23 am Reply

    Hi,
    Is there no problem on disabling the syscalls.c file.
    Or it may impact on the future code.

    1. ShawnHymel on August 16, 2021 at 3:27 pm Reply

      From my understanding, most (all?) of the function calls in syscalls.c are replicated elsewhere (https://www.openstm32.org/forumthread6814), which is why you need to disable that file to avoid duplication. While I have not seen any issues with it impacting other code, I can’t promise that it is 100% safe, as I have not tested it thoroughly.

  12. Abhi on August 22, 2021 at 6:41 pm Reply

    Hi
    How to send (transmit) float values?

    1. ShawnHymel on August 24, 2021 at 3:38 pm Reply

      If you’re getting no output when trying to print floating point values, you likely need to add “-u _printf_float” to the LDFLAGS in the options. This post shows you where to add it in STM32CubeIDE: https://community.st.com/s/question/0D50X0000B0AEJb/uprintffloat-where-to-set-for-clinker-in-stm32cubeide

  13. Martin on October 23, 2021 at 5:33 am Reply

    Does this guide also work for C++?

    I followed this guide step-by-step but I still get an error when I build my code:

    undefined reference to `RetargetInit(__UART_HandleTypeDef*)’

    Any suggestions are greatly appreciated.

    1. ShawnHymel on November 8, 2021 at 8:32 pm Reply

      I have not seen that error, but this thread might help: https://community.st.com/s/question/0D50X0000AIeIqo/undefined-reference-to-haluartinit

    2. Paulo on December 6, 2021 at 10:54 am Reply

      Hi @Martin,

      This problem is because you are calling C code from within CPP.
      Check this article: https://embeddedartistry.com/blog/2017/05/01/mixing-c-and-c-extern-c/

      The solution here is to enclose the retarget.h with:

      #ifdef __cplusplus
      extern “C”
      {
      #endif

      //C code goes here

      #ifdef __cplusplus
      } // extern “C”
      #endif

  14. JK on October 26, 2021 at 4:13 am Reply

    Hello,

    I am seeing these build errors…

    undefined reference to `_exit’
    undefined reference to `_getpid’
    undefined reference to `_kill’

    Any ideas as to what the problem could be?

    Thanks

    1. ShawnHymel on November 8, 2021 at 8:30 pm Reply

      I have not seen these errors personally. You might need to add

      -specs=nosys.specs

      to the linker flags as per this thread: https://www.openstm32.org/forumthread2449

  15. Tim on February 10, 2022 at 5:45 pm Reply

    Thanks for the blog post Shawn, this helped a lot just getting up and running. I later found that its actually much simpler to do, or at least for the transmit side (didn’t try receiving yet).

    All I had to add was to add this in function in my project (easiest is in the main function for access to huart2). Of course change the reference if using a different uart handle.

    int __io_putchar (int ch)
    {
    (void)HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
    }

    1. A on October 25, 2023 at 6:52 pm Reply

      Thanks a lot, this worked like a charm!

  16. Victor on March 15, 2022 at 1:05 am Reply

    Saludos, ¿cómo enviar (transmitir) una matriz n*n?

  17. Mansi on June 30, 2022 at 9:52 am Reply

    I am having error can not firn _ansi.h

  18. Gilissen E. on January 2, 2023 at 3:13 pm Reply

    Works perfect but printing to console takes quite some time, so has severe impact on the firmware timing.
    But certainly a nice addition to debug STM32 code.

  19. Jonan on January 3, 2023 at 9:44 am Reply

    I’m getting the following error for both files in STM32F7 (#include “stm32f7xx_hal.h”):
    unknown type name ‘UART_HandleTypeDef’; did you mean ‘I2C_HandleTypeDef’?

  20. Andrew Neil on December 18, 2023 at 1:55 pm Reply

    All of your retarget.c. functions contain a check on STDIN_FILENO or STDOUT_FILENO and STDERR_FILENO – except for _lseek.

    Is that correct? Why does _lseek not also include the test?

  21. Soner Sezgin on September 24, 2024 at 12:42 pm Reply

    I have tested several examples without success. Your example works great. Thanks for your effort.

Leave a Comment

Your email address will not be published. Marked fields are required.