32F4 - a weird UART issue - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: September 30, 2022, 07:52:34 am
content: With the project nearly finished I have finally got around to writing some comprehensive factory test code.And I found that serial port loopback does not work at baud rates over about 38k.The serial ports all work and have been heavily used with various devices (at/below 38k), and outputting data has been tested up to megabit speeds, but it turns out that at say 115k the 1st byte sent, on a loopback test, is never received. Subsequent ones are. If I send just one byte,the RX ISR is never hit. This behaviour could easily have never been noticed in practical usage...I've been "doing UARTs" since about 1980... In general, you- initialise it (the 32F4 UART transmits "an empty frame" there, apparently without a start bit, so I have a baud rate dependent wait to clear this silly feature)- flush RX buffer, as many times as the FIFO is deep (2x for the 32F4 one)- set up the RX interruptThere tend to be weird things like framing errors which need to be cleared on each RX ISR entry (unless you especially want to log them, etc) but these don't normally prevent the RX interrupt.The simple test I am running is- output 1 byte (with loopback, TX to RX)- wait for it to come back (it never does)There is a 1k circular buffer on RX but as I said above this is never touched because there is no interrupt. If I send two bytes, I get the interrupt and the data read from DR is the 1st byte. If I send 256 bytes then I get back 255.On a scope, the data looks fine.This is the RX ISRCode:void Kde_serial_receive_irq_handler(UART_HandleTypeDef *huart){uint8_t temp = huart->Instance->DR;// fetch the byte receiveduint8_t port = KDE_SERIAL_MAX_PORT;// max port numberint32_t count = 0;__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_RXNE);// Find which port this is in order to know which buffer to usewhile (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance){port--; // port is 1-4}// Put the char into the queue only if port # valid and the queue is not fullif (port > 0){port--;// offset from 0// If 7 bit, mask off any parity in the MS bit// If 8 bit with parity, the parity would be in bit 8 which we've already lost by reading into// and 8 bit valueif (kde_serial_port_configs.bits_per_word == 7) {temp &= 0x7f;}kde_serial_receive_buffer[kde_serial_receive_buffer_rx_index++] = temp;kde_serial_receive_buffer_rx_index %= KDE_SERIAL_RX_BUFFER_SIZE;if (kde_serial_receive_buffer_rx_index == kde_serial_receive_buffer_get_index){// Buffer is full - discard a byte from it. This is necessary to resolve the ambiguity of// the two pointers being equal if FULL or EMPTY. We make sure they are never equal after an input.// Increment the get index to ensure that only the last BUFFER-1 bytes can be accessedkde_serial_receive_buffer_get_index++;kde_serial_receive_buffer_get_index %= KDE_SERIAL_RX_BUFFER_SIZE;}}}I am doing the RX clearing diligently after UART init, but I wonder if anyone has any ideas.One thing I wondered was whether the GPIO (RX) is sampled fast enough; there is a range of sampling rates. I see code setting it up for "very high", but also enabling a UART on certain GPIOs may set up those pins; the extent to which this happens varies with the peripheral.EDIT: reading SR and then DR manually returns 10010000 (TX empty, ok, and Idle Line detected, not ok) and in DR I see the correct byte value.Basically, if I transmit by writing to the DR directly, it works, but if I transmit using a more complex TX function (which enables the TX empty and RX complete interrupts) it fails on the first byte. If I comment out the TXE and TC interrupt enable code, it works, but obviously I then get a very slow transmit, 1 byte at a time only. So is there something about these TX interrupts which might block the RX interrupt?

author: newbrain
author_rank: Super Contributor
timestamp: September 30, 2022, 01:00:18 pm
content: So, it seems we don't have the full picture.The IRQs are largely independent, but it all comes to what is done in the ISRs.One thing that I see here is a double clearing of RXNE: once by reading DR, then by actively clearing the flag.This should not be a problem though, unless some other IRQ interrupts the ISR in between for much too long.This puzzles me:QuoteWhen transmission is enabled, anidleframe is sent - of course without a start bit, otherwise it would just be a spurious character! - is this what you are referring to? And why this needs "clearing" (or rather, what "clearing" means here)?QuoteWhy 2x? There's no FIFO in STM32F417, AFAICS - clearing the status flags should be enough.QuoteThe data on the I/O pin is sampled every AHB1 clock, if, OTOH, you are referring to UART oversampling, 16 or 8 are the only alternatives.On the gripping hand, speed settings in GPIOx_OSPEEDR have absolutely nothing to do with input, or sampling.

author: peter-h
author_rank: Super Contributor
timestamp: September 30, 2022, 02:37:36 pm
content: QuoteI didn't realise the 32F4 did that. I know some UARTs do.EDIT: you are right; no need to clear RXNE. Big improvement in the error rate! I then did the same thing to the TX complete ISR, and it still runs:Code:void Kde_serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart){// Transmit completeuint8_t port = KDE_SERIAL_MAX_PORT;// max port number// Find which port this iswhile (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance){port--;}// Transmit completekde_serial_transmit_active[port-1] = false;// Clear and disable the TC interrupt//huart->Instance->SR &= ~(UART_FLAG_TC); // not needed__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}Quote"Clearing" is the wrong word really. My reading of the RM was that when you enable the UART, you can't use it for anything because it transmits that spurious byte (without a start bit; I agree and I checked that) at the current baud rate, which could be quite a long time. So my UART init code runs a timer for that, before it returns.QuoteWith some UARTs, you have a FIFO for data and a parallel FIFO for error conditions, so if say you get 1 good byte and 1 byte with duff parity, you read out the 1st byte and then the parity error status bit pops up. Hence the habit of clearing error conditions more than once, but this should be accompanied by a read of the DR.QuoteI think that once a couple of GPIOs are attached to a UART TX and RX, the config for the clock speed on those GPIOs is bypassed. It would be silly otherwise, and high baud rates would be problematic due to the loss of resolution on edge detection.I am working on this full time and testing on two boards. One of the boards isnotexhibiting the total lack of an RX interrupt on the 1st byte, but it isn't running the same RTOS tasks. The other board does produce the RX interrupt if I comment out some code which doesn't even get executed during the loopback! (But does get executed during UART initialisation). Disabling some RTOS tasks changes the error rate, which is a useful hint. AFAIK the ISR code is mainly from Cube MX (in fact most of it obviously is, looking at the machine comments) and ST have had lots of cases where there were critical regions. I wonder what the relative priority of the serial interrupts is. This is the ex MX code for the four UARTs. I struggle to find where the interrupt priority is configuredThis issue could have gone for years without anybody noticing. It is only a full-on loopback test at 115k that shows it.Code:// Common interrupt handler for all USART interruptsstatic void usart_common_IRQHandler(UART_HandleTypeDef *huart){// check if we are here because of RXNE interruptif (huart->Instance->SR & USART_SR_RXNE) { //if RX is not emptyKde_serial_receive_irq_handler(huart);}// check if we are here because of TXEIE interruptif (huart->Instance->SR & USART_SR_TXE) { //if TX is emptyKde_serial_transmit_irq_handler(huart);}// check if we are here because of the TC interruptif (huart->Instance->SR & USART_SR_TC) {Kde_serial_transmit_complete_irq_handler(huart);}}/*** @brief This function handles USART1 global interrupt.*/void USART1_IRQHandler(void){/* USER CODE BEGIN USART1_IRQn 0 */usart_common_IRQHandler(&huart1);/* USER CODE END USART1_IRQn 0 */// Standard HAL handler - not required//HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 *//* USER CODE END USART1_IRQn 1 */}/*** @brief This function handles USART2 global interrupt.*/void USART2_IRQHandler(void){/* USER CODE BEGIN USART2_IRQn 0 */usart_common_IRQHandler(&huart2);/* USER CODE END USART2_IRQn 0 */// Standard HAL handler - not required//HAL_UART_IRQHandler(&huart2);/* USER CODE BEGIN USART2_IRQn 1 *//* USER CODE END USART2_IRQn 1 */}/*** @brief This function handles USART3 global interrupt.*/void USART3_IRQHandler(void){/* USER CODE BEGIN USART3_IRQn 0 */usart_common_IRQHandler(&huart3);/* USER CODE END USART3_IRQn 0 */// Standard HAL handler - not required//HAL_UART_IRQHandler(&huart3);/* USER CODE BEGIN USART3_IRQn 1 *//* USER CODE END USART3_IRQn 1 */}/*** @brief This function handles USART6 global interrupt.*/void USART6_IRQHandler(void){/* USER CODE BEGIN USART6_IRQn 0 */usart_common_IRQHandler(&huart6);/* USER CODE END USART6_IRQn 0 */// Standard HAL handler - not required//HAL_UART_IRQHandler(&huart6);/* USER CODE BEGIN USART6_IRQn 1 *//* USER CODE END USART6_IRQn 1 */}

author: peter-h
author_rank: Super Contributor
timestamp: September 30, 2022, 03:11:42 pm
content: Newbrain fixed it!It was the surplus (double) clearing of the interrupts.

author: peter-h
author_rank: Super Contributor
timestamp: September 30, 2022, 07:10:55 pm
content: However, I am surprised that this ISR works given that it no longer clears TCCode:void Kde_serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart){// Transmit completeuint8_t port = KDE_SERIAL_MAX_PORT;// max port number// Find which port this iswhile (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance){port--;}// Transmit completekde_serial_transmit_active[port-1] = false;// Clear and disable the TC interrupt//huart->Instance->SR &= ~(UART_FLAG_TC);// Not needed__HAL_UART_DISABLE_IT(huart, UART_IT_TC);}Pag 1008 in the RM statesbut the last sentence is ambiguous in which way of clearing it it applies to.Still, disabling the interrupt (as the ISR does) should be sufficient. It gets enabled when a byte is placed into the UART for transmission, by the foreground code. That code does a read of SR (a read modify write, to clear TC) and a write to DR; exactly what the RM asks for, and I reckon this is accidentally rightCode:// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts// First clear TCkde_serial_port_handles->Instance->SR &= ~(UART_FLAG_TC);kde_serial_port_handles->Instance->DR = kde_serial_transmit_buffer[kde_serial_transmit_buffer_tx_index++];kde_serial_transmit_buffer_tx_index %= KDE_SERIAL_TX_BUFFER_SIZE;kde_serial_transmit_active = true;// Enable TX empty and TX complete interrupts__HAL_UART_ENABLE_IT(kde_serial_port_handles, UART_IT_TXE);__HAL_UART_ENABLE_IT(kde_serial_port_handles, UART_IT_TC);

author: newbrain
author_rank: Super Contributor
timestamp: September 30, 2022, 09:08:46 pm
content: QuoteTher is no config forclockspeed on (STM32) GPIOs AFAIU. The indicated speed is just a "hint" on what one can expect depending on Vdd and loading, and affects the drive strength (probably adding parallel MOSFETs).The words I used were, in fact, taken straight from the reference manual - the sampling of the port is probably going through a synchronizer, clocked by AHB.Better suffer a small delay than having metastable input data.Quote......I've already expressed my disagreement with your FreeRTOS code structure.QuoteAgain, that'snota spurious byte! It is an idle frame (probably inserted to make sure the receiver is properly synchronized) - i.e. the natural state of the line when nothing is transmitted.And of course you can us it!You can write a character to DR, that will be shifted out in due time. Why would you want to wait (especially in a busy loop)?From what you say later, I'm sure you are checking that the transmission is completed before writing something else!Quote from: peter-h on September 30, 2022, 03:11:42 pmNo,youfixed it, I was just being my grumpy self.QuoteConsidering all that's going on, the read from SR is probably being done in the Rx thread?In my reading, but I'm not a native speaker, the last sentence just suggests to avoid writing 0 to SR to clear the bit, and rather use the read SR/write DR sequence.

author: peter-h
author_rank: Super Contributor
timestamp: October 01, 2022, 09:03:06 am
content: QuoteI went to read about this and a lot of people were asking the same question. It is a crude slew rate control, on output pins only. This is misleadingCode:#define GPIO_SPEED_FREQ_LOW 0x00000000U /*!< IO works at 2 MHz, please refer to the product datasheet */#define GPIO_SPEED_FREQ_MEDIUM 0x00000001U /*!< range 12,5 MHz to 50 MHz, please refer to the product datasheet */#define GPIO_SPEED_FREQ_HIGH 0x00000002U /*!< range 25 MHz to 100 MHz, please refer to the product datasheet */#define GPIO_SPEED_FREQ_VERY_HIGH 0x00000003U /*!< range 50 MHz to 200 MHz, please refer to the product datasheet *//This is actually a gotcha because you can config AHB down to ~10MHz (on a 168MHz CPU) which will make a UART at > 115k not run too well, sampling the input at 16x or 8x. I reckon they bypass the AHB synchroniser for UART inputs. The RM doesn't say much.I still have no idea how the double clearing of the pending int caused those problems.The TX interrupts serve two purposes: one loads the next byte into the UART (the classic "transmit under interrupt" method) and the other, tx complete, turns off an RS485 driver: