Author Topic: 32F4 - a weird UART issue  (Read 812 times)

0 Members and 1 Guest are viewing this topic.

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3669
  • Country: gb
  • Doing electronics since the 1960s...
32F4 - a weird UART issue
« on: September 30, 2022, 07:52:34 am »
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 interrupt

There 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 ISR

Code: [Select]

void Kde_serial_receive_irq_handler(UART_HandleTypeDef *huart)
{
uint8_t temp = huart->Instance->DR; // fetch the byte received
uint8_t port = KDE_SERIAL_MAX_PORT; // max port number
int32_t count = 0;

__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_RXNE);

// Find which port this is in order to know which buffer to use
while (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 full
if (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 value
if (kde_serial_port_configs[port].bits_per_word == 7) {
temp &= 0x7f;
}

kde_serial_receive_buffer[port][kde_serial_receive_buffer_rx_index[port]++] = temp;

kde_serial_receive_buffer_rx_index[port] %= KDE_SERIAL_RX_BUFFER_SIZE;

if (kde_serial_receive_buffer_rx_index[port] == kde_serial_receive_buffer_get_index[port])
{
// 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 accessed
kde_serial_receive_buffer_get_index[port]++;
kde_serial_receive_buffer_get_index[port] %= 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?
« Last Edit: September 30, 2022, 11:53:13 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: 32F4 - a weird UART issue
« Reply #1 on: September 30, 2022, 01:00:18 pm »
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:
Quote
the 32F4 UART transmits "an empty frame" there, apparently without a start bit

When transmission is enabled, an idle frame 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)?

Quote
flush RX buffer, as many times as the FIFO is deep (2x for the 32F4 one)
Why 2x? There's no FIFO in STM32F417, AFAICS - clearing the status flags should be enough.

Quote
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"
The 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.
« Last Edit: September 30, 2022, 01:02:44 pm by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: peter-h

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3669
  • Country: gb
  • Doing electronics since the 1960s...
Re: 32F4 - a weird UART issue
« Reply #2 on: September 30, 2022, 02:37:36 pm »
Quote
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.

I 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: [Select]

void Kde_serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart)
{

// Transmit complete
uint8_t port = KDE_SERIAL_MAX_PORT; // max port number

// Find which port this is
while (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance)
{
port--;
}

// Transmit complete
kde_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
When transmission is enabled, an idle frame 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)?

"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.

Quote
Why 2x? There's no FIFO in STM32F417, AFAICS - clearing the status flags should be enough.

With 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.

Quote
The 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.

I 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 is not exhibiting 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 configured :)

This issue could have gone for years without anybody noticing. It is only a full-on loopback test at 115k that shows it.

Code: [Select]

// Common interrupt handler for all USART interrupts
static void usart_common_IRQHandler(UART_HandleTypeDef *huart)
{
// check if we are here because of RXNE interrupt
if (huart->Instance->SR & USART_SR_RXNE) { //if RX is not empty
Kde_serial_receive_irq_handler(huart);
}

// check if we are here because of TXEIE interrupt
if (huart->Instance->SR & USART_SR_TXE) { //if TX is empty
Kde_serial_transmit_irq_handler(huart);
}

// check if we are here because of the TC interrupt
if (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 */
}


« Last Edit: September 30, 2022, 02:56:17 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3669
  • Country: gb
  • Doing electronics since the 1960s...
Re: 32F4 - a weird UART issue
« Reply #3 on: September 30, 2022, 03:11:42 pm »
Newbrain fixed it!

It was the surplus (double) clearing of the interrupts.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 
The following users thanked this post: newbrain

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3669
  • Country: gb
  • Doing electronics since the 1960s...
Re: 32F4 - a weird UART issue
« Reply #4 on: September 30, 2022, 07:10:55 pm »
However, I am surprised that this ISR works given that it no longer clears TC

Code: [Select]

void Kde_serial_transmit_complete_irq_handler(UART_HandleTypeDef *huart)
{

// Transmit complete
uint8_t port = KDE_SERIAL_MAX_PORT; // max port number

// Find which port this is
while (port > 0 && huart->Instance != kde_serial_port_handles[port-1]->Instance)
{
port--;
}
// Transmit complete
kde_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 states



but 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 right ;)

Code: [Select]

// Transmitter is not already running so load the first byte and enable the TXE and TC interrupts
// First clear TC
kde_serial_port_handles[port]->Instance->SR &= ~(UART_FLAG_TC);
kde_serial_port_handles[port]->Instance->DR = kde_serial_transmit_buffer[port][kde_serial_transmit_buffer_tx_index[port]++];
kde_serial_transmit_buffer_tx_index[port] %= KDE_SERIAL_TX_BUFFER_SIZE;
kde_serial_transmit_active[port] = true;

// Enable TX empty and TX complete interrupts
__HAL_UART_ENABLE_IT(kde_serial_port_handles[port], UART_IT_TXE);
__HAL_UART_ENABLE_IT(kde_serial_port_handles[port], UART_IT_TC);
« Last Edit: September 30, 2022, 07:13:12 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: 32F4 - a weird UART issue
« Reply #5 on: September 30, 2022, 09:08:46 pm »
Quote
I 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.
Ther is no config for clock speed 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
but it isn't running the same RTOS tasks
... :horse:...
I've already expressed my disagreement with your FreeRTOS code structure.

Quote
when you enable the UART, you can't use it for anything because it transmits that spurious byte
Again, that's not a 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!

Newbrain fixed it!

It was the surplus (double) clearing of the interrupts.
No, you fixed it, I was just being my grumpy self.

Quote
I am surprised that this ISR works given that it no longer clears TC
Considering 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.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3669
  • Country: gb
  • Doing electronics since the 1960s...
Re: 32F4 - a weird UART issue
« Reply #6 on: October 01, 2022, 09:03:06 am »
Quote
the sampling of the port is probably going through a synchronizer, clocked by AHB.
Better suffer a small delay than having metastable input data.

I 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 misleading

Code: [Select]
#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:


Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf