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 S10101010Pand this is how the Tx pin waveform looks like:
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;
As an additional gotcha, the baudrate generator has quirks, if UART_BRR is set to some extreme values:
- if UART_BRR <= 7, there's no baudrate generation, i.e. after enabling UE/TE, Tx pin remains threestated indefinitely. This may quite easily occur in environments where baudrate is expected to be set "afterwards", e.g. from USB; or if baudrate is set from a possibly corrupted source, e.g. an EEPROM.
- similarly, if UART_BRR >= 0xFFF8, there's also no baudrate generation
- if 8 <= UART_BRR <= 0xF (with OVER8 = 0), baudrate is surprisingly low; for example with UART_BRR = 8 it's (UART_CLOCK / 0x8000)
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:
- UART_BRR is to be set to proper value early
- if Tx pin state has to be high at all times when there's no transmission, and UART_CR1.TE is cleared/set for whatever reason (e.g. in order to inject an Idle frame), enable pullup or use appropriate external pullup
- if Tx is connected to other driving pin and it's anticipated that it will go threestate after UART_CR1.TE is cleared, either the extra (half)bit has to be taken into account, or the pin should be set to Input in GPIO_MODER1
- the extra bit may should be taken into account in cases where tight timing is expected
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.