This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

UART receive in background without callback on nRF52 SDK11

Hi, I'm looking for an easy way to use the UART in blocking mode (or something similiar) with a receive buffer that is filled in background:

  • If possible using EasyDMA for better performance and easier software design
  • TX data may either block until all data has been sent or may return immediately and provide an additional function call to check if all data has been sent successfully.
  • RX data shall received in background and stored in a buffer/FIFO. It shall be possible to read data of arbitrary size from the receive buffer without stopping reception.
  • No callbacks or event handlers shall be used (synchroneous control flow in application).

In the past, on the nRF51, we used the app_uart library with FIFO support. Looking at the SDK11 documentation on infocenter.nordicsemi.com, I couldn't find any information about this library (although the directory is still there in the SDK11 archive). Is app_fifo still working on SDK11 on nRF52? Does it take advantage of the EasyDMA feature?

Looking at nrf_drv_uart.h, double buffering is mentioned to provide support for continuous data reception in non-bocking mode. But what does happen if the last received data byte does not fill the buffer to 100%? Will the driver wait for more bytes until the buffer is full, not giving any opportunity to receive data from the partially filled buffer? In other words, is there any possibility to receive "byte streams" of arbitrary length using UART with EasyDMA (something like app_uart with FIFO feature using EasyDMA)?

Parents
  • Based on my discussion with bat13 in the comments, I have been looking into the UARTE documentation in Product Specification v1.0 and came to the following conclusion:

    My current assumption is that it should be possible to prevent data loss if the START_RX task is triggered immediately after the END_RX event (see shortcut in SHORTS register). If the corresponding shortcut is set, START_RX should be triggered by END_RX again, just like when the RX buffer has become full. If you have a look at figure 95, p332, the shortcut has to be reset to really stop reception ("ENDRX_STARTRX = 0") If this bit stays 1, reception should continue.

    I didn't have time yet to check if this feature is already supported by the driver already (when used in EasyDMA mode).

    Race conditions also seem to become important here: A critical situation is when STOP_RX is issued shortly after the current RX buffer becomes full and triggers a END_RX task - END_RX might be triggered twice within a short time, before there was time to update the double buffered pointers... this might lead to corruption of RX data. Is there a good way to prevent this scenario? Maybe the FLUSHRX task can help?

    I'm really curious about is the FLUSHRX task. According to the current Product Specification, it is only used if RX has been stopped. Maybe it can also be used during reception to switch to the next RX buffer? Although that would be pretty much the same as described above with the STOP_RX task and the shortcut. Would be nice to have some more information.

    Update:

    One more question to the Nordic engineers

    From the moment when a full buffer is detected (which initiates a buffer update using the RX.PTR) to the moment when the RX.END interrupt handler is called, is it possible that application code which writes to the RX_STOP task is executed? And how will the STOP_RX task be handled in this case?

    Clean buffer switching might work under the following conditions:

    • Either a buffer full condition immediately interrupts the application control flow and switches to the END_RX interrupt hander, so that calling the STOP_RX task from a lower priority than the interrupt handler is prevented
    • Or a STOP_RX task that is issued from the moment when a full buffer is detected to the moment the interrupt handler is called will be merged with the full buffer STOP_RX event

    To the application engineers: A workaround proposal

    • Create a large RX buffer which cannot become full within a defined time interval, even in a worst case scenario.
    • Within the mentioned time interval, periodically call the STOP_RX task to switch to the next buffer. It might even be possible to use a hardware timer and the PPI to trigger the STOP_RX task.
    • In the END_RX event handler, configure the next buffer (double buffering) and handle the bytes received (if any).
    • In your timing calculations, take the timing requirements from the SoftDevice into account (it might be busy for quite some time and delay the END_RX handler, so that more data might arrive in the buffer than originally calculated).

    This way, using the EasyDMA feature, it should be possible to implement robust UART reception that doesn't lose data when the SoftDevice is active (e.g. during a radio event). The old UART device only had a short FIFO buffer that would overflow on long SoftDevice activities.

    The major drawback of this implementation is that it will cause periodic interrupts, even if no data has been received.

    Maybe the sample posted by Hung Bui (have a look at his post) shows something similiar to this workaround. I didnt have time to try it out yet. My extra suggestion to his implementation would be to use a large RX buffer that cannot be filled within the configured time interval (+ some extra time for the SoftDevice).

Reply
  • Based on my discussion with bat13 in the comments, I have been looking into the UARTE documentation in Product Specification v1.0 and came to the following conclusion:

    My current assumption is that it should be possible to prevent data loss if the START_RX task is triggered immediately after the END_RX event (see shortcut in SHORTS register). If the corresponding shortcut is set, START_RX should be triggered by END_RX again, just like when the RX buffer has become full. If you have a look at figure 95, p332, the shortcut has to be reset to really stop reception ("ENDRX_STARTRX = 0") If this bit stays 1, reception should continue.

    I didn't have time yet to check if this feature is already supported by the driver already (when used in EasyDMA mode).

    Race conditions also seem to become important here: A critical situation is when STOP_RX is issued shortly after the current RX buffer becomes full and triggers a END_RX task - END_RX might be triggered twice within a short time, before there was time to update the double buffered pointers... this might lead to corruption of RX data. Is there a good way to prevent this scenario? Maybe the FLUSHRX task can help?

    I'm really curious about is the FLUSHRX task. According to the current Product Specification, it is only used if RX has been stopped. Maybe it can also be used during reception to switch to the next RX buffer? Although that would be pretty much the same as described above with the STOP_RX task and the shortcut. Would be nice to have some more information.

    Update:

    One more question to the Nordic engineers

    From the moment when a full buffer is detected (which initiates a buffer update using the RX.PTR) to the moment when the RX.END interrupt handler is called, is it possible that application code which writes to the RX_STOP task is executed? And how will the STOP_RX task be handled in this case?

    Clean buffer switching might work under the following conditions:

    • Either a buffer full condition immediately interrupts the application control flow and switches to the END_RX interrupt hander, so that calling the STOP_RX task from a lower priority than the interrupt handler is prevented
    • Or a STOP_RX task that is issued from the moment when a full buffer is detected to the moment the interrupt handler is called will be merged with the full buffer STOP_RX event

    To the application engineers: A workaround proposal

    • Create a large RX buffer which cannot become full within a defined time interval, even in a worst case scenario.
    • Within the mentioned time interval, periodically call the STOP_RX task to switch to the next buffer. It might even be possible to use a hardware timer and the PPI to trigger the STOP_RX task.
    • In the END_RX event handler, configure the next buffer (double buffering) and handle the bytes received (if any).
    • In your timing calculations, take the timing requirements from the SoftDevice into account (it might be busy for quite some time and delay the END_RX handler, so that more data might arrive in the buffer than originally calculated).

    This way, using the EasyDMA feature, it should be possible to implement robust UART reception that doesn't lose data when the SoftDevice is active (e.g. during a radio event). The old UART device only had a short FIFO buffer that would overflow on long SoftDevice activities.

    The major drawback of this implementation is that it will cause periodic interrupts, even if no data has been received.

    Maybe the sample posted by Hung Bui (have a look at his post) shows something similiar to this workaround. I didnt have time to try it out yet. My extra suggestion to his implementation would be to use a large RX buffer that cannot be filled within the configured time interval (+ some extra time for the SoftDevice).

Children
  • Launched double buffer (something like cyclic DMA mode to STM32). In the interrupt indicates the beginning or middle of the buffer. NRF_UARTE_SHORT_ENDRX_STARTRX automatically starts the next buffer. Working. Sometime timeout call nrf_drv_uart_rx_abort. I fall into NRF_UARTE_EVENT_ENDRX handler. I can get a part of the buffer and switch to the next. NRF_UARTE_EVENT_RXTO in such cases do not occur. Still in doubt about the loss of data. Perhaps it is necessary to cause NRF_UARTE_TASK_FLUSHRX. But I can not understand at what point. Reception do not stop. Pooling is not very convenient. by Interrupt the flow of events would have been better.

  • UARTE does have a small internal RX FIFO. It can receive up to 4 bytes after the RX_STOP task has been triggered (in fact that seems to be intended for robust flow control when the communication partner sends some more bytes after RTS is released). So, if the STOPRX task is triggered, some more bytes may reach the RX circuit, and they may be received after the ENDRX event has been triggered. This is why, after receiving the last valid buffer in ENDRX, one more buffer can be used in RXTO (timeout) to read those remaining bytes into a separate buffer. If you don't use flow control, you can ignore the FLUSHRX task as you will lose data anyway (because there is no flow control that stops the sender).

    The great benefit of EasyDMA is that no interrupt is issued for each byte that has been received. This can greatly improve performance, and also, it makes the UART more robust while the high priority Bluetooth code within the SoftDevice is running which may delay your UART interrupt handler for so long that data will be due to a FIFO overflow - the hardware FIFO on the classic UART is only 6 bytes. With EasyDMA, you can use much larger buffers that are also handled in background (while BT code is running).

  • I read it in the documentation. But in this case, all remaining data from the FIFO falls to the next buffet (in theory, the reception time is active). Therefore, such additional processing RXTO not needed. Maybe I'm wrong :) Another question, if happened NRF_UARTE_EVENT_ENDRX, but the data did not come, whether to switch to the next buffer will happen. I do not tested it yet.

  • if the ENDRX-STARTRX shortcut is not set, there will be no "next buffer". And if the shortcut is set, receiving will be restarted and no RXTO will be issued. A new buffer is only used at the moment the STARTRX task is triggered. But like I saif, RXTO only seems to make sense when using flow control. Without flow control, it may be used, but doesn't help very much.

Related