High Speed UART on Nordic nRF52 Series

This blog is to show how to run the high speed UART on Nordic NRF52 Series MCU. I would introduce new advance UART library (libuarte) on the SDK 16.0 which is ready on the production release.

Following up the post ( https://jimmywongiot.com/2019/04/17/overview-uart-driver-handling-at-nrf52/), I would like to show another approach how to configure UART on nRF52 in order to achieve high speed such as 256000 or 1Mbps.

The DMA length of the nRF52 Series are

ChipsetMaximum length of UARTE DMA (bit)
NRF5281010 (1024 bytes)
NRF5281114 (4096 bytes)
NRF52832 8 (256 bytes)
NRF52833 16 (65536 bytes)
NRF52840 16 (65536 bytes)

In the Nordic nRF5 SDK, many developers would use the app_uart_fifo driver for UART communcation for external MCU / PC.

The UART default configuration is located in sdk_config.h. If UARTE is present on the chip, the driver can be configured at runtime to support UART mode, UARTE mode, or both. The following example shows how to configure the driver to support both modes in runtime and have legacy mode as the default:

#ifdef NRF52
#define UART0_USE_EASY_DMA        false
#define UART_LEGACY_SUPPORT       1
#endif //NRF52

By default, the app_uart_fifo is based on the legacy uart driver which can support both nRF51 and nRF52. It gets one byte on every time (imply that the DMA length = 1).

By using the app_uart_fifo driver, it would get challenge on higher baud rate during the BLE link is established.

According from the Softdevice specification (such as S140 v2.1), the priority of 0, 1 and 4 are used for the stack. The priority of application level can use 2, 3, 5, 6, 7.

It means that the application interrupt would be blocked by softdevice priority task.

On difference scenarios such as advertising, peripheral connection, there are difference interrupt latency.

The maximum interrupt latency of advertising is 170us.

The maximum interrupt latency during the peripheral connect is 250us.

This is the reason why sometimes there are some data loss during the high UART baud rate.

Libuarte – advanced UARTE driver

SDK 16.0 introduces the advanced UARTE driver (Libuarte) and it is ready for the official release instead of experimental.

You can find all the details at


Libuarte is a UARTE library that consists of the following layers:

  • nrf_libuarte_drv – a low level UARTE driver, with extended functionality like a continuous counting of received bytes, double buffering, optional events for starting and stopping receiver, and an optional task that can be triggered when the receiver is started and stopped.
  • nrf_libuarte_async – a library suitable for receiving and transmitting asynchronous packets. It manages the receive buffers and implements the receiver inactivity timeout. The library is using nrf_libuarte. It is meant to be used in a typical UART use case, in which the counterparty is asynchronously sending packets of variable length. In such case, user gets an event on packet boundary (that is, on timeout) and whenever the DMA buffer is full.


nrf_libuarte_drv is using EasyDMA double buffering feature and PPI connections to ensure a reliable reception. The continuous reception is established by the STARTRX task, connected to the ENDRX event. Once the STARTRX task is started, the RXSTARTED event is generated.

On the RXSTARTED event:

  • EasyDMA is configured for the transfer and can be configured for the next transfer.
  • Configuration registers are latched.

Additionally, on the RXSTARTED event, nrf_libuarte_drv is calling the user handler with the request for the next buffer. The handler responds with the new buffer. As long as the buffer is provided before the end of the transfer that has just started, no data will be lost.

With this approach, the system latency depends on the size of the buffers used. If buffers are large enough, the system can ensure the complete reception without flow control with a multimillisecond latency. This minimal latency can cover for the SoftDevice or any other higher priority interrupts and flash operations (including flash page erase).

Moreover, a dedicated TIMER peripheral in the counter mode is used to track the number of bytes received.

nrf_libuarte_drv can be configured with hardware events that will start and stop the receiver. This option can be used, for example, to build a low power UART protocol with request and response pins that will enable the receiver only during a transfer. All hardware tasks and events are connected with PPI, which allows autonomous operation that does not depend on the interrupt handling time.

Hardware resources usage

nrf_libuarte_drv is using the following hardware resources:

  • UARTE instance
  • TIMER instance
  • PPI channels:
    • If optional tasks and events are not used, a minimum of 2 PPI channels (3 on devices with a maximum of 255 EasyDMA transfers).
    • If optional tasks and events are used, a maximum of 9 PPI channels and 2 PPI groups.

nrf_libuarte_drv usage

nrf_libuarte_drv is initialized with nrf_libuarte_drv_init.

The configuration includes:

  • UART configuration (pins, baurdate, hardware flow control and parity)
  • interrupt priority
  • hardware events (start RX, end RX)
  • tasks (RX started, RX done)
  • TIMER peripheral used for counting bytes
  • user event handler


The receiver is activated using a call to nrf_libuarte_drv_rx_start with the receive buffer and the information if the receiver should be started immediately or via PPI.

Whenever nrf_libuarte_drv sets a EasyDMA receive buffer, a buffer request event (NRF_LIBUARTE_DRV_EVT_RX_BUF_REQ) is generated. In such case, make sure the application responds with nrf_libuarte_drv_rx_buf_rsp and provides the new buffer. The new receive buffer must be provided before the end of the transfer that has just started, otherwise UARTE will start overwriting the active buffer, since the reception restarts autonomously.

On every completion of the EasyDMA transfer, the NRF_LIBUARTE_DRV_EVT_RX_DATA event is generated.


nrf_libuarte_drv allows transmitting packets with length of 65535 bytes maximum.

If the device does not support 16-bit-long EasyDMA transfers, the EasyDMA double buffering feature and the PPI connection between the ENDTX event and the STARTTX task are used to ensure continouos transfer. On transfer completion, the NRF_LIBUARTE_DRV_EVT_TX_DONE event is generated.


nrf_libuarte_async library is built on top of the lib_libuarte_drv driver. It implements a receiver inactivity timeout, which results in an event with the amount of data received.

The library has dedicated buffers for reception and handles the buffer request event from the driver (NRF_LIBUARTE_DRV_EVT_RX_BUF_REQ).

Timeout implementation

You can implement the receiver inactivity timeout using one of the following options, depending on the available resources:

  • a dedicated TIMER peripheral
  • a dedicated RTC peripheral
  • an app_timer instance

TIMER/RTC peripheral

The TIMER or RTC peripheral is used along with the PPI connection between the byte boundary event (RXDRDY) and the task CLEAR in the TIMER/RTC peripheral.

TIMER/RTC is configured in such a way as to trigger an interrupt on the compare event. An interrupt is triggered when a task CLEAR is not triggered on time.

The timeout in configurable and the resolution equals the tick length of the peripheral used.

app_timer instance

Alternatively to TIMER/RTC peripheral, an app_timer instance can be used for the timeout implementation. This approach requires generating an app_timer event periodically at the specified time interval.

At the timeout, the app_timer event handler checks if the number of received bytes has changed. Based on this check, the handler reports the packet boundary.

The timeout resolution is lower compared with TIMER/RTC peripheral and equals the shortest app_timer timeout that can be set.

Hardware resources usage

nrf_libuarte_async is using the following hardware resources:

  • nrf_libuarte_drv driver in the most basic configuration:
    • UARTE instance
    • TIMER instance
    • 2 or 3 PPI channels
  • If TIMER or RTC peripheral is used for the receiver inactivity timeout, the following additional resources are used:
    • RTC or TIMER instance
    • 2 PPI channels

nrf_libuarte_async usage

nrf_libuarte_async is initialized with nrf_libuarte_async_init.

The configuration includes:

  • same elements as for the nrf_libuarte_drv configuration (see nrf_libuarte_drv usage)
  • receiver inactivity timeout
  • timeout infrastructure (TIMER, RTC, or app_timer)
  • amount of memory dedicated for the receiver


nrf_libuarte_async_enable enables the receiver.

On packet or buffer boundary, an event handler is called with NRF_LIBUARTE_ASYNC_EVT_RX_DATA. The event structure contains a pointer to the data and the amount of data received. When you process the data, it must be returned to the library with a call to nrf_libuarte_async_rx_free.


nrf_libuarte_async_tx is used to start the transfer.

The event handler with the NRF_LIBUARTE_ASYNC_EVT_TX_DONE event is called on the transfer completion.

Resource on RTC

nRF52810Yes Yes
nRF52811 Yes Yes
nRF52832 Yes Yes Yes
nRF52833 Yes Yes Yes
nRF52840 Yes Yes

Resource requirements on Bluetooth Stack (Softdevice)

Inside the softdevice specification, the RTC0 and Timer0 are used by Softdevice. The application can’t use them.

Meanwhile the app_timer library is used the RTC1. It implies that there are no free RTC for the libuarte library.

Thus, if you need to use libuarte on nRF52 series, you should only have such configuration because of the limited number of RTC on nRF5281x.

NRF52RTC / TimerPPIUART Instanc
nRF528102 x Timer Instance Yes Yes
nRF528112 x Timer Instance Yes Yes
nRF52832 1 Timer Instance + 1 RTC Instance Yes Yes
nRF52833 1 Timer Instance + 1 RTC Instance Yes Yes
nRF52840 1 Timer Instance + 1 RTC Instance Yes Yes

7 thoughts on “High Speed UART on Nordic nRF52 Series

  1. How can I initialize 2 UART with libuarte in nRF52840 ? thanks

    I wrote
    nrf_libuarte_async_config_t nrf_libuarte_async_config = {
    .tx_pin = TX_PIN_NUMBER,
    .rx_pin = RX_PIN_NUMBER,
    .baudrate = NRF_UARTE_BAUDRATE_115200,
    .timeout_us = 100,
    .int_prio = APP_IRQ_PRIORITY_LOW

    nrf_libuarte_async_config_t nrf_libuarte_async_config1 = {
    .tx_pin = SER_APP_TX_PIN,
    .rx_pin = SER_APP_RX_PIN,
    .baudrate = NRF_UARTE_BAUDRATE_9600,
    .timeout_us = 100,
    .int_prio = APP_IRQ_PRIORITY_LOW

    err_code = nrf_libuarte_async_init(&libuarte, &nrf_libuarte_async_config, uart_event_handler, (void *)&libuarte);
    //nrf_libuarte_async_init(&libuarte1, &nrf_libuarte_async_config1, uart_event_handler1, (void *)&libuarte1);


  2. nrf_libuarte_async_init(&libuarte, &nrf_libuarte_async_config, uart_event_handler, (void *)&libuarte);
    nrf_libuarte_async_init(&libuarte1, &nrf_libuarte_async_config1, uart_event_handler1, (void *)&libuarte1); ?


    1. You can find nrf_libuarte_async.h. it has specify the resource and instance of the libuarte.

      if you need to get more help, please go through the Nordic devzone official channel.

      #define NRF_LIBUARTE_ASYNC_DEFINE(_name, _uarte_idx, _timer0_idx,\
      _rtc1_idx, _timer1_idx,\
      _rx_buf_size, _rx_buf_cnt) \
      STATIC_ASSERT(_rx_buf_cnt >= 3, “Wrong number of RX buffers”);\
      (_rtc1_idx == NRF_LIBUARTE_PERIPHERAL_NOT_USED) && \
      (_timer1_idx == NRF_LIBUARTE_PERIPHERAL_NOT_USED)), \
      “App timer support disabled”);\
      NRF_LIBUARTE_DRV_DEFINE(CONCAT_2(_name, _libuarte), _uarte_idx, _timer0_idx);\
      NRF_QUEUE_DEF(uint8_t *, CONCAT_2(_name,_rxdata_queue), _rx_buf_cnt, NRF_QUEUE_MODE_NO_OVERFLOW);\
      NRF_BALLOC_DEF(CONCAT_2(_name,_rx_pool), _rx_buf_size, _rx_buf_cnt);\
      /* Create TIMER instance only if _timer1_idx != NRF_LIBUARTE_PERIPHERAL_NOT_USED */ \
      NRFX_CONCAT_3(NRFX_TIMER, _timer1_idx, _ENABLED),\
      (STATIC_ASSERT((_timer1_idx == NRF_LIBUARTE_PERIPHERAL_NOT_USED) || (CONCAT_3(NRFX_TIMER,_timer1_idx, _ENABLED) == 1), “TIMER instance not enabled”);\
      static const nrfx_timer_t CONCAT_2(_name, _timer) = NRFX_TIMER_INSTANCE(_timer1_idx);),\
      (/* empty */))\
      /* Create RTC instance only if _timer1_idx != NRF_LIBUARTE_PERIPHERAL_NOT_USED */ \
      NRFX_CONCAT_3(NRFX_RTC, _rtc1_idx, _ENABLED),\
      (STATIC_ASSERT((_rtc1_idx == NRF_LIBUARTE_PERIPHERAL_NOT_USED) || (CONCAT_3(NRFX_RTC,_rtc1_idx, _ENABLED) == 1), “RTC instance not enabled”);\
      static const nrfx_rtc_t CONCAT_2(_name, _rtc) = NRFX_RTC_INSTANCE(_rtc1_idx);),\
      (/* empty */))\
      (/* empty */),\
      (_LIBUARTE_ASYNC_EVAL(NRFX_CONCAT_3(NRFX_RTC, _rtc1_idx, _ENABLED),(/* empty */), \
      (APP_TIMER_DEF(CONCAT_2(_name,_app_timer)); \
      nrf_libuarte_app_timer_ctrl_blk_t CONCAT_2(_name,_app_timer_ctrl_blk);))) \
      static nrf_libuarte_async_ctrl_blk_t CONCAT_2(_name, ctrl_blk);\
      NRFX_CONCAT_3(NRFX_RTC, _rtc1_idx, _ENABLED), \
      (static void CONCAT_2(_name, _rtc_handler)(nrfx_rtc_int_type_t int_type);),\
      (/* empty */)) \


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.