STM32 gotchas
87.UART Tx is not driven immediately after enabling UART

This minor issue has been spotted and reported by user with nick il-2 on a russian-language forum.

In STM32, if a pin is set to Alternate Function in GPIO_MODER and assigned to given peripheral in GPIO_AFR[], that pin's output circuitry is controlled from the given peripheral. For example, if pin is assigned to a timer, if given timer channel is set to Input Capture, the pin's output circuitry is disabled (i.e. it functions as an input), if it is set to Output Compare and enabled, it's output circuitry is enabled and pulls up or down depending on the state of given channel (or pulls only down if set to Open Drain in GPIO_OTYPER).

Most of the peripherals have one or more "enable" bits, which directly influence the respective pin's output state. In UART, transmission - thus Tx pin's output state - is driven by UART_CR1.TE, but UART_CR1.UE has to be enabled, too (UE presumably controls the overall clocking of the UART). So, one would expect, that as soon as UART_CR1.TE is set, the given UARTx_TX pin will be driven high (to UART idle state).

But this is not how UART behaves. The following code is aimed at testing that ('F411 is used, PA9 as USART1_TX loaded by a voltage divider so that the Hi-Z state shows as cca 2V; USART1's clock in RCC and PA9's GPIO_AFR[] have been already set):

    GPIOA->MODER = (GPIOA->MODER & ~(3 << (2 * 9))) | (1 << (2 * 9));  // generate a marker by setting PA9 to Output
    GPIOA->ODR |= (1 << 9);                                            // pulse PA9
    GPIOA->ODR &= ~(1 << 9);
    GPIOA->MODER = (GPIOA->MODER & ~(3 << (2 * 9))) | (2 << (2 * 9));  // set PA9 as AF, i.e. place it under control of USART1

    USART1->BRR = 0x20;  // set baudrate

    USART1->CR1 = 0      // enable Tx and enable USART
      | USART_CR1_TE
      | USART_CR1_UE
    ;
    (void)USART1->SR;    // clear TC
    USART1->DR=0x55;     // transmit S10101010P
and this is how the Tx pin waveform looks like:

USART does not drive Tx pin immediately after TE is set.

As the waveform shows, after TE is enabled, there is one bit long delay, during which Tx pin is in Hi-Z state, before it pulls high. (There is also one frame length Idle transmitted before the first frame - this feature is documented in RM.)

As the discussion in the source forum points out, the output pin is not driven directly by the TE bit, rather, it's controlled from the baudrate generator.

There's also a similar delay after TE is cleared, i.e. Tx pin does not go into Hi-Z immediately. However, this delay appears to be somewhat shorter, maybe half-bit long. For testing that, the above code has been amended by: resulting in the following waveform (the delay in question exhibits itself as a prolonged stopbit, before the pin goes threestate):

    while ((USART1->SR & USART_SR_TXE) == 0);
    while ((USART1->SR & USART_SR_TC) == 0);
    USART1->CR1 = USART_CR1_UE;    // clear TE, Tx pin ought to go to threestate

    USART1->CR1 = 0                // enable transmitter again
      | USART_CR1_TE
      | USART_CR1_UE
    ;
    (void)USART1->SR;  // clear TC
    USART1->DR=0x55;
    while ((USART1->SR & USART_SR_TXE) == 0);
    while ((USART1->SR & USART_SR_TC) == 0);
    USART1->CR1 = USART_CR1_UE;

There's delay also after TE is cleared.

As an additional gotcha, the baudrate generator has quirks, if UART_BRR is set to some extreme values:

While these features are unlikely to be important in the vast majority of usual applications, there may be niche applications where behaviour other than "expected" may cause unpleasant surprises. For example,

Lessons to be taken from these observations:


1. While manipulating GPIO_MODER runtime and in interrupt/multitasking context, one has to observe atomicity if several such operations are present throughout the program. While this may seem "self-evident", manipulating this sort of registers runtime is unusual and this requirement may be easy to be overlooked resulting usually in rarely occuring and hard to debug issues.