nRF52832: problem with refreshing/clearing RTC for looping

Task description

I would like to use RTC2's COMPARE_0-event to trigger SAADC's conversion task via PPI, continuously. The RTC needs to operate by itself without CPU intervention or interrupts. I've managed to trigger the required task on the SAADC side, and got out some readings after the conversion, which would suggest that at least the PPI connection and SAADC are working. I tried to look for answers on this forum (elsewhere online too, but this forum seemed most likely to provide an answer) but in most cases the provided help was a dead link or some old library that I don't have an access to.

The goal is a low-power, autonomous RTC-to-SAADC operation.

I've been working with both hal/nrf- and nrfx-libraries regarding all these drivers.

my_rtc.c

#include <hal/nrf_rtc.h>
#include <nrfx_rtc.h>

static nrfx_rtc_t my_rtc = NRFX_RTC_INSTANCE(2);

static nrfx_rtc_config_t my_rtc_conf = {
    .prescaler          = RTC_FREQ_TO_PRESCALER(8),
    .interrupt_priority = NRFX_RTC_DEFAULT_CONFIG_IRQ_PRIORITY,
    .tick_latency       = 0,
    .reliable           = false,
};

static uint32_t p_mask;

static void my_rtc_handler(nrfx_rtc_int_type_t int_type)
{
    LOG_INF("my_rtc_handler");
}

uint32_t my_rtc_address_event_comp0(void)
{
    return nrfx_rtc_event_address_get(&my_rtc, NRF_RTC_EVENT_COMPARE_0);
}

uint32_t my_rtc_address_task_clear(void)
{
    return nrfx_rtc_task_address_get(&my_rtc, NRF_RTC_TASK_CLEAR);
}

void my_rtc_init()
{
    nrfx_err_t err;
    err = nrfx_rtc_init(&my_rtc, &my_rtc_conf, rtc_handler);
    nrfx_rtc_tick_disable(&my_rtc);
    nrfx_rtc_overflow_disable(&my_rtc);
    nrfx_rtc_int_disable(&my_rtc, &p_mask);
    err = nrfx_rtc_cc_set(&my_rtc, 0, 4, false);
    nrfx_rtc_enable(&my_rtc);
}

my_ppi.c

#include <nrfx_ppi.h>

static nrf_ppi_channel_t my_ppi_ch0;

void my_ppi_set()
{
    nrfx_ppi_channel_alloc(&my_ppi_ch0);
    nrfx_ppi_channel_assign(my_ppi_ch0, my_rtc_address_event_comp0(), my_saadc_address_task_sample());
    nrfx_ppi_channel_enable(my_ppi_ch0);
}

my_saadc.c

#include <hal/nrf_saadc.h>
#include <nrfx_saadc.h>

#define MM_SAADC_CH_CNT 2

static nrf_saadc_value_t samples[MY_SAADC_CH_CNT];
static nrfx_saadc_channel_t channels[MY_SAADC_CH_CNT] = {
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
};

static nrfx_saadc_adv_config_t p_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;

uint32_t my_saadc_address_task_sample()
{
    return nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
}

static uint8_t next_free_buf_index(void)
{
    static uint8_t buffer_index = -1;
    buffer_index = (buffer_index + 1) % MY_SAADC_CH_CNT;
    return buffer_index;
}

static void my_saadc_handler(nrfx_saadc_evt_t const * p_event)
{
    LOG_INF("SAADC handler reached");
    nrfx_err_t err;
    switch (p_event->type)
    {
        case NRFX_SAADC_EVT_DONE:
            LOG_INF("-------------> NRFX_SAADC_EVT_DONE");
            // Buffer with data is available here: p_event->data.done.p_buffer
            for (int i = 0; i < p_event->data.done.size; i++) {
                LOG_INF("CH%d: %d", i, p_event->data.done.p_buffer[i]);
            }
            break;
        case NRFX_SAADC_EVT_BUF_REQ:
            LOG_INF("NRFX_SAADC_EVT_BUF_REQ");
            err = nrfx_saadc_buffer_set(&samples[next_free_buf_index()], MM_SAADC_CH_CNT);
            break;
        default:
            LOG_INF("default: event type %x", p_event->type);
            break;
    }
}

void my_saadc_init(void)
{
    nrfx_err_t err;

    IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)), DT_IRQ(DT_NODELABEL(adc), priority),
                nrfx_isr, nrfx_saadc_irq_handler, 0);

    err = nrfx_saadc_init(NRFX_SAADC_DEFAULT_CONFIG_IRQ_PRIORITY);
    err = nrfx_saadc_channels_config(channels, MY_SAADC_CH_CNT);

    err = nrfx_saadc_advanced_mode_set((1<<0|1<<1), NRF_SAADC_RESOLUTION_12BIT,
                                       &p_config, my_saadc_handler);

    err = nrfx_saadc_buffer_set(&samples[next_free_buf_index()], MY_SAADC_CH_CNT);

    err = nrfx_saadc_mode_trigger();
}

main.c

void main(void)
{
    my_saadc_init();
    my_rtc_init();
    my_ppi_set();
}

The problem

Once establishing the connection between RTC2 and SAADC via PPI, the RTC sends the event as expected, but does this one-shot. I need the RTC to continue running and providing events. The ideal situation would be that once the SAADC has completed the conversion, the CPU would be allowed to return to the sleep state, and the rest of the peripherals kept operating autonomously.

I tried to call a function from the SAADC handler, that would clear some of the registers and re-start the RTC. This was my drowning man's straw, since I know this doesn't support the key idea of CPU-independent peripheral. But even with this solution I was unable to re-start the RTC.

/* my_rtc.c */

void my_rtc_clear_all(void)
{
    nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_0);
    nrf_rtc_task_trigger(NRF_RTC2, NRF_RTC_TASK_CLEAR);
    nrf_rtc_task_trigger(NRF_RTC2, NRF_RTC_TASK_START);
}

/* my_saadc.h */

...
...
    switch (p_event->type)
    {
        case NRFX_SAADC_EVT_DONE:
            LOG_INF("-------------> NRFX_SAADC_EVT_DONE");
            my_rtc_clear_all();                             // <--- Attempting to restart RTC
            for (int i = 0; i < p_event->data.done.size; i++) {
                LOG_INF("CH%d: %d", i, p_event->data.done.p_buffer[i]);
            }
            break;
        case NRFX_SAADC_EVT_BUF_REQ:
...
...

Further development

Once I get the RTC to trigger SAADC conversion autonomously, my goal is to yet further to bind SAADC ready-type event to GPIOTE toggle-task. In my previous tests I managed to create RTC-to-GPIOTE connection via PPI with a successful pin toggle, but even then the RTC triggers only once. This is why I think I've got something wrong with either the RTC setup or usage.

Disclaimer

This is my very first post on any professional platform, so I apoligize for any bad form that might take in place with this ticket. Thank you for understanding and for your assistance.

  • Hi,

    Unlike the TIMER, the RTC does not have internal shortcuts between COMPARE events and the CLEAR task. However, you can hook this up externally via PPI. In other words, use a PPI channel to connect the relevant EVENTS_COMPARE register to TASKS_CLEAR, and that should do the trick. (I see you have your my_rtc_address_event_comp0() and my_rtc_address_task_clear() in your my_rtc.c, so it looks like you have been thinking about this already).

  • Hello, thank you for the reply.

    Indeed, I have tried to trigger the TASKS_CLEAR by forking directly from the COMPARE_0 event, like this:

    my_ppi.c

    #include <nrfx_ppi.h>
    
    static nrf_ppi_channel_t my_ppi_ch0;
    
    void my_ppi_set()
    {
        nrfx_ppi_channel_alloc(&my_ppi_ch0);
        nrfx_ppi_channel_assign(my_ppi_ch0, my_rtc_address_event_comp0(), my_saadc_address_task_sample());
        nrfx_ppi_channel_fork_assign(my_ppi_ch0, my_rtc_address_task_clear());
        nrfx_ppi_channel_enable(my_ppi_ch0);
    }

    I also created a function to return the counter value, and I ran tests both with the PPI-fork and without:

    /* my_rtc.h */
    
    void my_rtc_read_counter(void)
    {
        LOG_INF("my_rtc_read_counter %d", nrfx_rtc_counter_get(&my_rtc));
    }
    
    /* my_saadc.c */
    ...
    ...
        switch (p_event->type)
        {
            case NRFX_SAADC_EVT_DONE:
                LOG_INF("-------------> NRFX_SAADC_EVT_DONE");
                for (int i = 0; i < p_event->data.done.size; i++) {
                    LOG_INF("CH%d: %d", i, p_event->data.done.p_buffer[i]);
                }
                my_rtc_read_counter();
                break;
            case NRFX_SAADC_EVT_BUF_REQ:
                LOG_INF("NRFX_SAADC_EVT_BUF_REQ");
    ...
    ...


    The test shows that without the forking the counter keeps counting, as expected, and with the fork the counter is cleared. So everything works exactly how it should be, except that clearing the counter is obviously not enough to assure repetitious RTC triggering from the COMPARE_0-event, or seemingly at all. So what am I missing here, what is the step that I haven't taken?

    Using nrfx- or nrf-based libraries is not a necessity either, I can drop them if that is required to solve the issue, and manage by changing the register values manually.

    Since RTC is said to be a low-power solution, I'd like to hold on to it as long as I manage to solve the issue. Using TIMER must be the absolute last option.

    Thank you.

  • Hi,

    OK, so you see that clearing works, but you no longer get interrupt on capture except for the first time? Then you are seeing a peculiarity of the RTC driver. Whenever there is a CC interrupt, the interrupt is disabled. So you need to enable it every time. That should have been in the API doc, but alas it is not. You can see an explanation in this old post, and even if the driver implementation has changed a bit, this remains the same. You comment out the disabling of the interrupts as suggested in that post, or add something like this in your RTC event handler:

    nrf_rtc_int_enable(my_rtc.p_reg, RTC_CHANNEL_INT_MASK(0));

  • Summary questions:

    1) How to use RTC repetitiously without interrupts, is it possible?

    2) After repetitious RTC interrupt handling I still can't trigger the COMPARE0-event but once. I've tried with both explicitly clearing the register (the commented function in the RTC handler) and without, makes no difference as far as I can tell.

    Thank you for the help and directions so far. So, the primary approach for me was NOT to use any interrupts, that wake the CPU up. Is this possible in any scenario? I refer to this image from the nRF52832 datasheet:

    However, I did try to follow your suggestions, and I edited my rtc-code like this:

    static void rtc_handler(nrfx_rtc_int_type_t int_type)
    {
        //nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_0);
        nrfx_rtc_counter_clear(&my_rtc);
        nrfx_rtc_int_enable(&my_rtc, RTC_CHANNEL_INT_MASK(0));
        LOG_INF("RTC handler reached");
    }
    
    void my_rtc_init()
    {
        IRQ_CONNECT(DT_IRQN(DT_NODELABEL(rtc2)), DT_IRQ(DT_NODELABEL(rtc2), priority),
                    nrfx_isr, nrfx_rtc_2_irq_handler, 0);
    
        nrfx_err_t err;
        err = nrfx_rtc_init(&my_rtc, &my_rtc_conf, rtc_handler);
        //nrfx_rtc_tick_disable(&my_rtc);
        //nrfx_rtc_overflow_disable(&my_rtc);
        //nrfx_rtc_int_disable(&my_rtc, &p_mask);
        err = nrfx_rtc_cc_set(&my_rtc, 0, 4, true);
        nrfx_rtc_enable(&my_rtc);
    }

    This got me to trigger the rtc_handler()-function repeatedly, which is - i guess - a step forward. But still the COMPARE0-event triggers only once. When the COMPARE0-event is bound to SAADC-task via PPI (as in from the OP) I get the ADC-conversions to print only once, and even though I reach the RTC handler repetitiously, I never manage to reach the ADC-conversion again. I have tried to clear the COMPARE0-register explicitly by calling the clear-function in the RTC handler (the function is currently commented out), but the behaviour didn't change as far as I can tell.

    I'm not sure if I have to clear any SAADC registers like I have to do with the RTC, but I also tried to connect the COMPARE0-event to GPIOTE toggle task. I saw one change in the pin state - the one shot - but after that nothing but regular prints from the RTC handler.

    I'm happy for your help, I'm making some kind of progress, but still I'm not 100% satisfied.

    Side note:

    I actually got the SAADC to trigger GPIOTE toggle via PPI by changing the SAADC event from NRF_SAADC_EVENT_DONE / NRF_SAADC_EVENT_RESULTDONE ---> NRF_SAADC_EVENT_END. Although I'm happy to see the signal path reaching the beginning all the way to the end, I'm not sure if this is the correct and reliable way to do this. I feel like I lack the knowledge and experience.

  • Hi,

    ISH said:
    1) How to use RTC repetitiously without interrupts, is it possible?

    It is just a matter of not enabling interrupts. Based on what you write things should work, and I put together a simple sample (based on the Zephyr nrfx sample) that demonstrated it. See attached prj.confg and main.c. This will use RTC, GPIOTE and PPI to toggle a LED without any interrupts.

    /*
     * Copyright (c) 2019 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/zephyr.h>
    #include <nrfx_gpiote.h>
    #include <helpers/nrfx_gppi.h>
    #include <hal/nrf_rtc.h>
    #include <nrfx_rtc.h>
    #include <nrfx_ppi.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(nrfx_rtc_capture_clear_ppi_sample, LOG_LEVEL_INF);
    
    #define OUTPUT_PIN	DT_GPIO_PIN(DT_ALIAS(led0), gpios)
    
    static nrfx_rtc_t rtc = NRFX_RTC_INSTANCE(2);
    
    static const nrfx_rtc_config_t rtc_conf = {
        .prescaler          = RTC_FREQ_TO_PRESCALER(32768),
        .interrupt_priority = NRFX_RTC_DEFAULT_CONFIG_IRQ_PRIORITY,
        .tick_latency       = 0,
        .reliable           = false,
    };
    
    
    static void dummy_rtc_handler(nrfx_rtc_int_type_t int_type)
    {
        LOG_INF("RTC interrupt is not needed. Should never see this log message.");
    }
    
    
    void rtc_init()
    {
    	nrfx_err_t err;
    
    }
    
    
    void main(void)
    {
    	LOG_INF("nrfx_rtc_capture_clear_ppi sample on %s", CONFIG_BOARD);
    
    	nrfx_err_t err;
    	uint8_t in_channel, out_channel;
    	uint8_t ppi_channel;
    
    	/* Initialize the RTC */
    	err = nrfx_rtc_init(&rtc, &rtc_conf, dummy_rtc_handler);
    	nrfx_rtc_tick_disable(&rtc);
    	nrfx_rtc_overflow_disable(&rtc);
    	err = nrfx_rtc_cc_set(&rtc, 0, 32768, false);
    	nrfx_rtc_enable(&rtc);
    
    	/* Initialize GPIOTE (the interrupt priority passed as the parameter
    	 * here is ignored, see nrfx_glue.h).
    	 */
    	err = nrfx_gpiote_init(0);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_gpiote_init error: 0x%08X", err);
    		return;
    	}
    
    	err = nrfx_gpiote_channel_alloc(&in_channel);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("Failed to allocate in_channel, error: 0x%08X", err);
    		return;
    	}
    
    	err = nrfx_gpiote_channel_alloc(&out_channel);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("Failed to allocate out_channel, error: 0x%08X", err);
    		return;
    	}
    
    	/* Initialize output pin. SET task will turn the LED on,
    	 * CLR will turn it off and OUT will toggle it.
    	 */
    	static const nrfx_gpiote_output_config_t output_config = {
    		.drive = NRF_GPIO_PIN_S0S1,
    		.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
    		.pull = NRF_GPIO_PIN_NOPULL,
    	};
    	const nrfx_gpiote_task_config_t task_config = {
    		.task_ch = out_channel,
    		.polarity = NRF_GPIOTE_POLARITY_TOGGLE,
    		.init_val = 1,
    	};
    	err = nrfx_gpiote_output_configure(OUTPUT_PIN,
    					   &output_config,
    					   &task_config);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_gpiote_output_configure error: 0x%08X", err);
    		return;
    	}
    
    	nrfx_gpiote_out_task_enable(OUTPUT_PIN);
    
    	LOG_INF("nrfx_gpiote initialized");
    
    	/* Allocate a PPI channel. */
    	err = nrfx_gppi_channel_alloc(&ppi_channel);
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("nrfx_gppi_channel_alloc error: 0x%08X", err);
    		return;
    	}
    
    	/* Configure endpoints of the channel so that the input pin event is
    	 * connected with the output pin OUT task. This means that each time
    	 * the button is pressed, the LED pin will be toggled.
    	 */
    	nrfx_gppi_channel_endpoints_setup(ppi_channel,
    		nrfx_rtc_event_address_get(&rtc, NRF_RTC_EVENT_COMPARE_0),
    		nrfx_gpiote_out_task_addr_get(OUTPUT_PIN));
    
    	/* Clear the RTC on RTC via PPI on the compare 0 event.Using fork mechanism here.
    	 * devices without fork mechanism (nRF53, nRF91)
    	 */
    	nrfx_gppi_fork_endpoint_setup(ppi_channel, nrfx_rtc_task_address_get(&rtc, NRF_RTC_TASK_CLEAR));
    
    	/* Enable the channel. */
    	nrfx_gppi_channels_enable(BIT(ppi_channel));
    
    	LOG_INF("PPI configured, leaving main()");
    }
    

    prj.conf:

    CONFIG_GPIO=n
    CONFIG_NRFX_GPIOTE=y
    CONFIG_NRFX_RTC2=y
    CONFIG_LOG=y
    CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=100

    Tested with nRF Connect SDK 2.1.2 and a nRF52 DK.

    PS: A slight modification is needed to run on nRF53/nRF91, as there is no PPI fork feature there. (With DPPI, a additional channel is needed instead).

Related