Search code examples
cprintfstm32

STM32, arm-none-eabi: String is not output with printf if the string contains a leading `\n`


In my STM32 application I have the following code in main: printf("\nRunning Smoke Tests...\n");

The problem is that the whole string is not sent via UART. With some testing I found out that the leading \n is the cause. If i remove the leading \n the string is sent without issues via UART.

This is the implementation of _write which is used by printf:

int _write(int file, char* ptr, uint16_t len)
{
    UNUSED(file);

    // Calculate minimum transmission time in milliseconds with a safety margin of 10
    static constexpr uint32_t BAUD_RATE = 115200; // Baud rate in bits per second - see MX_USART2_UART_Init()
    static constexpr uint32_t BITS_PER_CHARACTER = 10; // 1 start bit, 8 data bits, 1 stop bit, no parity - see MX_USART2_UART_Init()
    auto const timeout_ms =
        static_cast<uint32_t>((static_cast<double>(len * BITS_PER_CHARACTER) * 1000.0 / BAUD_RATE) * 10);

    auto aux_result = HAL_UART_Transmit(&huart2, reinterpret_cast<const uint8_t*>(ptr), len, timeout_ms);
    if (aux_result == 0) {
        return len; // transmit succeeded, return length of transmitted data
    } else {
        return -1; // transmit failed, return error
    }
}

The debugger gives some insight:

This is the Stacktrace:

stm32-template-project.elf  
    Thread #1 (Suspended : Breakpoint)  
        _write() at stm32_system.cpp:180 0x80096aa  
        _write_r() at 0x800c1ac 
        __sflush_r() at 0x800b7cc   
        _fflush_r() at 0x800b81e    
        __sfvwrite_r() at 0x800ba28 
        _puts_r() at 0x800bcf0  
        smokeTests() at main.cpp:17 0x8001554   
        main() at main.cpp:37 0x80015a8 

With the debugger I see that len=1 is passed when _write is called.

If I remove the leading \n in the string len is correctly passed with 23.

My toolchain:

  • STM32CubeIDE 1.15.1
  • GNU tools for STM32 12.3. rel1
  • Standards: ISO C++20, ISO C18

Solution

  • There is a subtle bug in _write implementation. The timeout_ms value for a single-character string turns out to be 0. HAL_UART_Transmit with 0 timeout fails with HAL_TIMEOUT immediately after initiating a transmission; that error gets promoted upwards into libc which flags stdout as erred. Therefore, libc wouldn't even try to write anything anymore until that error is manually cleared, thus the rest of the string never gets sent.