Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Can't get me' PPI channel between timer (working in counter mode) and the UART RXDRDY event working

I must be doing something obviously wrong but I just can't see it.

I'm trying to create a PPI linkage between the UART RXDRDY event and a timer working in counter mode so that I can count received characters.  However my counter never moves from zero.  I can see that the DMA has received and transferred the expected number of characters when the ENDRX event is handled if I call nrf_uarte_rx_amount_get() but my counter never increases.  My initialisation code is below, can anyone spot what I'm doing wrong?  I am reading the counter with nrfx_timer_capture_get(&gTimer, 0).

static nrfx_timer_t       gTimer = NRFX_TIMER_INSTANCE(2);
static nrf_ppi_channel_t  gPpiChannel;
static NRF_UARTE_Type    *gpReg = NRF_UARTE0;

static void counterEventHandler(nrf_timer_event_t eventType,
                                void *pContext)
{
    (void) eventType;
    (void) pContext;
}

// Set up UART and counter.
int32_t init(int32_t pinTx, int32_t pinRx,
             int32_t baudRateNrf)
{
    int32_t             errorCode = 0;
    nrfx_timer_config_t timerConfig = NRFX_TIMER_DEFAULT_CONFIG;

    timerConfig.mode = NRF_TIMER_MODE_COUNTER;
    timerConfig.bit_width = NRF_TIMER_BIT_WIDTH_32;

    if (nrfx_timer_init(&gTimer,
                        &timerConfig, counterEventHandler) == NRFX_SUCCESS) {
        if (nrfx_ppi_channel_alloc(&gPpiChannel)) == NRFX_SUCCESS) {
            if ((nrfx_ppi_channel_assign(gPpiChannel,
                                         nrf_uarte_event_address_get(gpReg,
                                                                     NRF_UARTE_EVENT_RXDRDY),
                                         nrfx_timer_task_address_get(&gTimer,
                                                                     NRF_TIMER_TASK_COUNT)) != NRFX_SUCCESS) ||
                (nrfx_ppi_channel_enable(gPpiChannel) != NRFX_SUCCESS)) {
                nrfx_ppi_channel_disable(gPpiChannel);
                nrfx_ppi_channel_free(gPpiChannel);
                errorCode = -1;
            }
        } else {
            errorCode = -1;
        }
    } else {
        errorCode = -1;
    }

    if (errorCode == 0) {

        // [ I create UART buffers, "pRxBufferWriteNext" and the like, etc. here]

        nrf_uarte_baudrate_set(gpReg, baudRateNrf);
        nrf_gpio_pin_set(pinTx);
        nrf_gpio_cfg_output(pinTx);
        nrf_uarte_txrx_pins_set(gpReg, pinTx, pinRx);

        nrf_uarte_configure(gpReg, NRF_UARTE_PARITY_EXCLUDED, NRF_UARTE_HWFC_DISABLED);

        nrf_uarte_enable(gpReg);

        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_ENDRX);
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_ENDTX);
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_ERROR);
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_RXSTARTED);
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_TXSTOPPED);

        nrf_uarte_shorts_enable(gpReg, NRF_UARTE_SHORT_ENDRX_STARTRX);

        nrf_uarte_rx_buffer_set(gpReg,
                                (uint8_t *) (pRxBufferWriteNext->pStart),
                                256);
        pRxBufferWriteNext = pRxBufferWriteNext->pNext;
        nrf_uarte_task_trigger(gpReg, NRF_UARTE_TASK_STARTRX);
        nrf_uarte_int_enable(gpReg, NRF_UARTE_INT_ENDRX_MASK     |
                                    NRF_UARTE_INT_ERROR_MASK     |
                                    NRF_UARTE_INT_RXSTARTED_MASK);
        NRFX_IRQ_PRIORITY_SET(getIrqNumber((void *) gpReg),
                              NRFX_UARTE_DEFAULT_CONFIG_IRQ_PRIORITY);
        NRFX_IRQ_ENABLE(getIrqNumber((void *) (gpReg)));

        nrfx_timer_enable(&gTimer);
        nrfx_timer_clear(&gTimer);
    }

    return errorCode;
}

void nrfx_uarte_0_irq_handler(void)
{
    if (nrf_uarte_event_check(gpReg, NRF_UARTE_EVENT_ENDRX)) {
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_ENDRX);
    } else if (nrf_uarte_event_check(gpReg, NRF_UARTE_EVENT_RXSTARTED)) {
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_RXSTARTED);
        nrf_uarte_rx_buffer_set(gpReg,
                                (uint8_t *) (pRxBufferWriteNext->pStart),
                                256);
        pRxBufferWriteNext = pRxBufferWriteNext->pNext;
    } else if (nrf_uarte_event_check(gpReg, NRF_UARTE_EVENT_ERROR)) {
        nrf_uarte_event_clear(gpReg, NRF_UARTE_EVENT_ERROR);
        nrf_uarte_errorsrc_get_and_clear(gpReg);
    }
}

  • Hi, 

    Please take a look of the code and there are multiple PPI tasks to capture the counter, searching "nrfx_timer_capture_task_address_get".

    You will see it captures counter when RX is stopped as well.

    ret = ppi_channel_configure(
                    &p_libuarte->ctrl_blk->ppi_channels[NRF_LIBUARTE_DRV_PPI_CH_EXT_STOP_STOPRX],
                    p_config->endrx_evt,
                    nrf_uarte_task_address_get(p_libuarte->uarte, NRF_UARTE_TASK_STOPRX),
                    nrfx_timer_capture_task_address_get(&p_libuarte->timer, 1));

    Please read through the code and if you can't understand the whole logic, the best way is to read it line by line and understand what it does.

  • To help you guys out, there is a big gap here.  I asked in this ticket for help with understanding how the UARTE HW is meant to be driven for continuous receive.  I didn't want more software, I wanted design guidance on how the hardware was intended to be used.  I was told to go look at nrf_libuarte_driv.c to find out how to do this: you have no design guidelines on how to do this, no "story" style documentation (as ESP32 has), no comprehensive example code (as STM32F4 has).

    This is a real problem: being directed to read the even more ridiculously complicated nrf_libuarte_driv.c code, with its multiple PPI channels for multiple use cases and paucity of comments, cannot be considered help or advice.  nrf_libuarte_driv.c is intended as functional code that meets your API: fine, that's not a problem but please do NOT consider it a substitute for design guidance.

    You REALLY need design guidance.  Just letting you know this as constructive customer feedback.  Your HW is a helluvalot simpler to drive directly (as evidenced by the small number of lines of code offered by @hmolesworth above) than any of the five layers of driver code above and, if it were well explained, you'd be so much better off.  The lack of such documentation means it has taken me many weeks longer to achieve running code on the NRF52840 versus the competition.

    Anyway, I do now have working code, so thank you.

  • Hi Rob,

    Sorry to hear that you have struggled long time to make it through.

    My previous comment was solely for the response that you don't understand how it works in nrf_libuarte_driv. I didn't know that what you really want is a simple continuous steam to receive uart data.

    If you look at the libuarte example, you can find a link pointing to libuarte description, where you can find how libuart is designed to achieve the goal.

    Would this help you? and do you consider this link as a design guideline?

  • This is better, yes, so if nrf_libuarte_driv.c included more comments it might be possible to make it through to the other side.

    The problem, which I gather I wasn't alone in, is that the obvious UART driver design is to time-out the receive and restart it.  It _looks_ as if this is possible since there is a four character UART buffer and lots of time to do it; it is only when you try it that you discover that it ALWAYS results in character loss, hence the approach that nrf_libuarte_driv.c takes.  Stating this up-front would be a very helpful step.

  • I agree, we shall probably address limitation more obviously to developers and also recommend other alternative solutions in various use case.

Related