cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F410 uart1 lost break/framing error interrupt

law__
Associate II

Hi all,

Im using STM32F410 uart 1 to transmit and receive.
I use DMA to transmit bytes. I then use TC interrupt to send a break,
then push an event to an event queue to signal that the transmitter is free.

void interface_uart_tx_eot_isr(if_uart_t* p) {
    if (p->tx_last_in_chunk) {
        // Transmit break at end of tx work
        LockFromISR();
        p->uart_dev->usart->CR1 |= USART_CR1_SBK;
        UnlockFromISR();
        // register event for more tx data
        p->transmit_empty_handlerI(p->event_source);
    }
}

On the same uart I use DMA in circular mode to dump incoming bytes.
Im using framing error interrupt to wake up a worker to deal with the received data.
For the sake of this explanation, I have three stm32f410 devices connected in a ring
1Tx -> 2Rx, 2Tx -> 3Rx, 3Tx -> 1Rx.
So it is possible for, eg, device 2 to be recieving data as it is sending data.

This is working well except:
I find occasionally that I am missing a break interrupt and I'm pretty lost figuring it out.

Oscilloscope shot uart_break_1.jpg shows 3 cycles of data.
The first is successful (5 uart interrupts - Blue trace).
500us later the second fails (3 uart interrupts), then 500us after that the error is detected and a breakpoint hit (yellow going high on another device - data exchange is stopped).
Note the last one is semi-successful (5 interrupts).

The oscilloscope traces are:
Green: device TX
Pink: device RX
Blue: uart interrupt (gpio toggled in interrupt)

In oscilloscope screen uart_break_2.jpg:
See data is coming in via pink. The break is also present.
At the same time, data is being sent out via green.
Blue going low indicates the uart interrupt firing, then the TC interrupt handler transmits break.

I tracked the SR register of uart 1 to see if the framing error bit is actually set:

  }, {
    sr_val = 208,
    time_us = 19787747  <--- one where error occurs. FE bit is not set. SR flags: TXE, TC, IDLE
  }, {
    sr_val = 210,       <--- FE correctly captured
    time_us = 19788109
  }, {
    sr_val = 0,
    time_us = 19788138
  }, {
    sr_val = 192,
    time_us = 19788177
  }, {
    sr_val = 210,       <--- FE correctly captured
    time_us = 19788247
  }, {
    sr_val = 192,
    time_us = 19788379  <--- Last gpio toggle

These are the last 6 SR register values captured in the interrupt.
Note in uart_break_2.jpg the uart interrupt with TC flag set fires about 200ns after RX break (pink) goes low.

I'm using Chibios and will add the usart handler.
Break is capture via FE and _uart_rx_error_isr_code and TC via _uart_tx2_isr_code.
I'm at a bit of a loss. I suspect break is not quiet set when TC interrupt happens. Maybe it is then getting cleared during servicing of TC.

Note, this is working pretty well, I just get these dropped FE interrupts about every 10-20 seconds.
Any thoughts?

void serve_usart_irq(UARTDriver *uartp) {
  uint16_t sr;
  USART_TypeDef *u = uartp->usart;
  uint32_t cr1 = u->CR1;

  sr = u->SR;   /* SR reset step 1.*/

  // Record SR and signal
  LED_LCL_TOGGLE;
  sr_cap_t cap = {
    .sr_val = sr,
    .time_us = mod_time_util_us_get()
  };
  sr_capture_history[sr_history_idx] = cap;
  sr_history_idx++;
  if (sr_history_idx >= 16) {
    sr_history_idx = 0;
  }

  if (sr & (USART_SR_LBD | USART_SR_ORE | USART_SR_NE |
            USART_SR_FE  | USART_SR_PE)) {

    (void)u->DR;  /* SR reset step 2 - clear ORE.*/

    u->SR = ~USART_SR_LBD;
    _uart_rx_error_isr_code(uartp, translate_errors(sr));
  }

  if ((sr & USART_SR_TC) && (cr1 & USART_CR1_TCIE)) {
    /* TC interrupt cleared and disabled.*/
    u->SR = ~USART_SR_TC;
    u->CR1 = cr1 & ~USART_CR1_TCIE;

    /* End of transmission, a callback is generated.*/
    _uart_tx2_isr_code(uartp);
  }

  /* Timeout interrupt sources are only checked if enabled in CR1.*/
  if ((cr1 & USART_CR1_IDLEIE) && (sr & USART_SR_IDLE)) {
    _uart_timeout_isr_code(uartp);
  }
}

 

1 ACCEPTED SOLUTION

Accepted Solutions
law__
Associate II

Hey all,

I'll close this off for now. I did not find an actual solution to FE being cleared in TC interrupt.

I settled on a work-around / alternate approach:

My aim was to use UART TC to fire off a break. I disabled uart TC and instead fire off the break in DMA TC.

This is a bit sketchy, but by setting SBK a break is sent "after completing the current character transmission", which it looks like is in progress by the time DMA TC interrupt is serviced. So it is working well so far, but I do accept I might have to revisit if DMA TC fires too early.

 

Thank again

View solution in original post

5 REPLIES 5
waclawek.jan
Super User

UART_SR.FE is cleared by a sequence of read-SR-then-read-DR.

If your ISR fires because of UART_SR.TC, reads UART_SR (priming the sequence) with UART_SR.FE=0, then the break's end occurs, UART_SR.FE gets set, the Rx DMA reads UART_DR, that completes the sequence and clears UART_SR.FE.

What's IMO more dangerous is reading UART_DR in the ISR, while simultaneously DMA on Rx is set. Consider a scenario where say UART_SR.NE is set, so that the ISR branches into the UART_DR-reading code. Now if a byte arrives so that UART_SR.RXNE is set a few cycles before code reads UART_DR, the Rx DMA starts to go through its paces when the processor reads UART_DR and thus removes the UART_SR.RXNE signal, that may lead to the Rx DMA Stream to lock up. The coincidence needed for this to happen is high and the probability low, thus you'd end up with a rarely occuring problem detrimentally affecting your system reliability.

There may or may not be a solution for the primary issue in the method you've devised, I don't know. UART Rx and DMA don't mix that well and there are several corner/race cases like these to be considered, using interrupts are simpler thus potentially safer. The newer STM32 UARTs are slightly different in the way how flags are cleared. Another option to contemplate is also to use two separate UARTs for Tx and Rx.

JW

Thanks for your reply.

If I can't find any other solution, I'll look at inspecting DMA NDTR at the end of TC worker, then if it has changed fire off the RX worker. Its a bit of a cludge, but might get me out of trouble.

Unfortunately I can't easily use separate uarts for TX and RX. I'm updating software for a legacy board design.

waclawek.jan
Super User

Just be careful with that NDTR...

Have you contemplated using IDLE?

JW

Hey, yes my NDTR plan did not work out. There were cases where checking NDTR would incorrectly trigger the RX worker.

I have considered IDLE, but I suspect I will encounter the same issue. It looks like IDLE bit is cleared in the same manner as FE - a read to SR followed by a read to DR.

Today I will look at just disabling uart TC completely. I'll use DMA TC to start a timer (or an event worker to poll TXE or something), then when the timer expires or TXE shows the uart is ready, do my uart TC tasks. This is most unpleasant, but will eliminate the two uart flag events from coinciding.

As an aside, another suggestion has been to look at using LIN break mode via LINEN and LDBIE. I did have a quick try at this (I ran out of time), but was unsuccessful (I got noise and parity error flags). I don't have experience with LIN, but it looks like stm32 LIN mode expects break to occur, followed by sync character. LIN reception description says the receiver will stop until the break detector receives either a 1 or a delimiter character. To me this sounds like I cannot use it to signal the *end* of a block of data arriving. If anyone can correct my thinking, I would be interested in further investigation.

Thanks again

law__
Associate II

Hey all,

I'll close this off for now. I did not find an actual solution to FE being cleared in TC interrupt.

I settled on a work-around / alternate approach:

My aim was to use UART TC to fire off a break. I disabled uart TC and instead fire off the break in DMA TC.

This is a bit sketchy, but by setting SBK a break is sent "after completing the current character transmission", which it looks like is in progress by the time DMA TC interrupt is serviced. So it is working well so far, but I do accept I might have to revisit if DMA TC fires too early.

 

Thank again