This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts
This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Bug in app_uart_fifo.c

Hi,

I'm attempting to use the UART in a high speed configuration without flow control but I get regularely get two kinds of errors in the output:

  • Lost characters
  • Reversed characters

Expected UART test output:

...
0123456789
0123456789
0123456789
0123456789
0123456789
0123456789
...

Sample of actual UART output:

...
123456798
0123465789
0132456789
123456798
0123465789
0132456789
123456798
0123546789
0213456789
0123457689
...

The output is visible on different hardware:

  • Our own nrf51822 boards
  • pca10028 when not running the UART through the Interface chip/USB

Pseudo code for my setup:

  • No softdevice used
  • Only RX/TX, HWFC disabled and RTS/CTS lines disconnected
  • UART_BAUDRATE_BAUDRATE_Baud921600
#define LENGTH 24 
int main(void) {
    char buf[LENGTH] = "0123456789\r\n0123456789\r\n";
  
    while (true)
    {
        for( int i = 0; i < LENGTH; i++) {
            while(app_uart_put(buf[i]) != NRF_SUCCESS);
        }
    }
}

I have found potential problem sources in app_uart_fifo.c. To me, the underlying problem seems to be that calling app_uart_put() in rapid succession results in a race condition between direct calls to nrf_drv_uart_tx() in app_uart_put() and the emptying of the fifo also done with calls to nrf_drv_uart_tx() in uart_event_handler(). The former calls are made from thread mode and the later from interrupt (LOW) mode.

Example:

Say that the app uart tx fifo contains data that is being processed due to a previous call to app_uart_put(). A new call to app_uart_put() calls nrf_drv_uart_tx() precisely after a byte has been sent by the UART driver, but before the TXDRDY signal is serviced. Then:

  1. Two bytes could be sent in the reversed order, i.e. a byte not in the fifo is sent to the uart driver before a byte is taken from the fifo, or
  2. A byte is silently dropped as uart_event_handler() calls nrf_drv_uart_tx() without checking the results. What if nrf_drv_uart_tx() returns NRF_ERROR_BUSY due to the race condition above?

I modified app_uart_fifo.c successfully by doing the following:

  1. In app_uart_fifo.c initialize a SWI with the same priority as the UART driver (LOW)
  2. In app_uart_put() call app_fifo_put() and then nrf_drv_swi_trigger(). Never call nrf_drv_uart_tx() directly.
  3. In both uart_event_handler() and swi_event_handler() call a common function: app_uart_tx()
static void app_uart_tx(void)
{
    app_uart_evt_t app_uart_event;

    // Get next byte from FIFO.
    if (app_fifo_get(&m_tx_fifo, tx_buffer) == NRF_SUCCESS)
    {
        while(nrf_drv_uart_tx(tx_buffer,1) != NRF_ERROR_BUSY);
    }
    if (FIFO_LENGTH(m_tx_fifo) == 0)
    {
        // Last byte from FIFO transmitted, notify the application.
        app_uart_event.evt_type = APP_UART_TX_EMPTY;
        m_event_handler(&app_uart_event);
    }
}

This is the same logic used in uart_event_handler() today with the addition of looping the nrf_drv_uart_tx() until success.

The changes above result in stable UART output as expected, i.e. no missing or reversed characters.

Is there some way of achieving the same results without using a SWI? I tried NVIC_DisableIRQ(UART0_IRQn) together with _DSB and _ISB but never got it to work properly.

/Pablo

  • I'll let someone else comment about the app_uart issue (I'm not very familiar with the module), but I can answer some of your other questions.

    First of all, the PCA10028 boards (and similar) gets the UART routed through the onboard Segger chip, which retransmits the bytes over USB. This chip is what will receive the hardware flow control lines, and they should be enabled in those case.

    Secondly, interfacing with the UART peripheral is fairly easy without interrupts, but it consumes more power. Here is some sample code on how to send bytes as fast as possible. You should disable the peripheral afterwards to stop it from requesting the more expensive 16MHz clock.

    // Must be called once initially
    void uart_init(void)
    {
      NRF_UART0->ENABLE           = 0x04;
    
      NRF_UART0->PSELRTS = BOARD_UART0_RTS;
      NRF_UART0->PSELTXD = BOARD_UART0_TX;
      NRF_UART0->PSELCTS = BOARD_UART0_CTS;
      NRF_UART0->PSELRXD = BOARD_UART0_RX;
    
      NRF_UART0->BAUDRATE         = 0x10000000; // 1M baud
      NRF_UART0->CONFIG           = (UART_CONFIG_HWFC_Enabled << UART_CONFIG_HWFC_Pos);
      NRF_UART0->EVENTS_RXDRDY    = 0;
      NRF_UART0->TASKS_STARTTX    = 1;
      NRF_UART0->TASKS_STARTRX    = 1;
    }
    
    // Send a single byte
    void uart_putchar(uint8_t ch)
    {
      NRF_UART0->EVENTS_TXDRDY = 0;
      NRF_UART0->TXD = ch;
      while(NRF_UART0->EVENTS_TXDRDY != 1){}  // Wait for TXD data to be sent
      NRF_UART0->EVENTS_TXDRDY = 0;
    }
    

    Then replace the line where you call app_uart_put with uart_putchar instead.

    To stop the UART, set ENABLE to 0x00 and enable the TASKS_STOPRX and TASKS_STOPTX tasks. To enable it again, set ENABLE to 0x04 and start RX and/or TX.

    You mentioned RX as well in your question though, and to accomplish RX you would have to poll NRF_UART0->EVENTS_RXDRDY. If it's 1, set it to 0 and pull the byte from NRF_UART0->RXD.

  • Firstly, on the pca10028 I am not running UART through the interface Segger chip, I have reconfigured the UART pins to P0.15 and P0.16 and connect a FTDI TTL-232 cable directly to these pins. The reason for doing this was to reproduce the observed problem on common hardware in case someone else wanted to investigate further.

    Secondly, I favor the app_uart solution of driving the output to the UART through interrupts instead of busy waiting for the TXDRDY event. But your proposal has benefits; less code and easier to overview :)

  • I figured as much, after reading your question again. I just wanted to show an example on how to do this without any interrupts or potential race conditions. The app_uart example is quite complex now, and it's not easy to understand what's happening under the cover.

    It looks like you already had a solution to the problem you identified, so it's a matter of flagging this internally. There is a known issues thread here devzone.nordicsemi.com/.../ - and I've notified the individual updating it. I hope he will answer here too.

  • I also encountered the same problem, reported it here: devzone.nordicsemi.com/.../

  • The issue is reported and supposed to be fixed in the next SDK release.

Related