Easily Use printf on STM32

Aside from blinking an LED, printing informative messages to a serial console is perhaps the simplest, most straightforward, and most common technique employed when debugging embedded projects. While most platforms have their own APIs capable transmitting data over a UART bus, they all lack the power and popularity of the printf() function. Unfortunately, to utilize this function in an embedded C application, simply including the stdio library is usually insufficient. In the case of an STM32 project, a few extra lines of code are required.

For those wishing to both send and receive formatted data over a UART bus, check out the companion article Easily Use scanf on STM32 as well!

Procedure

This procedure covers the preliminary step of configuring and enabling the appropriate UART peripheral, the primary step of mapping that UART instance to the printf() function, and an optional secondary step for those wishing to print floating point numbers. It was written for projects developed with STM32CubeIDE (version 1.11.2), but should be extendable to other environments as well.

0. Establish a UART Instance

To properly include the printf() function from the stdio library as described in the next step, a UART (or USART) peripheral must be configured to transmit the formatted string. Typically when working on an ST development board, the UART peripheral with RX and TX lines connected to the ST-LINK programmer/debugger is chosen for this purpose. This allows strings to be sent to a serial console thanks to the ST-LINK’s USB Virtual COM port bridge. Luckily, when starting a new project in STM32CubeIDE or STM32CubeMX, this UART instance is configured by default! You as the programmer just have to take note of which UART was configured and move on to the next step.

For those with an existing project and no UART configured, simply open the project’s .ioc file and make the modification outlined in Figure 1. In particular,

  • Select the appropriate peripheral instance
  • Set the mode and configuration parameters (the settings shown in Figure 1 are most commonly used)
  • Ensure the appropriate GPIO pins are configured as UART RX and TX.

To finish, save the .ioc file to generate code for the project.


Figure 1: An example of enabling and configuring the desired U(S)ART peripheral.

1. Redirect printf() to UART Instance

With the UART ready to go, adding printf() is just a matter of adding a few lines of code.

a. Add #include <stdio.h> to the Includes section at the top of the main.c file (Figure 2).

include
Figure 2: Including the stdio library

b. Copy and paste the following code into the main.c file before the main() function but after the UART handle declaration. A sensible option is in the Private Function Prototypes section, as shown in Figure 3 below. Note that the UART handle must be changed from &huart? to the handle of the desired UART peripheral (e.g., &huart2).

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

putchar_prototype
Figure 3: Adding retargeting code to main.c file.

The printf() function should now behave as expected for all but the floating point format specifiers. To enable those, continue on to the next step.

2. Enable Floating Point Support (Optional)

To illustrate the issue with floating point formatting, we can use the example code provided on the printf() reference page.

printf("Characters: %c %c\n", 'a', 65);
printf("Decimals: %d %ld\n", 1977, 650000L);
printf("Preceding with blanks: %10d\n", 1977);
printf("Preceding with zeros: %010d\n", 1977);
printf("Some different radices: %d %x %o %#x %#o\n", 100, 100, 100, 100, 100);
printf("floats: %4.2f %+.0e %E\n", 3.1416, 3.1416, 3.1416);
printf("Width trick: %*d\n", 5, 10);
printf("%s\n", "A string");

After adding this code to our project, a warning appears explaining that “the float formatting support is not enabled”. If we continue to build and run the project, the output in the serial console confirms that the floating point numbers will not be printed (Figure 4).

ex_output_noFloat
Figure 4: Example output without float formatting support enabled.

This may not be a problem if floating point numbers are not utilized in your application. However, if they are and you would like to enable them, simply right-click on the project name in the Project Explorer and choose Properties. Choose Settings in the “C/C++ Build” category and select MCU Settings under the Tool Settings tab. Check the box next to “Use float with printf from newlib-nano”, as shown in Figure 5 below. Click Apply and Close.

Note that enabling float formatting support for printf() will consume a fair amount of additional memory. Specifically, it will consume approximately 0.35 KB of RAM and 10.30 KB of Flash on top of what the printf() function consumes without floating point support. This may be an issue for lower-end devices.


Figure 5: Enabling float formatting support.

Now, after re-building the project and running the code, the floating point values are properly formatted and displayed as Figure 6 demonstrates.

ex_output_float
Figure 6: Example output with float formatting support enabled.

1 Like

Hi Matt,

Thanks for the article. The “Enabling float” worked until I set the “Use float with printf…” option in a FreeRTOS project. As soon as I tried to print a float it invoked “HardFault_Handler()” (at least that’s where debug ended up). I could not see a stack backtrace in STM32CubeIDE to try and figure out what was tripping it up.

Hi @softeky. Welcome to the Fourm! I’d be happy to help you track down the source of this bug. I’ll need some more information, though. First, what hardware are you using? Second, can you provide your source code? You can send these to me in a private message if you prefer (just click on my name and choose message).

1 Like

Hi Matt, thanks for the reply.

I’m using an “STM32L562” (Discovery Kit). RAM should not be a problem. STMCubeIDE v1.11.0 (running on MacOS 13.1 (which sports a horrible “Information Section” crash if that’s left open or used for 2 mins or more (argh))).

I did some research and may have worked around this issue. I created 2 very simple projects (to bully the problem into submission). One with FreeRTOS tasks and the second without FreeRTOS (just printf test block in main()). Only the FreeRTOS version crashes. In both I call the printf test block before the forever loop. e.g. calling:

void
StartBlink01(void *argument) {
  /* USER CODE BEGIN 5 */
#if defined(PRINTF_TEST)
	char str[80];
	int i;

	printf("\r\nRTOS\r\nCharacters: %c %c\r\n", 'a', 65);
	printf("Decimals: %d %ld\r\n", 1977, 650000L);
	printf("Preceding with blanks: %10d\r\n", 1977);
	printf("Preceding with zeros: %010d\r\n", 1977);
	printf("Some different radices: %d %x %o %#x %#o\r\n", 100, 100, 100, 100, 100);
	printf("floats: %4.2f %+.0e %E\r\n", 3.1416, 3.1416, 3.1416);
	printf("Width trick: %*d\r\n", 5, 10);
	printf("%s\r\n\r\n", "A string");
#endif /* defined(PRINTF_TEST) */

	 /* Infinite loop */
 for(;;) {
	if(HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin) == GPIO_PIN_SET) {
		HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
		osDelay(1);
	}
	else {
		HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
		osDelay(600);
	}
  }
  /* USER CODE END 5 */
}

I found https://microchipsupport.force.com/s/article/Execution-crashes-when-using-sprintf-in-a-FreeRTOS-task while researching.

The probIem went away when I changed the stack available to the FreeRTOS threads that use printf, from 128 words to 512 words. (which reserves 2KB and is less than TOTAL_HEAP_SIZE (8KB)).

Without the stack size change, the crash did not seem to leave a usable stack to crawl through (as far as I could tell). Additionally the crash was cropping up inconsistently, even without “Use float with printf…” option set. A memory-stomp fingerprint!

Please let me know if I can provide any more info. I’m happy to send you the whole project if you’d like to look further.

I’m currently trying to figure out an EOLN issue with scanf, which may be associated with my use of UNIX “screen” command as a terminal emulator. Baby steps as with the start of any new learning curve :-).

Thanks again.

1 Like

Hi @softeky. Thanks for posting this great info! I’m sure it will help many others down the road.

Sounds like you’re making great progress! I’m not sure exactly what your EOLN issue is, but did you add the line setvbuf(stdin, NULL, _IONBF, 0); to your initialization code? That’s a common gotcha…

1 Like

Thanks again Matt. You got it! (Brings back a bunch of memories from the ol’ PDP-11/03, raw i/o, mini-UNIX days). Here’s the latest printf-scanf test block. It works like a charm.

#if defined(PRINTF_TEST)
	char str[80];
	int i;

	setbuf(stdin, NULL);

	printf("\r\nRTOS\r\nCharacters: %c %c\r\n", 'a', 65);
	printf("Decimals: %d %ld\r\n", 1977, 650000L);
	printf("Preceding with blanks: %10d\r\n", 1977);
	printf("Preceding with zeros: %010d\r\n", 1977);
	printf("Some different radices: %d %x %o %#x %#o\r\n", 100, 100, 100, 100, 100);
	printf("floats: %4.2f %+.0e %E\r\n", 3.1416, 3.1416, 3.1416);
	printf("Width trick: %*d\r\n", 5, 10);
	printf("%s\r\n\r\n", "A string");

	printf("Enter your family name: ");
	scanf("%79s", str);
	printf("\r\nEnter your age: ");
	scanf("%d", &i);
	printf("\r\nMr. %s, %d years old.\r\n", str, i);
	printf("\r\nEnter a hexadecimal number: ");
	scanf("%x", &i);
	printf("\r\nYou have entered %#x (%d).\r\n", i, i);
#endif /* defined(PRINTF_TEST) */

1 Like

Hi Matt,
My problem is about Sprintf with floating point number on STM32L452RET6P.

Although all settings are correct, sprintf doesn’t work with floating point number only. Although it works on stm32cubeMX, it doesn’t work on bare-metal program. It loops endlessly in the Default_Handler function. I’m about to go crazy.

First sprintf properly works. But the second one does not. After second sprintf, I’ve traced back the code on the debugger and I can see that the DefaultHandler executes and traps the board in an Infinite_Loop as a result of WWDG_IRQHandler misfiring.

Error message tells me "No source available for “() at 0xfffffff9” , No source available for “_printf_float() at 0x8001418”, No source available for “_svfprintf_r() at 0x800347e”, No source available for “sprintf() at 0x8001bd0” . I can’t seem to solve this problem, do you have any idea?



Hello @shouryuken,

It’s not clear from the code you posted what you mean by “bare-metal programming”. I assume you are simply not using either the HAL or LL abstraction layers? Which libraries/drivers are you using, if any?

Making some assumptions, I attempted to replicate your issue using a NUCLEO-L476RG board I have on-hand (I don’t have a NUCLEO-L452RE-P at the moment…). First, I created a new project using the procedure outlined in Setting Up an Empty STM32CubeIDE Project. I then created the following main.c file based on what you’ve provided:

#include <stdint.h>
#include <stdio.h>
#include "stm32l4xx.h"

char str[30];

void Display_Centimeters(void);

int main(void)
{
  Display_Centimeters();

	while(1)
	{

	}
}

void Display_Centimeters(void)
{
  int n = 30;
  int a = 10;
  float f = 5.0;

  n = sprintf((char *)str, "distance = %d \r\n", a);
  sprintf((char *)str, "distance = %f \r\n", f);

  (void)n; // avoid compiler warning
}

Note that my project settings match those in the screenshot you provided (except the MCU part number is different, of course). When I debug the project, I don’t receive any of the errors you describe. Both calls to sprintf() work as expected.

If you’re still having issues with your code, It would be very helpful to see a complete copy of your project (or a scaled-back project with the same issue) so we can figure out what’s missing. You can attach it to a private message if you prefer.

1 Like

Matt,
Thanks.I think that FPU is not switched in the startup code. Is it defined in SystemInit function?
I couldnt find them in system_stm32l452xx.h. They are defined as extern void SystemInit (void) and
extern void SystemCoreClockUpdate (void).
and what about SystemCoreClockUpdate?
Please explain them in detail.

Also, do you mean the following?

int main(void)
{

System_Clock_Init();
SystemInit();
SystemCoreClockUpdate();
Display_Centimeters();

Display_Centimeters();

while(1)
{

}

}
void System_Clock_Init(void){

// Enable High Speed Internal Clock (HSI = 16 MHz)
RCC->CR |= ((uint32_t)RCC_CR_HSION);

// Wait until HSI is ready
while ( (RCC->CR & (uint32_t) RCC_CR_HSIRDY) == 0 );

// Select HSI as system clock source
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_HSI; //01: HSI16 oscillator used as system clock

// Wait till HSI is used as system clock source
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) == 0 ) {;}
}

Also, according to Setting Up an Empty STM32CubeIDE Project, you didnt any any source file.
SystemInit (void) function is also defined in system_stm32l452xx.c. But, you did not add it. why?

Please show me your project explorer.

The SystemInit() function is defined in system_stm32l4xx.c. As I noted in Setting Up an Empty STM32CubeIDE Project :

Because the STM32L476 does include an FPU, I did include the file.

SystemCoreClockUpdate() simply updates the SystemCoreClock variable (also defined in system_stm32l4xx.c). For this example it’s not necessary, so I’ll edit my previous post to remove it.

I just used the default system clock configuration for this code.

Here’s a screenshot of my project explorer (click on it to view the entire image):

1 Like

Hi Matt,
You are great one. As far as I understand, SystemInit () is called before the main function is called. Thus, the FPU hardware is activated. Why would I need to call SystemInit() again if I’m right? The problem was due to not adding the system_stm32l4xx.c file.
However, I did not rewrite the SystemInit() function in the main function. And program works well as expected.

Good point! I hadn’t noticed the call to SystemInit() in the reset handler. Sure enough, removing the superfluous function call from main() doesn’t change the program behavior. I’ll edit the previous post again to remove it. Thanks for pointing that out.

Hi,
I think printf is using some buffers internally that it sets up on the first call. Because I also had the hardfault when using printf with freertos. But if I just used for instance sprintf in the main before the freertos kernel was started, it seems to work.