Unexpected RXDRDY count

Hello,

I'm writing my own UARTE library because I need some functionality not offered in the SDK. But I came across a hardware behaviour I can't explain.

In my understanding, the RXDRDY event is triggered when a byte is fully received in the RX pin (at the stop bit). Then, after the RX FIFO and DMA it will eventually be written to RAM.

My code, similarly to libuarte_async, uses a TIMER to detect when there is no more activity in RX and another TIMER to count the number of RXDRDY events, which should represent the number of bytes physically received at the RX pin.

The problem is, the number of RXDRDY events is sometimes not what I would expect. For instance, consider this trace:

At [1], the RXTO irq handler runs because the DMA transfer is complete. As you can see, 78 bytes were received up to that point. The RXD.AMOUNT register reads 76 and the RXDRDY count is 77. I would expect the RXDRDY count to be 78.

Then, at [2], the RXTO irq handler runs because the reception is stopped due to RX idle. At this point, the RXD.AMOUNT register reads 14 and the RXDRDY count is 13. I would expect the RXDRDY count to be 12.

In short, it looks like the RXDRDY event is not triggered when a byte is received at the RX pin.

Can you please shed some light on this behaviour?

Parents Reply Children
  • Here's the relevant code:

        //Configure RX count timer
        nrfx_timer_config_t tmr_config_count = NRFX_TIMER_DEFAULT_CONFIG;
        tmr_config_count.mode = NRF_TIMER_MODE_COUNTER;
        tmr_config_count.p_context = (void *)p_serial;
        ret = nrfx_timer_init(p_serial->p_timer_rxcount, &tmr_config_count, timer_rxcount_evt_handler);
        if (ret != NRFX_SUCCESS)
            return NRF_ERROR_INTERNAL;
        nrfx_timer_enable(p_serial->p_timer_rxcount);
    
        //Configure RX timeout timer
        nrfx_timer_config_t tmr_config_timeout = NRFX_TIMER_DEFAULT_CONFIG;
        tmr_config_timeout.frequency = NRF_TIMER_FREQ_1MHz;
        tmr_config_timeout.p_context = (void *)p_serial;
        ret = nrfx_timer_init(p_serial->p_timer_rxtimeout, &tmr_config_timeout, timer_rxtimeout_evt_handler);
        if (ret != NRFX_SUCCESS)
            return NRF_ERROR_INTERNAL;
        nrfx_timer_compare(p_serial->p_timer_rxtimeout, NRF_TIMER_CC_CHANNEL0, p_config->timeout_us, false);
    
        // ...
        
        //  When a byte is received, increment RX count timer
        PPI_CH_SETUP(p_serial->p_ctrl_blk->ppi_channels[SERIALPORT_PPI_CH_0],
                     nrf_uarte_event_address_get(p_serial->uarte, NRF_UARTE_EVENT_RXDRDY),
                     nrfx_timer_task_address_get(p_serial->p_timer_rxcount, NRF_TIMER_TASK_COUNT),
                     NULL);
        //  When a byte is received, restart RX timeout timer
        PPI_CH_SETUP(p_serial->p_ctrl_blk->ppi_channels[SERIALPORT_PPI_CH_1],
                     nrf_uarte_event_address_get(p_serial->uarte, NRF_UARTE_EVENT_RXDRDY),
                     nrfx_timer_task_address_get(p_serial->p_timer_rxtimeout, NRF_TIMER_TASK_START),
                     nrfx_timer_task_address_get(p_serial->p_timer_rxtimeout, NRF_TIMER_TASK_CLEAR));
        //  On RX timeout timer compare, stop it and stop UART RX
        PPI_CH_SETUP(p_serial->p_ctrl_blk->ppi_channels[SERIALPORT_PPI_CH_2],
                     nrfx_timer_compare_event_address_get(p_serial->p_timer_rxtimeout, 0),
                     nrfx_timer_task_address_get(p_serial->p_timer_rxtimeout, NRF_TIMER_TASK_STOP),
                     nrf_uarte_task_address_get(p_serial->uarte, NRF_UARTE_TASK_STOPRX));
    

    When the RX timeout timer compares, it triggers the STOPRX task on the UARTE.

    My RXTO irq handler then reads the RXD.AMOUNT register and the value of the other timer which I use to count the RXDRDY events.

    On that image, no more bytes are sent when the RTS is deasserted.

    No, I don't trigger FLUSHRX. If there's extra data in the RX FIFO, I'm fine with it being read when I setup the next DMA transaction, but I need to know if there's data there so I can manually restart the RX timeout timer. I need to do that because I manually stop the RX timeout timer at the beginning of the RXTO handler. Which, in turn, I do because the RX timeout timer is stopped by PPI on the RX timeout timer Compare event, but up to 4 extra bytes could be received, and those RXDRDY events would make PPI restart the RX timer.

    It's a bit mind-bending, I hope that makes sense.

    But I just want to know when the RXDRDY event is triggered. It appears it's not when the stop bit is received at the RX pin, like the docs suggest.

  • Would you be able to output the events (RXDRDY, RXTO, END, COMPARE event) on GPIOs, to read out with the logic analyzer how the events are generated compared to your incoming data and task triggering? You can PPI and GPIOTE to setup this.

    Also, if you can provide a minimal example that can be used to reproduce the behavior you are seeing, that would help us give you a better answer.

  • Here's a capture with the events, as requested. GPIOTE is set to toggle the pins when the event occours.

    Zoomed:

    In this example:

    21 bytes are received in the first block, but AMOUNT is 19 and RXDRDY count is 20. You can see that there was no RXDRDY event for the last byte. Instead, the RXDRDY for that byte occurs immediately before reasserting RTS. Here's a zoomed view:

  • Interesting issue; since the first ENDRX is generated after 19 bytes which implies AMOUNT is correct assuming the stop was triggered by hitting MAXCOUNT minus the 4 bytes due to use of flow control so is the MAXCNT = 23? That first ENDRX was not issued by hitting the timeout. My interpretation is that the unexpected ENDRX count is correct depending on latency of interrupt generation:

    "If the ENDRX event has not already been generated when the UARTE receiver has
    come to a stop, which implies that all pending content in the RX FIFO has been moved to the RX
    buffer, the UARTE will generate the ENDRX event explicitly even though the RX buffer is not full. In
    this scenario the ENDRX event will be generated before the RXTO event is generated."

Related