nrfx: GRTC driver, syscounter with compare events and GPPI/DPPI

Hello,

I am trying to get to grips with the nrfx driver: GRTC.

I want to create the following sequence with the lowest possible power consumption. Activate output 1 for 100 ms, sleep for 30 seconds, then perform an action on output 2 for 100 ms.

I have already created a version with a TIMER (TIMER20) that works with GPPI/DPPI. However, to save energy, I would like to switch to system off for at least 30 seconds, and if possible during the 100ms of output activation by GPPI.

Before switching to system off, I want to do the same thing with GRTC and syscounter as I did with the timer. For GRTC management, I'm stuck on the functions for STOP, CLEAR syscounter, and START.

Here is my current code with SDK v3.2.1 on the nrf54l15-DK board:

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/__assert.h>
#include <string.h>
#include <stdbool.h>
#include "hal/nrf_gpio.h"
#include "hal/nrf_grtc.h"
#include <nrfx_gpiote.h>
#if defined(CONFIG_GPIO)
#include <gpiote_nrfx.h>
#endif
#include "nrfx_grtc.h"
#include <helpers/nrfx_gppi.h>

#include "main.h"
#include "motor.h"


#define OUTPUT_PIN0  0  // P0.00
#define OUTPUT_PIN1  1  // P0.01

#define OPEN_PIN     NRF_GPIO_PIN_MAP(0, OUTPUT_PIN0)   // Your OUTPUT_PIN0
#define CLOSE_PIN    NRF_GPIO_PIN_MAP(0, OUTPUT_PIN1)   // Your OUTPUT_PIN1


#define GPIOTE_NODE	DT_NODELABEL(gpiote30)

// define time in ticks (assuming 1MHz timer frequency for easy conversion to microseconds)
#define TICKS_0MS_1MHZ 1 // 0ms
#define TICKS_100MS_1MHZ 100000 // 100ms at 1MHz
#define TICKS_30S_1MHZ 30000000 // 30s at 1MHz

// 
#if !defined(CONFIG_GPIO)
static nrfx_gpiote_t gpiote_node = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(GPIOTE_NODE));
static nrfx_gpiote_t *gpiote_inst = &gpiote_node;
#else
static nrfx_gpiote_t *gpiote_inst = &GPIOTE_NRFX_INST_BY_NODE(GPIOTE_NODE);
#endif


void motor_set_move(void) {
    
	// Trigger SET tasks timer to start the sequence (OPEN at 100ms, CLOSE at 30s, stop at 30s + 100ms)
    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_STOP); 
    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_CLEAR); 
    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_START); 
    // DPPI/GPPI will take care of the rest (clear after 100ms, set after 30s, clear after 30s + 100ms)

    log_debug(1, 20);
}


nrfx_gpiote_t *motor_get_gpiote_inst_ptr(void)
{
    return gpiote_inst;
}

/**
 * @brief Thread to manage the motor
 * 
 */
void main_motor(void)
{
	nrfx_err_t rv;
	uint8_t out_channel0, out_channel1;

    
    log_debug(1, 0);

    /*******************
    *   GPIO
    *******************/

    // Configure as outputs manually if needed (before GPIOTE config)
    nrf_gpio_cfg_output(OPEN_PIN);
    nrf_gpio_cfg_output(CLOSE_PIN);

    /*******************
    *   GPIOTE
    *******************/

    /* Initialisation GPIOTE (à faire une seule fois dans l’appli) */
    if (!nrfx_gpiote_init_check(gpiote_inst)) {
        rv = nrfx_gpiote_init(gpiote_inst,NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
        if (rv != 0) {
            log_debug(1, 1);
            return;
        }
    }

	// Allocate GPIOTE channels 0 and 1 for the two output pins 
    rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel0);
    if (rv != 0) {
        log_debug(1, 2);
        return;
    }

    // Allocate another GPIOTE channel for the second output
    rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel1);
    if (rv != 0) {
        log_debug(1, 3);
        return;
    }
    
    // Configure the output pins with GPIOTE, pull-up disabled, standard drive
	nrfx_gpiote_output_config_t output_config = {
	    .drive = NRF_GPIO_PIN_S0S1,
        .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
        .pull = NRF_GPIO_PIN_NOPULL
	};

    // Task configuration for the first pin (OPEN_PIN): task toogle on compare event, initial value low
    nrfx_gpiote_task_config_t task_config0 =
    {
        .task_ch = out_channel0,
        .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
        .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
    };

    // Task configuration for the second pin (CLOSE_PIN): task toogle on compare event, initial value low
    nrfx_gpiote_task_config_t task_config1 =
    {
        .task_ch = out_channel1,
        .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
        .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
    };

    // configure OPEN_PIN pin with task config in GPIOTE
    rv = nrfx_gpiote_output_configure(gpiote_inst, OPEN_PIN, &output_config, &task_config0);
    if (rv != 0) {
        log_debug(1, 4);
        return;
    }
 
    // Configure CLOSE_PIN pin with task config in GPIOTE
    rv = nrfx_gpiote_output_configure(gpiote_inst, CLOSE_PIN, &output_config, &task_config1);
    if (rv != 0) {
        log_debug(1, 5);
        return;
    }

    // Enable the tasks for both pins
    nrfx_gpiote_out_task_enable(gpiote_inst, OPEN_PIN);
    nrfx_gpiote_out_task_enable(gpiote_inst, CLOSE_PIN);

    /*******************
    *   TIMER
    *******************/

    // Initialize Timer
    /*nrfx_timer_config_t timer_cfg = {
        .frequency = MOTOR_TIMER20_FREQ_1MHz, // 1MHz for low consumption 
        .mode = NRF_TIMER_MODE_TIMER,
        .bit_width = NRF_TIMER_BIT_WIDTH_32,
        .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
        .p_context = NULL
    };

    // Check if timer is already initialized, if not initialize it
    if (!nrfx_timer_init_check(&timer)) {
        rv = nrfx_timer_init(&timer, &timer_cfg, NULL);  // No IRQ handler
        if (rv != 0) {
            log_debug(1, 6);
            log_debug(6, rv);
            return 0;
        }
    }

    // Set up 100ms compare (1MHz * 0.10s = 100000 ticks), one-shot with stop and clear
    uint32_t ticks_100ms = TICKS_0MS_1MHZ + TICKS_100MS_1MHZ;
    uint32_t ticks_30s = ticks_100ms + TICKS_30S_1MHZ; // 30s after the first event;
    uint32_t ticks_30s_100ms = ticks_30s + ticks_100ms; // 30s + 100ms after the first event;

    // Configure the timer compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
    nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL0, TICKS_0MS_1MHZ,
                                0 ,
                                false);  // No interrupt
    // Configure the timer compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
    nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, ticks_100ms,
                                0 ,
                                false);  // No interrupt
    // Configure the timer compare events with no interrupt, but with shortcuts at 30s to set the CLOSE_PIN
    nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL2, ticks_30s,

                                0,
                                false);  // No interrupt
    // Configure the timer compare events with no interrupt, but with shortcuts at 30s + 100ms to clear the CLOSE_PIN                                
    nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL3, ticks_30s_100ms,
                                NRF_TIMER_SHORT_COMPARE3_STOP_MASK | NRF_TIMER_SHORT_COMPARE3_CLEAR_MASK,
                                false);  // No interrupt*/
    /*******************
    *   GRTC
    *******************/

    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_STOP); 
    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_CLEAR); 

    // initialize GRTC (if not already initialized, can be done in another thread if needed)
    if (!nrfx_grtc_init_check()) {
        rv = nrfx_grtc_init(NRFX_GRTC_DEFAULT_CONFIG_IRQ_PRIORITY);
        if (rv != 0) {
            log_debug(1, 6);
            return;
        }
    }

    // Set up 100ms compare (1MHz * 0.10s = 100000 ticks), one-shot with stop and clear
    uint32_t ticks_100ms = TICKS_0MS_1MHZ + TICKS_100MS_1MHZ;
    uint32_t ticks_30s = ticks_100ms + TICKS_30S_1MHZ; // 30s after the first event;
    uint32_t ticks_30s_100ms = ticks_30s + ticks_100ms; // 30s + 100ms after the first event;

    // allocate a GRTC channel for the user data, can be any channel from 0 to 7
    uint8_t GRTC_channel_0, GRTC_channel_1, GRTC_channel_2, GRTC_channel_3;
    rv = nrfx_grtc_channel_alloc(&GRTC_channel_0);
    if (rv != 0) {
        log_debug(1, 60);
        return;
    }

    // Configure the GRTC compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
    rv = nrfx_grtc_syscounter_cc_absolute_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_0 }, TICKS_0MS_1MHZ, false);
    if (rv != 0) {
        log_debug(1, 70);
        return 0;
    }

    rv = nrfx_grtc_channel_alloc(&GRTC_channel_1);
    if (rv != 0) {
        log_debug(1, 60);
        return;
    }

    // Configure the GRTC compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
    rv = nrfx_grtc_syscounter_cc_absolute_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_1 }, ticks_100ms, false);
    if (rv != 0) {
        log_debug(1, 72);
        return 0;
    }

    rv = nrfx_grtc_channel_alloc(&GRTC_channel_2);
    if (rv != 0) {
        log_debug(1, 60);
        return;
    }

    // Configure the GRTC compare events with no interrupt, but with shortcuts at 30s to set the CLOSE_PIN
    rv = nrfx_grtc_syscounter_cc_absolute_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_2 }, ticks_30s, false);
    if (rv != 0) {
        log_debug(1, 73);
        return 0;
    }

    // Configure the GRTC compare events with no interrupt, but with shortcuts at 30s + 100ms to clear the CLOSE_PIN
    rv = nrfx_grtc_channel_alloc(&GRTC_channel_3);
    if (rv != 0) {
        log_debug(1, 60);
        return;
    }

    rv = nrfx_grtc_syscounter_cc_absolute_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_3 }, ticks_30s_100ms, false);
    if (rv != 0) {
        log_debug(1, 74);
        return 0;
    }


    /*******************
    *   DPPI/GPPI
    *******************/

    // DPPI/GPPI handles for the connections
    nrfx_gppi_handle_t gppi_handle_open, gppi_handle_clear_open, gppi_handle_pause30s, gppi_handle_close;

    // Get the addresses of the tasks and events to connect with DPPI/GPPI
    uint32_t set_task_addr0 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN0);
    uint32_t clr_task_addr0 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN0);
    uint32_t set_task_addr1 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN1);
    uint32_t clr_task_addr1 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN1);

    // Get the addresses of the timer compare events
    /*uint32_t eep = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL0);
    uint32_t eep2 = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL1);
    uint32_t eep3 = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL2);
    uint32_t eep4 = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL3);*/

    uint32_t eep = nrfx_grtc_event_compare_address_get(GRTC_channel_0);
    uint32_t eep2 = nrfx_grtc_event_compare_address_get(GRTC_channel_1);
    uint32_t eep3 = nrfx_grtc_event_compare_address_get(GRTC_channel_2);
    uint32_t eep4 = nrfx_grtc_event_compare_address_get(GRTC_channel_3);

    // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
    rv = nrfx_gppi_conn_alloc(eep, set_task_addr0, &gppi_handle_open);
    if (rv != 0) {
        log_debug(1, 7);
        return 0;
    }

    // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
    rv = nrfx_gppi_conn_alloc(eep2, clr_task_addr0, &gppi_handle_clear_open);
    if (rv != 0) {
        log_debug(1, 8);
        return 0;
    }

    // Allocate a GPPI channel and connect the timer event to the second task : start CLOSE at 30s (one-to-one)
    rv = nrfx_gppi_conn_alloc(eep3, set_task_addr1, &gppi_handle_pause30s);
    if (rv != 0) {
        log_debug(1, 9);
        return 0;
    }

    // Allocate a GPPI channel and connect the timer event to the third task : stop CLOSE at 30s + 100ms (one-to-one)
    rv = nrfx_gppi_conn_alloc(eep4, clr_task_addr1, &gppi_handle_close);
    if (rv != 0) {
        log_debug(1, 10);
        return 0;
    }

    // Enable the GPPI channels
    nrfx_gppi_conn_enable(gppi_handle_open);
    nrfx_gppi_conn_enable(gppi_handle_clear_open);
    nrfx_gppi_conn_enable(gppi_handle_pause30s);
    nrfx_gppi_conn_enable(gppi_handle_close);

    log_debug(1, 11);
    
    // start the timer to trigger the sequence of events (0.001ms, 100ms, 30s, 30s+100ms)
    //nrfx_grtc_syscounter_start(false, &RTC_main_channel); // Start the GRTC system counter, no busy wait, get the allocated channel for the user data
    //nrf_grtc_task_trigger(NRF_GRTC, NRF_GRTC_TASK_START);
    //nrf_barrier_w();
    //nrfy_grtc_task_trigger(NRF_GRTC, NRF_GRTC_TASK_START);
    nrfx_grtc_action_perform(NRFX_GRTC_ACTION_START); 

	//motor controle handler loop
	while (1) {
		k_msleep(100);
	}

}

K_THREAD_DEFINE(motor_id, STACKSIZE, main_motor, NULL, NULL, NULL, PRIORITY, 0, 0);

In the current version, if I comment out the lines: nrfx_grtc_action_perform(NRFX_GRTC_ACTION_XXXX);
30 seconds after programming the card, I have 100 ms on output 2.
But I don't have activation of type 1 for 100 ms.

For me, it's Zephir that starts the syscounter before I configure the first compares.
So the first comparison values have already passed.

That's why I want to do a stop, clear before start syscounter at the end of the configuration.

But in the nrfx_grtc.c file of the SDK v3.2.1, I have this function, and I don't see how we can perform the NRFX_GRTC_ACTION_STOP action.

because if syscounter is counting, no action is taken :

typedef enum
{
    NRFX_GRTC_ACTION_START = NRF_GRTC_TASK_START, /**< Start the GRTC. */
    NRFX_GRTC_ACTION_STOP  = NRF_GRTC_TASK_STOP,  /**< Stop the GRTC. */
    NRFX_GRTC_ACTION_CLEAR = NRF_GRTC_TASK_CLEAR, /**< Clear the GRTC. */
} nrfx_grtc_action_t;

int nrfx_grtc_action_perform(nrfx_grtc_action_t action)
{
    NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);
    int err_code = 0;

    if (is_syscounter_running())
    {
        err_code = -ECANCELED;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));

        return err_code;
    }

    nrf_grtc_task_t task = (nrf_grtc_task_t)action;
    nrfy_grtc_task_trigger(NRF_GRTC, task);

    NRFX_LOG_INFO("GRTC %s action.", GRTC_ACTION_TO_STR(action));
    return err_code;
}

What is the solution for stopping and clearing the syscounter?

doc used : https://docs.nordicsemi.com/bundle/nrfx-apis-3.0.1/page/group_nrfx_grtc.html

Parents
  • Hello,

    I can try to take a look, but I would like to mention that I don't expect much power benefit of using nrfx for this. A simple k_msleep() or use k_timer_start() to execute something every 30seconds will do the job here. Using that api you can expect a sleep current of ~1-3uA when CPU is not executing code while waiting to wakeup.

    With the implementation you have used until now you have been using TIMER (121-450uA @ 3V):
    https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/chapters/current_consumption/doc/current_consumption.html#ariaid-title12 

    By using the zephyr kernel api to for sleep (1.5-3.5uA @ 3V depending on the amount of RAM you retain):
    https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/chapters/current_consumption/doc/current_consumption.html#ariaid-title4 

    Edit: added links and fixed some text.

    Kenneth

  • Hello again,

    The syscounter will only reset on power-on reset, brown-out reset and pin reset. For other reset sources the syscounter is retained. There is no way to clear the syscounter by other means.

    Kenneth

  • Hello,

    Thank you for your reply.
    That's interesting information.

    You're right about that: for the first activation of output 1 for 100 ms, using GRTC won't save us much in terms of power consumption.
    So we can leave it on timer20 as in my first test.

    It is for the 30-second sleep that it is important to use GRTC to have the event compare in system OFF mode and save on power consumption.
    And this part can work without STOP or RESET.

    We do not want to use the kernel timers because in our use case we must be real-time.
    We cannot afford to have jitter on the 100ms time, output 1 or 2.
    The precision over 30 seconds is less important.

    Output 1 at high level for 100ms ⇒ on channel compare0 timer20@1MHz (121uA @ 3V) this power consumption level will be for 100ms
    30-second pause ⇒ on channel compareX GRTC@1MHz, we will have low consumption when I put the system in OFF mode.
    Output 2 at high level for 100ms ⇒ on channel compareX GRTC@1MHz, we will have low consumption when I put the system in OFF mode.

    Here is my code with a mixed functional solution (timer + GRTC) :

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/__assert.h>
    #include <string.h>
    #include <stdbool.h>
    #include "hal/nrf_timer.h"
    #include "hal/nrf_gpio.h"
    #include "hal/nrf_grtc.h"
    #include <nrfx_gpiote.h>
    #if defined(CONFIG_GPIO)
    #include <gpiote_nrfx.h>
    #endif
    #include "nrfx_timer.h"
    #include "nrfx_grtc.h"
    #include <helpers/nrfx_gppi.h>
    
    #include "main.h"
    #include "motor.h"
    
    
    #define OUTPUT_PIN0  0  // P0.00
    #define OUTPUT_PIN1  1  // P0.01
    
    #define OPEN_PIN     NRF_GPIO_PIN_MAP(0, OUTPUT_PIN0)   // Your OUTPUT_PIN0
    #define CLOSE_PIN    NRF_GPIO_PIN_MAP(0, OUTPUT_PIN1)   // Your OUTPUT_PIN1
    
    #define TIMER_INST   20
    
    #define GPIOTE_NODE	DT_NODELABEL(gpiote30)
    
    // define time in ticks (assuming 1MHz timer frequency for easy conversion to microseconds)
    #define TICKS_0MS_1MHZ 1 // 0ms
    #define TICKS_100MS_1MHZ 100000 // 100ms at 1MHz
    #define TICKS_30S_1MHZ 30000000 // 30s at 1MHz
    #define MOTOR_TIMER20_FREQ_1MHz 1000000UL
    
    #define TICK_100MS (TICKS_0MS_1MHZ + TICKS_100MS_1MHZ) // 100ms after the first event
    #define TICK_30S (TICK_100MS + TICKS_30S_1MHZ) // 30s after the first event
    #define TICK_30S_100MS (TICK_30S + TICKS_100MS_1MHZ) // 30s + 100ms after the first event
    
    // 
    #if !defined(CONFIG_GPIO)
    static nrfx_gpiote_t gpiote_node = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(GPIOTE_NODE));
    static nrfx_gpiote_t *gpiote_inst = &gpiote_node;
    #else
    static nrfx_gpiote_t *gpiote_inst = &GPIOTE_NRFX_INST_BY_NODE(GPIOTE_NODE);
    #endif
    static nrfx_timer_t timer = NRFX_TIMER_INSTANCE(NRF_TIMER_INST_GET(TIMER_INST));
    
    static uint8_t GRTC_channel_0 = 0; // Channel for CLOSE_PIN
    static uint8_t GRTC_channel_1 = 0; // Channel for CLOSE_PIN
    
    static void grtc_compare_set_GPPI(void)
    {
        
        nrfx_err_t rv;
    
        // free the channel in case it was already allocated (e.g. after a previous run of the test)
        if (GRTC_channel_0 > 0) 
        {
            rv = nrfx_grtc_channel_free(GRTC_channel_0);
            if (rv != 0) {
                log_debug(1, 60);
                return;
            }
        }
    
        if (GRTC_channel_1 > 0) 
        {
            rv = nrfx_grtc_channel_free(GRTC_channel_1);
            if (rv != 0) {
                log_debug(1, 60);
                return;
            }
        }
    
        rv = nrfx_grtc_channel_alloc(&GRTC_channel_0);
        if (rv != 0) {
            log_debug(1, 60);
            return;
        }
    
        // Configure the GRTC compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
        rv = nrfx_grtc_syscounter_cc_relative_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_0 }, TICK_30S, true,
    						     NRFX_GRTC_CC_RELATIVE_SYSCOUNTER);
        if (rv != 0) {
            log_debug(1, 70);
            return 0;
        }
    
        rv = nrfx_grtc_channel_alloc(&GRTC_channel_1);
        if (rv != 0) {
            log_debug(1, 60);
            return;
        }
    
        // Configure the GRTC compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
        rv = nrfx_grtc_syscounter_cc_relative_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_1 }, TICK_30S_100MS, true,
    						     NRFX_GRTC_CC_RELATIVE_SYSCOUNTER);
        if (rv != 0) {
            log_debug(1, 72);
            return 0;
        }
    }
    
    void motor_set_move(void) {
        
    	// Trigger SET tasks timer to start the sequence (OPEN at 100ms, CLOSE at 30s, stop at 30s + 100ms)
        grtc_compare_set_GPPI();
        nrf_timer_task_trigger(timer.p_reg, NRF_TIMER_TASK_START);
        // DPPI/GPPI will take care of the rest (clear after 100ms, set after 30s, clear after 30s + 100ms)
    
        log_debug(1, 20);
    }
    
    
    nrfx_gpiote_t *motor_get_gpiote_inst_ptr(void)
    {
        return gpiote_inst;
    }
    
    /**
     * @brief Thread to manage the motor
     * 
     */
    void main_motor(void)
    {
    	nrfx_err_t rv;
    	uint8_t out_channel0, out_channel1;
        
        log_debug(1, 0);
    
        /*******************
        *   GPIO
        *******************/
    
        // Configure as outputs manually if needed (before GPIOTE config)
        nrf_gpio_cfg_output(OPEN_PIN);
        nrf_gpio_cfg_output(CLOSE_PIN);
    
        /*******************
        *   GPIOTE
        *******************/
    
        /* Initialisation GPIOTE (à faire une seule fois dans l’appli) */
        if (!nrfx_gpiote_init_check(gpiote_inst)) {
            rv = nrfx_gpiote_init(gpiote_inst,NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
            if (rv != 0) {
                log_debug(1, 1);
                return;
            }
        }
    
    	// Allocate GPIOTE channels 0 and 1 for the two output pins 
        rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel0);
        if (rv != 0) {
            log_debug(1, 2);
            return;
        }
    
        // Allocate another GPIOTE channel for the second output
        rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel1);
        if (rv != 0) {
            log_debug(1, 3);
            return;
        }
        
        // Configure the output pins with GPIOTE, pull-up disabled, standard drive
    	nrfx_gpiote_output_config_t output_config = {
    	    .drive = NRF_GPIO_PIN_S0S1,
            .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
            .pull = NRF_GPIO_PIN_NOPULL
    	};
    
        // Task configuration for the first pin (OPEN_PIN): task toogle on compare event, initial value low
        nrfx_gpiote_task_config_t task_config0 =
        {
            .task_ch = out_channel0,
            .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
        };
    
        // Task configuration for the second pin (CLOSE_PIN): task toogle on compare event, initial value low
        nrfx_gpiote_task_config_t task_config1 =
        {
            .task_ch = out_channel1,
            .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
        };
    
        // configure OPEN_PIN pin with task config in GPIOTE
        rv = nrfx_gpiote_output_configure(gpiote_inst, OPEN_PIN, &output_config, &task_config0);
        if (rv != 0) {
            log_debug(1, 4);
            return;
        }
     
        // Configure CLOSE_PIN pin with task config in GPIOTE
        rv = nrfx_gpiote_output_configure(gpiote_inst, CLOSE_PIN, &output_config, &task_config1);
        if (rv != 0) {
            log_debug(1, 5);
            return;
        }
    
        // Enable the tasks for both pins
        nrfx_gpiote_out_task_enable(gpiote_inst, OPEN_PIN);
        nrfx_gpiote_out_task_enable(gpiote_inst, CLOSE_PIN);
    
        /*******************
        *   TIMER
        *******************/
    
        // Initialize Timer
        nrfx_timer_config_t timer_cfg = {
            .frequency = MOTOR_TIMER20_FREQ_1MHz, // 1MHz for low consumption 
            .mode = NRF_TIMER_MODE_TIMER,
            .bit_width = NRF_TIMER_BIT_WIDTH_32,
            .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
            .p_context = NULL
        };
    
        // Check if timer is already initialized, if not initialize it
        if (!nrfx_timer_init_check(&timer)) {
            rv = nrfx_timer_init(&timer, &timer_cfg, NULL);  // No IRQ handler
            if (rv != 0) {
                log_debug(1, 6);
                log_debug(6, rv);
                return 0;
            }
        }
    
        // Configure the timer compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL0, TICKS_0MS_1MHZ,
                                    0 ,
                                    false);  // No interrupt
        // Configure the timer compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, TICK_100MS,
                                    NRF_TIMER_SHORT_COMPARE1_STOP_MASK | NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK,
                                    false);  // No interrupt
    
        /*******************
        *   GRTC
        *******************/
       
        // initialize GRTC (if not already initialized, can be done in another thread if needed)
        if (!nrfx_grtc_init_check()) {
            rv = nrfx_grtc_init(NRFX_GRTC_DEFAULT_CONFIG_IRQ_PRIORITY);
            if (rv != 0) {
                log_debug(1, 6);
                return;
            }
        }
    
        grtc_compare_set_GPPI();
    
        /*******************
        *   DPPI/GPPI
        *******************/
    
        // DPPI/GPPI handles for the connections
        nrfx_gppi_handle_t gppi_handle_open, gppi_handle_clear_open, gppi_handle_pause30s, gppi_handle_close;
    
        // Get the addresses of the tasks and events to connect with DPPI/GPPI
        uint32_t set_task_addr0 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN0);
        uint32_t clr_task_addr0 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN0);
        uint32_t set_task_addr1 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN1);
        uint32_t clr_task_addr1 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN1);
    
        // Get the addresses of the timer compare events
        uint32_t eep = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL0);
        uint32_t eep2 = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL1);
        uint32_t eep3 = nrfx_grtc_event_compare_address_get(GRTC_channel_0);
        uint32_t eep4 = nrfx_grtc_event_compare_address_get(GRTC_channel_1);
    
    
        // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep, set_task_addr0, &gppi_handle_open);
        if (rv != 0) {
            log_debug(1, 7);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep2, clr_task_addr0, &gppi_handle_clear_open);
        if (rv != 0) {
            log_debug(1, 8);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the second task : start CLOSE at 30s (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep3, set_task_addr1, &gppi_handle_pause30s);
        if (rv != 0) {
            log_debug(1, 9);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the third task : stop CLOSE at 30s + 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep4, clr_task_addr1, &gppi_handle_close);
        if (rv != 0) {
            log_debug(1, 10);
            return 0;
        }
    
        // Enable the GPPI channels
        nrfx_gppi_conn_enable(gppi_handle_open);
        nrfx_gppi_conn_enable(gppi_handle_clear_open);
        nrfx_gppi_conn_enable(gppi_handle_pause30s);
        nrfx_gppi_conn_enable(gppi_handle_close);
    
        log_debug(1, 11);
        
        // start the timer to trigger the sequence of events (0.001ms, 100ms, 30s, 30s+100ms)
        nrf_timer_task_trigger(timer.p_reg, NRF_TIMER_TASK_START);
    
    	//motor controle handler loop
    	while (1) {
    		k_msleep(100);
    	}
    
    }
    
    K_THREAD_DEFINE(motor_id, STACKSIZE, main_motor, NULL, NULL, NULL, PRIORITY, 0, 0); // gestion du moteur a implementer

    I still need to implement the system off part, but I don't see any issues with that.

    thank you !

    Gaby

Reply
  • Hello,

    Thank you for your reply.
    That's interesting information.

    You're right about that: for the first activation of output 1 for 100 ms, using GRTC won't save us much in terms of power consumption.
    So we can leave it on timer20 as in my first test.

    It is for the 30-second sleep that it is important to use GRTC to have the event compare in system OFF mode and save on power consumption.
    And this part can work without STOP or RESET.

    We do not want to use the kernel timers because in our use case we must be real-time.
    We cannot afford to have jitter on the 100ms time, output 1 or 2.
    The precision over 30 seconds is less important.

    Output 1 at high level for 100ms ⇒ on channel compare0 timer20@1MHz (121uA @ 3V) this power consumption level will be for 100ms
    30-second pause ⇒ on channel compareX GRTC@1MHz, we will have low consumption when I put the system in OFF mode.
    Output 2 at high level for 100ms ⇒ on channel compareX GRTC@1MHz, we will have low consumption when I put the system in OFF mode.

    Here is my code with a mixed functional solution (timer + GRTC) :

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/__assert.h>
    #include <string.h>
    #include <stdbool.h>
    #include "hal/nrf_timer.h"
    #include "hal/nrf_gpio.h"
    #include "hal/nrf_grtc.h"
    #include <nrfx_gpiote.h>
    #if defined(CONFIG_GPIO)
    #include <gpiote_nrfx.h>
    #endif
    #include "nrfx_timer.h"
    #include "nrfx_grtc.h"
    #include <helpers/nrfx_gppi.h>
    
    #include "main.h"
    #include "motor.h"
    
    
    #define OUTPUT_PIN0  0  // P0.00
    #define OUTPUT_PIN1  1  // P0.01
    
    #define OPEN_PIN     NRF_GPIO_PIN_MAP(0, OUTPUT_PIN0)   // Your OUTPUT_PIN0
    #define CLOSE_PIN    NRF_GPIO_PIN_MAP(0, OUTPUT_PIN1)   // Your OUTPUT_PIN1
    
    #define TIMER_INST   20
    
    #define GPIOTE_NODE	DT_NODELABEL(gpiote30)
    
    // define time in ticks (assuming 1MHz timer frequency for easy conversion to microseconds)
    #define TICKS_0MS_1MHZ 1 // 0ms
    #define TICKS_100MS_1MHZ 100000 // 100ms at 1MHz
    #define TICKS_30S_1MHZ 30000000 // 30s at 1MHz
    #define MOTOR_TIMER20_FREQ_1MHz 1000000UL
    
    #define TICK_100MS (TICKS_0MS_1MHZ + TICKS_100MS_1MHZ) // 100ms after the first event
    #define TICK_30S (TICK_100MS + TICKS_30S_1MHZ) // 30s after the first event
    #define TICK_30S_100MS (TICK_30S + TICKS_100MS_1MHZ) // 30s + 100ms after the first event
    
    // 
    #if !defined(CONFIG_GPIO)
    static nrfx_gpiote_t gpiote_node = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(GPIOTE_NODE));
    static nrfx_gpiote_t *gpiote_inst = &gpiote_node;
    #else
    static nrfx_gpiote_t *gpiote_inst = &GPIOTE_NRFX_INST_BY_NODE(GPIOTE_NODE);
    #endif
    static nrfx_timer_t timer = NRFX_TIMER_INSTANCE(NRF_TIMER_INST_GET(TIMER_INST));
    
    static uint8_t GRTC_channel_0 = 0; // Channel for CLOSE_PIN
    static uint8_t GRTC_channel_1 = 0; // Channel for CLOSE_PIN
    
    static void grtc_compare_set_GPPI(void)
    {
        
        nrfx_err_t rv;
    
        // free the channel in case it was already allocated (e.g. after a previous run of the test)
        if (GRTC_channel_0 > 0) 
        {
            rv = nrfx_grtc_channel_free(GRTC_channel_0);
            if (rv != 0) {
                log_debug(1, 60);
                return;
            }
        }
    
        if (GRTC_channel_1 > 0) 
        {
            rv = nrfx_grtc_channel_free(GRTC_channel_1);
            if (rv != 0) {
                log_debug(1, 60);
                return;
            }
        }
    
        rv = nrfx_grtc_channel_alloc(&GRTC_channel_0);
        if (rv != 0) {
            log_debug(1, 60);
            return;
        }
    
        // Configure the GRTC compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
        rv = nrfx_grtc_syscounter_cc_relative_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_0 }, TICK_30S, true,
    						     NRFX_GRTC_CC_RELATIVE_SYSCOUNTER);
        if (rv != 0) {
            log_debug(1, 70);
            return 0;
        }
    
        rv = nrfx_grtc_channel_alloc(&GRTC_channel_1);
        if (rv != 0) {
            log_debug(1, 60);
            return;
        }
    
        // Configure the GRTC compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
        rv = nrfx_grtc_syscounter_cc_relative_set(&(nrfx_grtc_channel_t){ .handler = NULL, .p_context = NULL, .channel = GRTC_channel_1 }, TICK_30S_100MS, true,
    						     NRFX_GRTC_CC_RELATIVE_SYSCOUNTER);
        if (rv != 0) {
            log_debug(1, 72);
            return 0;
        }
    }
    
    void motor_set_move(void) {
        
    	// Trigger SET tasks timer to start the sequence (OPEN at 100ms, CLOSE at 30s, stop at 30s + 100ms)
        grtc_compare_set_GPPI();
        nrf_timer_task_trigger(timer.p_reg, NRF_TIMER_TASK_START);
        // DPPI/GPPI will take care of the rest (clear after 100ms, set after 30s, clear after 30s + 100ms)
    
        log_debug(1, 20);
    }
    
    
    nrfx_gpiote_t *motor_get_gpiote_inst_ptr(void)
    {
        return gpiote_inst;
    }
    
    /**
     * @brief Thread to manage the motor
     * 
     */
    void main_motor(void)
    {
    	nrfx_err_t rv;
    	uint8_t out_channel0, out_channel1;
        
        log_debug(1, 0);
    
        /*******************
        *   GPIO
        *******************/
    
        // Configure as outputs manually if needed (before GPIOTE config)
        nrf_gpio_cfg_output(OPEN_PIN);
        nrf_gpio_cfg_output(CLOSE_PIN);
    
        /*******************
        *   GPIOTE
        *******************/
    
        /* Initialisation GPIOTE (à faire une seule fois dans l’appli) */
        if (!nrfx_gpiote_init_check(gpiote_inst)) {
            rv = nrfx_gpiote_init(gpiote_inst,NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
            if (rv != 0) {
                log_debug(1, 1);
                return;
            }
        }
    
    	// Allocate GPIOTE channels 0 and 1 for the two output pins 
        rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel0);
        if (rv != 0) {
            log_debug(1, 2);
            return;
        }
    
        // Allocate another GPIOTE channel for the second output
        rv = nrfx_gpiote_channel_alloc(gpiote_inst, &out_channel1);
        if (rv != 0) {
            log_debug(1, 3);
            return;
        }
        
        // Configure the output pins with GPIOTE, pull-up disabled, standard drive
    	nrfx_gpiote_output_config_t output_config = {
    	    .drive = NRF_GPIO_PIN_S0S1,
            .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT,
            .pull = NRF_GPIO_PIN_NOPULL
    	};
    
        // Task configuration for the first pin (OPEN_PIN): task toogle on compare event, initial value low
        nrfx_gpiote_task_config_t task_config0 =
        {
            .task_ch = out_channel0,
            .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
        };
    
        // Task configuration for the second pin (CLOSE_PIN): task toogle on compare event, initial value low
        nrfx_gpiote_task_config_t task_config1 =
        {
            .task_ch = out_channel1,
            .polarity = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW,
        };
    
        // configure OPEN_PIN pin with task config in GPIOTE
        rv = nrfx_gpiote_output_configure(gpiote_inst, OPEN_PIN, &output_config, &task_config0);
        if (rv != 0) {
            log_debug(1, 4);
            return;
        }
     
        // Configure CLOSE_PIN pin with task config in GPIOTE
        rv = nrfx_gpiote_output_configure(gpiote_inst, CLOSE_PIN, &output_config, &task_config1);
        if (rv != 0) {
            log_debug(1, 5);
            return;
        }
    
        // Enable the tasks for both pins
        nrfx_gpiote_out_task_enable(gpiote_inst, OPEN_PIN);
        nrfx_gpiote_out_task_enable(gpiote_inst, CLOSE_PIN);
    
        /*******************
        *   TIMER
        *******************/
    
        // Initialize Timer
        nrfx_timer_config_t timer_cfg = {
            .frequency = MOTOR_TIMER20_FREQ_1MHz, // 1MHz for low consumption 
            .mode = NRF_TIMER_MODE_TIMER,
            .bit_width = NRF_TIMER_BIT_WIDTH_32,
            .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
            .p_context = NULL
        };
    
        // Check if timer is already initialized, if not initialize it
        if (!nrfx_timer_init_check(&timer)) {
            rv = nrfx_timer_init(&timer, &timer_cfg, NULL);  // No IRQ handler
            if (rv != 0) {
                log_debug(1, 6);
                log_debug(6, rv);
                return 0;
            }
        }
    
        // Configure the timer compare events with no interrupt, but with shortcuts at 0.001ms (1 tick) to set the OPEN_PIN
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL0, TICKS_0MS_1MHZ,
                                    0 ,
                                    false);  // No interrupt
        // Configure the timer compare events with no interrupt, but with shortcuts at 100ms to clear the OPEN_PIN
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, TICK_100MS,
                                    NRF_TIMER_SHORT_COMPARE1_STOP_MASK | NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK,
                                    false);  // No interrupt
    
        /*******************
        *   GRTC
        *******************/
       
        // initialize GRTC (if not already initialized, can be done in another thread if needed)
        if (!nrfx_grtc_init_check()) {
            rv = nrfx_grtc_init(NRFX_GRTC_DEFAULT_CONFIG_IRQ_PRIORITY);
            if (rv != 0) {
                log_debug(1, 6);
                return;
            }
        }
    
        grtc_compare_set_GPPI();
    
        /*******************
        *   DPPI/GPPI
        *******************/
    
        // DPPI/GPPI handles for the connections
        nrfx_gppi_handle_t gppi_handle_open, gppi_handle_clear_open, gppi_handle_pause30s, gppi_handle_close;
    
        // Get the addresses of the tasks and events to connect with DPPI/GPPI
        uint32_t set_task_addr0 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN0);
        uint32_t clr_task_addr0 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN0);
        uint32_t set_task_addr1 = nrfx_gpiote_set_task_address_get(gpiote_inst, OUTPUT_PIN1);
        uint32_t clr_task_addr1 = nrfx_gpiote_clr_task_address_get(gpiote_inst, OUTPUT_PIN1);
    
        // Get the addresses of the timer compare events
        uint32_t eep = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL0);
        uint32_t eep2 = nrfx_timer_compare_event_address_get(&timer, NRF_TIMER_CC_CHANNEL1);
        uint32_t eep3 = nrfx_grtc_event_compare_address_get(GRTC_channel_0);
        uint32_t eep4 = nrfx_grtc_event_compare_address_get(GRTC_channel_1);
    
    
        // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep, set_task_addr0, &gppi_handle_open);
        if (rv != 0) {
            log_debug(1, 7);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the first task : stop OPEN at 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep2, clr_task_addr0, &gppi_handle_clear_open);
        if (rv != 0) {
            log_debug(1, 8);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the second task : start CLOSE at 30s (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep3, set_task_addr1, &gppi_handle_pause30s);
        if (rv != 0) {
            log_debug(1, 9);
            return 0;
        }
    
        // Allocate a GPPI channel and connect the timer event to the third task : stop CLOSE at 30s + 100ms (one-to-one)
        rv = nrfx_gppi_conn_alloc(eep4, clr_task_addr1, &gppi_handle_close);
        if (rv != 0) {
            log_debug(1, 10);
            return 0;
        }
    
        // Enable the GPPI channels
        nrfx_gppi_conn_enable(gppi_handle_open);
        nrfx_gppi_conn_enable(gppi_handle_clear_open);
        nrfx_gppi_conn_enable(gppi_handle_pause30s);
        nrfx_gppi_conn_enable(gppi_handle_close);
    
        log_debug(1, 11);
        
        // start the timer to trigger the sequence of events (0.001ms, 100ms, 30s, 30s+100ms)
        nrf_timer_task_trigger(timer.p_reg, NRF_TIMER_TASK_START);
    
    	//motor controle handler loop
    	while (1) {
    		k_msleep(100);
    	}
    
    }
    
    K_THREAD_DEFINE(motor_id, STACKSIZE, main_motor, NULL, NULL, NULL, PRIORITY, 0, 0); // gestion du moteur a implementer

    I still need to implement the system off part, but I don't see any issues with that.

    thank you !

    Gaby

Children
Related