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