2025-09-21 11:21 PM
Hi everyone,
I’m working on an STM32 firmware project (HAL + CubeIDE). I’m trying to output debug messages to Tera Term via UART using printf and puts.
Here’s the situation:
Debug build: Both printf and puts work perfectly.
Release build: Calling printf or puts causes the MCU to reset immediately. Maybe due to watchdog reset and these function call cause the MCU freezing.
I also retarget the printf function by implemented the following:
int __io_putchar(int ch) {
while (!(huart1.Instance->ISR & USART_ISR_TXE));
huart1.Instance->TDR = (uint16_t)ch;
return ch;
}
int _write(int file, char *ptr, int len) {
for (int i = 0; i < len; i++) {
__io_putchar(ptr[i]);
}
return len;
}
My questions:
Why do printf and puts crash/reset in Release but work in Debug?
Are there known issues with _write or __io_putchar in Release builds?
Any recommended approach to safely use printf/puts in Release firmware for UART output?
Thanks in advance!
2025-09-21 11:59 PM
Compare the build settings for debug and release.
Normally you have semihosting enabled for debug, which includes additional code and these outputs are routed via JTAG/SWD or an UART line.
Relase build setting do usually not.
This is why you have debug and release builds in the first place - you don't do debug printf()s in release versions.
I'm not using CubeIDE, so I don't know why it crashes.
With my toolchain, the _write and __io_putchar calls are empty functions in the release version, and thus printf()s in the release build amount to nothing.
2025-09-22 1:25 AM
The thing is my application support CLI function, accept commands from console like tera term and response accordingly.
2025-09-22 1:47 AM
Then don't use the semihosting interface for that, but a separate UART.
Or extend the release build settings accordingly.
2025-09-22 5:45 AM
You can still run the Release version in the debugger to see what's happening.
@chai2145 wrote:
Maybe due to watchdog reset and these function call cause the MCU freezing
Maybe instrument your code so that you can see if that actually is the case ...
As @Ozone said, check what's different between the builds - Optimisation level is another common one.
When increasing the optimisation level breaks your code, that usually indicates flaws in the code.
eg, failure to use 'volatile' where required...
2025-09-22 8:07 PM
Hi Andrew,
I'd tried to debug the code, found that it actually stuck at while loop.
while (!(huart1.Instance->ISR & USART_ISR_TXE));
and cause the watchdog reset.
int __io_putchar(int ch) {
while (!(huart1.Instance->ISR & USART_ISR_TXE));
huart1.Instance->TDR = (uint16_t) ch;
return ch;
}
2025-09-23 12:01 AM
To wich pins is UART1 routed, and what are these pins connected to externally ?
Is UART1 initialized anywhere in your release code ?
I suppose not.
2025-09-23 12:10 AM
I'm using UART1, initialization is done by autogenerated code. MX_USART1_UART_Init();
2025-09-23 12:41 AM
I don't use Cube, so I can't help you with specific details directly.
But check the initialisation routine is actually called, and in addition, the UART1 interrupt is initialized and enabled.
By the way, the names "debug" and "release" for both builds are arbitrary, and mere suggestions.
Although the initial configuration set by the IDE usually differs, with low/no optimisation and debug output (semihosting) enabled in the debug variant.
You can as well modify the "debug build" settings to your liking, and use the output as your "release builds".
2025-09-23 1:22 AM
@chai2145 wrote:found that it actually stuck at while loop.
while (!(huart1.Instance->ISR & USART_ISR_TXE));
So it's waiting for the Transmit register to become empty - and that never happens.
As @Ozone said, are you sure the USART is being enabled?
You haven't said what chip you're using - does it have hardware flow control?