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

Understand UART, UARTE and getting it to work with PPI

Hi all,

Using UART is confusing for me because I see various implementations in the documentation. There is a Serial library, then there is the UART driver and HAL. The HAL is further divided to UART and UARTE. I read that the difference between UART and UARTE is that the UARTE uses EasyDMA.

I like to try a few things:

1. Use PPI to setup a timer event to trigger a UART TX task. Eg. Send out "Hello" every 1 second.

2. Use PPI to setup a timer event to trigger a UART RX task. Eg. Read from UART every 1 second.

For (1), I have setup the ppi channel as follows:

err_code = nrf_drv_ppi_channel_assign(m_ppi_channel1, nrf_drv_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE0), nrf_drv_uart_task_address_get(&uart_driver_instance, NRF_UART_TASK_STARTTX));

 

In this case, should I be using nrf_uart_task_address_get or nrf_uarte_task_address_get or nrf_drv_uart_task_address_get?

I also noticed that there is a NRF_UART_TASK_STOPTX. Does it mean that I also need to have an event to trigger a stop task? Otherwise, it will never stop transmitting?

How does the UART tx happen? Should I be placing the string in some register or ram address so that when the task is triggered, the string is automatically send from the tx pin? If I am using UARTE, I presume i should initalise the tx buffer with this function nrf_uarte_tx_buffer_set.?

For(2), the ppi channel is setup as follows:

err_code = nrf_drv_ppi_channel_assign(m_ppi_channel1, nrf_drv_timer_event_address_get(&m_timer1, NRF_TIMER_EVENT_COMPARE0), nrf_drv_uart_task_address_get(&uart_driver_instance, NRF_UART_TASK_STARTRX));

Similarly, how does the RX happen? What do I need to configure for the RX data to be stored to memory? How can I access it later when I wake up the CPU? Do I also need another event to stop the RX task?

This is the code that I have now.

#include <stdint.h>

#include "nrf_delay.h"
#include "app_error.h"

#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "nrf_drv_uart.h"


#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define UART0_USE_EASY_DMA        true
#define UART_EASY_DMA_SUPPORT     1
//#define UART_LEGACY_SUPPORT       1

#define PPI_EXAMPLE_TIMER1_INTERVAL             (1000)  // Timer interval in milliseconds

static const nrf_drv_uart_t uart_driver_instance = NRF_DRV_UART_INSTANCE(UART0_INSTANCE_INDEX);
static const nrf_drv_timer_t m_timer1 = NRF_DRV_TIMER_INSTANCE(1);
static nrf_ppi_channel_t m_ppi_channel1;

static volatile uint32_t m_counter;

/* Timer event handler. Not used since Timer1 is used only for PPI. */
static void empty_timer_handler(nrf_timer_event_t event_type, void * p_context)
{
}

/** @brief Function for initializing the UART peripheral.
*/
static void uart_init(void)
{
    uint32_t err_code = NRF_SUCCESS;
	
		nrf_drv_uart_config_t config = NRF_DRV_UART_DEFAULT_CONFIG;
		config.use_easy_dma = true;
		
		err_code = nrf_drv_uart_init(&uart_driver_instance, &config, NULL);
		APP_ERROR_CHECK(err_code);
	
}
	
/** @brief Function for initializing the PPI peripheral.
*/
static void ppi_init(void)
{
    uint32_t err_code = NRF_SUCCESS;

    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);

    /* Configure 1st available PPI channel to start UART RX event on TIMER1 COMPARE[0] match,
     * which is every even number of seconds.
     */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel1);
    APP_ERROR_CHECK(err_code);
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel1,
                                          nrf_drv_timer_event_address_get(&m_timer1,
                                                                          NRF_TIMER_EVENT_COMPARE0),
                                          nrf_drv_uart_task_address_get(&uart_driver_instance,
                                                                         NRF_UART_TASK_STARTTX));
    APP_ERROR_CHECK(err_code);

    // Enable both configured PPI channels
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel1);
    APP_ERROR_CHECK(err_code);
}


/** @brief Function for Timer 1 initialization.
 *  @details Initializes TIMER1 peripheral to generate an event every 1 seconds. The events 
 *           are used to start UART TX or RX via PPI: TIMER1->EVENT_COMPARE[0] triggers UART->TASK_STARTTX/TASK_STARTRX.
 */
static void timer1_init(void)
{
    // Check TIMER1 configuration for details.
    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.frequency = NRF_TIMER_FREQ_31250Hz;
    ret_code_t err_code = nrf_drv_timer_init(&m_timer1, &timer_cfg, empty_timer_handler);
    APP_ERROR_CHECK(err_code);

    nrf_drv_timer_extended_compare(&m_timer1,
                                   NRF_TIMER_CC_CHANNEL0,
                                   nrf_drv_timer_ms_to_ticks(&m_timer1,
                                                             PPI_EXAMPLE_TIMER1_INTERVAL),
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                   false);
}


/**
 * @brief Function for application main entry.
 */
int main(void)
{
    uint32_t old_val = 0;
    uint32_t err_code;

    err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();

		uart_init();
    ppi_init();
    timer1_init(); // Timer to generate events

    nrf_drv_timer_enable(&m_timer1);

    while (true)
    {
			
    }
}

Thanks in advance!

Parents
  • Hi,

    You are correct that there are multiple implementations of UART drivers and libraries in the SDK. Which implementation to choose depends on much of the low-level stuff you want abstracted away. At the bottom, the UART peripheral is controlled by writing to and reading from the registers, to configure, start tasks, and receive events. The Hardware Abstraction Layer (HAL) is a set of functions that simplyfy reading and writing the registers. The HAL does not provide any additional functionality. Next, the UART driver provides APIs on a higher level than the HAL. The driver implements an interrupt handler that can detect events for you and pass these to the application. As you realized, the nRF52 series have two UART peripherals, legacy peripherals (UART) and peripherals using EasyDMA (UARTE). The UART driver provide a common API for two types of peripherals, and can be configured to use either one of them.

    On an even higher abstraction layer, you find the UART library (app_uart) and the Serial Port library. These are libraries built on top of the UART driver, providing additional abstraction and functionality, as described in the documentation. The serial port library is the latest addition to the SDK, and provide better support for multiple UART instances (available on nRF52840 IC), in addition to some new nice features.

    What you describe does only make sense if using the UARTE peripheral, as the UART peripheral can only hold a single byte in the TXD register, meaning you will have to update the register in software for each byte you want to transmitt. In your question your are doing a lot of mixing of the HAL functions with the driver functions. You should stick to using one of the them, as calling HAL functions when the driver is active can cause the driver to enter an unexpected state. I would recommend that you study the transmission and reception sections in the UARTE peripheral documentation, and use the UARTE HAL to setup the transfers. You can later use the PPI driver, as shown in the PPI example, to connect the timer compare event to the STARTTX/STARTRX tasks in the UARTE peripheral.

    Both TX and RX should be stopped automatically when the number of bytes given in the TXD.MAXCNT register have been transmitted, or the RX buffer have been filled with RXD.MAXCNT bytes. You might have to stop the receiption if you do not receive the number of bytes available in your buffer, but you should try to write your application in a way that avoids this.

    Hope this helps clearing things up!

    Best regards,
    Jørgen

  • Note that the EasyDMA feature of UARTE peripheral on nRF52832 have a maximum size of 255 bytes.

    You should debug your application using this method, to determine which function is returning an error, and what the error code is. This should help you get the application working with the second solution.

Reply Children
No Data
Related