Help nRF5340: GPIOTE, DPPI, and Timer Configuration for Encoder Counting

Hi everyone,

I'm working on a project using the Nordic nRF5340 microcontroller and I need to count both rising and falling edges of a square wave signal on pin P0.14, coming from an incremental encoder.

My current approach involves combining GPIOTE, DPPI, and a TIMER peripheral configured as a counter. I've tried to use Zephyr APIs as much as possible.

Here's the relevant code snippet I'm using:

#define ENCODER_01_NODE              DT_NODELABEL(encoder_01)
static const struct gpio_dt_spec encoder_01              = GPIO_DT_SPEC_GET(ENCODER_01_NODE, gpios);

#define ENCODER_COUNTER_NODE DT_NODELABEL(timer0)

// Input pin initialization function
void InputInit()
{
    if (!device_is_ready(encoder_01.port)) {
        BOARD_DEBUG("Error: %s is not ready", encoder_01.port->name);
        return 1;
    }

    ret = gpio_pin_configure_dt(&encoder_01, GPIO_INPUT | GPIO_PULL_DOWN);
    if (ret < 0) {
        BOARD_DEBUG("Error %d: failed to configure Encoder 01", ret);
        return 1;
    }

    ret = gpio_pin_interrupt_configure_dt(&encoder_01, GPIO_INT_EDGE_BOTH);
    if (ret < 0) {
        BOARD_DEBUG("Error %d: failed to configure Encoder 01 edge", ret);
        return 1;
    }
}

/**
 * @brief Initialize the encoder counter using TIMER0 in counter mode
 * @return 0 on success, non-zero on failure
 */
int EncoderCounterInit(void)
{
    // Get timer0 device for counter functionality
    encoder_counter_dev = DEVICE_DT_GET(ENCODER_COUNTER_NODE);
    
    if (!device_is_ready(encoder_counter_dev)) {
        BOARD_DEBUG("Error: Encoder counter device not ready");
        return 1;
    }

    // Start the counter
    int ret = counter_start(encoder_counter_dev);
    if (ret < 0) {
        BOARD_DEBUG("Error %d: failed to start encoder counter", ret);
        return 1;
    }

    BOARD_DEBUG("Encoder counter initialized successfully");
    return 0;
}

/**
 * @brief Read the current encoder counter value
 * @return Current counter value
 */
uint32_t EncoderCounterRead(void)
{
    uint32_t value = 0;
    
    if (encoder_counter_dev == NULL) {
        BOARD_DEBUG("Error: Encoder counter not initialized");
        return 0;
    }

    int ret = counter_get_value(encoder_counter_dev, &value);
    if (ret < 0) {
        BOARD_DEBUG("Error %d: failed to read encoder counter", ret);
        return 0;
    }

    return value;
}

void DPPI_init()
{
    nrfx_err_t err;

    nrfx_dppi_t dppi = NRFX_DPPI_INSTANCE(0);

    uint8_t ppi_channel = 0;

    err = nrfx_dppi_channel_alloc(&dppi, &ppi_channel);
    if (err != NRFX_SUCCESS) {
        BOARD_DEBUG("Error %d: failed to allocate DPPI channel", err);
        return EXIT_FAILURE;
    }

    #define GPIOTE_INST	NRF_DT_GPIOTE_INST(DT_NODELABEL(encoder_01), gpios)

    const nrfx_gpiote_t gpiote = NRFX_GPIOTE_INSTANCE(GPIOTE_INST);



    //const nrfx_timer_t timer0 = NRFX_TIMER_INSTANCE(0);
    // Definizione alternativa per timer instance (nRF Connect SDK 2.9.0)
    static const nrfx_timer_t timer0 = {
        .p_reg = NRF_TIMER0,
        .instance_id = 0,
        .cc_channel_count = 6  // nRF5340 Timer0 ha 6 canali CC
    };


    uint32_t gpiote_event_addr = nrfx_gpiote_in_event_address_get(&gpiote, ((32 + 0) + 24));
    uint32_t timer_count_task_addr = nrfx_timer_task_address_get(&timer0, NRF_TIMER_TASK_COUNT);


    nrfx_gppi_channel_endpoints_setup(ppi_channel, gpiote_event_addr, timer_count_task_addr);

    // Enable the channel.
	nrfx_gppi_channels_enable(BIT(ppi_channel));
    
    BOARD_DEBUG("DPPI channel %d configured for encoder counter", ppi_channel);
    BOARD_DEBUG("Encoder events will now increment timer counter");
}

The code compiles successfully, but when I read the counter register using EncoderCounterRead(), I get incorrect values that are too high, as if the counter is counting an internal frequency.

Then, I tried to disconnect the event and task by not enabling the DPPI channel, but I noticed that the counter still continues to count. Shouldn't it remain stopped since I haven't enabled the DPPI?

I also tried changing the timer and using timer2 but the result doesn't change.

Can anyone help me troubleshoot this issue? I'm finding very little documentation on the internet and various forums for this specific scenario.

I'm using the nRF5340 microcontroller with Zephyr and nRF Connect SDK v2.9.0.

Thank you.

Parents
  • Hi Sergi, 

    Are you sure that your timer is in counter mode and not the timer mode?

    Your code int ret = counter_start(encoder_counter_dev); does not necessarily start the timer in counter mode. Do you have these configs enabled in your prj.conf? 

    CONFIG_COUNTER=y
    CONFIG_COUNTER_TIMER0=y
    

    Timer in counter mode is discussed before in this forum for example here my colleague gave a working example on how to use it using a similar configuration as yours. 

    Like in other thread, I recommend using the nrfx API directly to have full control on the mode of timer and other peripherals. I have not tried using Zephyr API to try this configuration to automate counting edges.

  • Sorry for delayed answer Sergio, 

    Can you please try the below changes

    #include <zephyr.h>
    #include <nrfx_timer.h>
    #include <nrfx_gpiote.h>
    #include <nrfx_dppi.h>
    #include <boards.h>
    #include <stdio.h>
    
    static const nrfx_timer_t TIMER0 = NRFX_TIMER_INSTANCE(0);
    
    static void timer0_counter_mode_init(void)
    {
        nrfx_timer_config_t cfg = NRFX_TIMER_DEFAULT_CONFIG;
        cfg.mode = NRF_TIMER_MODE_COUNTER;
        nrfx_err_t err = nrfx_timer_init(&TIMER0, &cfg, NULL);
        if (err != NRFX_SUCCESS) {
            printf("TIMER init failed: %u\n", err);
            return;
        }
        nrfx_timer_enable(&TIMER0);
    }
    
    static uint8_t gpiote_ch;
    
    static void gpiote_in_init(uint32_t pin)
    {
        nrfx_err_t err = nrfx_gpiote_init();
        if (err != NRFX_SUCCESS && err != NRFX_ERROR_ALREADY_INITIALIZED) {
            printf("GPIOTE init failed: %u\n", err);
            return;
        }
    
        err = nrfx_gpiote_channel_alloc(&gpiote_ch);
        if (err != NRFX_SUCCESS) {
            printf("GPIOTE ch alloc failed: %u\n", err);
            return;
        }
    
        nrfx_gpiote_in_config_t in_cfg = NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
        err = nrfx_gpiote_in_init(gpiote_ch, pin, &in_cfg, NULL);
        if (err != NRFX_SUCCESS) {
            printf("GPIOTE in init failed: %u\n", err);
            return;
        }
    
        nrfx_gpiote_in_event_enable(gpiote_ch, false);
    }
    
    static uint8_t dppi_ch;
    
    static void dppi_link(void)
    {
        nrfx_err_t err = nrfx_dppi_channel_alloc(&dppi_ch);
        if (err != NRFX_SUCCESS) {
            printf("DPPI ch alloc failed: %u\n", err);
            return;
        }
    
        uint32_t evt_addr = nrfx_gpiote_in_event_addr_get(gpiote_ch);
        uint32_t task_addr = nrfx_timer_task_address_get(&TIMER0, NRF_TIMER_TASK_COUNT);
    
        err = nrfx_dppi_event_endpoint_setup(dppi_ch, evt_addr);
        if (err != NRFX_SUCCESS) {
            printf("DPPI evt setup failed: %u\n", err);
            return;
        }
        err = nrfx_dppi_task_endpoint_setup(dppi_ch, task_addr);
        if (err != NRFX_SUCCESS) {
            printf("DPPI task setup failed: %u\n", err);
            return;
        }
    
        nrfx_dppi_channel_enable(dppi_ch);
    }
    
    int EncoderCounterInit(void)
    {
        timer0_counter_mode_init();
        gpiote_in_init(14);
        dppi_link();
        return 0;
    }
    
    uint32_t EncoderCounterRead(void)
    {
        nrfx_timer_capture(&TIMER0, NRF_TIMER_CC_CHANNEL0);
        return nrfx_timer_capture_get(&TIMER0, NRF_TIMER_CC_CHANNEL0);
    }
    
    void main(void)
    {
        EncoderCounterInit();
        while (1) {
            uint32_t cnt = EncoderCounterRead();
            printf("Encoder count: %u\n", cnt);
            k_msleep(500);
        }
    }
    

  • I solved it using the following code:

    nrfx_timer_t timer0 = NRFX_TIMER_INSTANCE(0);
    nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);
    
    // Il microcontrollore nRF5340 ha solo GPIOTE0
    // I due encoder hanno canali GPIOTE diversi dello stesso modulo GPIOTE0
    static const nrfx_gpiote_t gpiote = NRFX_GPIOTE_INSTANCE(0);
    
    // Encoder configuration structure
    typedef struct {
        const struct gpio_dt_spec *gpio;  // GPIO pin specification from device tree
        nrfx_timer_t *timer;              // Timer instance for this encoder
        uint8_t gpiote_channel;           // GPIOTE channel allocated for this encoder
        uint8_t dppi_channel;             // DPPI channel for GPIOTE?TIMER connection
        bool initialized;                 // Initialization status
    } encoder_config_t;
    
    // Global encoder configurations
    static encoder_config_t encoder_01_config = {
        .gpio = &encoder_01, 
        .timer = &timer0, 
        .initialized = false
    };
    static encoder_config_t encoder_02_config = {
        .gpio = &encoder_02, 
        .timer = &timer1, 
        .initialized = false
    };
    
    /**
     * @brief Initialize timer in counter mode for encoder counting
     * @param p_timer Pointer to timer instance to initialize
     * @return 0 on success, non-zero on failure
     */
    int EncoderCounterInit(nrfx_timer_t *p_timer)
    {
        // Validate input parameter
        if (p_timer == NULL || p_timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Invalid timer instance provided");
            return EXIT_FAILURE;
        }
    
        // Get base frequency for this timer instance
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(p_timer->p_reg);
        
        // Configure timer with default settings based on frequency
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
        
        // Override specific settings for encoder counter mode
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_24;  // 24-bit counter for extended range
        timer_config.mode = NRF_TIMER_MODE_COUNTER;       // Counter mode (not timer mode)
    
        // Initialize timer with counter configuration
        nrfx_err_t ret = nrfx_timer_init(p_timer, &timer_config, NULL);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to initialize timer in counter mode", ret);
            return EXIT_FAILURE;
        }
    
        // Enable the timer counter
        nrfx_timer_enable(p_timer);
        
        BOARD_DEBUG("Timer counter initialized successfully (24-bit, counter mode)");
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Read the current encoder counter value
     * @details Captures the current timer counter value using CC[0] channel.
     *          The counter represents the number of encoder pulses detected.
     * @param timer Pointer to timer instance
     * @return Current counter value (24-bit) or 0 if timer not properly initialized
     */
    uint32_t EncoderCounterRead(nrfx_timer_t *timer)
    {
        // Validate timer instance before use
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Timer instance not initialized");
            return 0;
        }
    
        // Check if timer is enabled and in counter mode
        if (!nrfx_timer_is_enabled(timer)) {
            BOARD_DEBUG("Warning: Timer counter is not enabled");
            return 0;
        }
    
        // Trigger capture of current counter value to CC[0] channel
        nrfx_timer_capture(timer, NRF_TIMER_CC_CHANNEL0);
        
        // Read the captured counter value from CC[0] register
        uint32_t counter_value = nrfx_timer_capture_get(timer, NRF_TIMER_CC_CHANNEL0);
        
        // Mask to 24-bit value (timer configured with 24-bit width)
        counter_value &= 0x00FFFFFF;
        
        return counter_value;
    }
    
    /**
     * @brief Reset the encoder counter to zero
     * @details Clears the timer counter register, resetting the encoder count to 0.
     *          This operation stops counting temporarily and restarts from zero.
     * @param timer Pointer to timer instance
     * @return 0 on success, non-zero on failure
     */
    int EncoderCounterReset(nrfx_timer_t *timer)
    {
        // Validate timer instance before use
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Timer instance not initialized");
            return EXIT_FAILURE;
        }
    
        // Check if timer is enabled before reset
        if (!nrfx_timer_is_enabled(timer)) {
            BOARD_DEBUG("Warning: Timer counter is not enabled");
            return EXIT_FAILURE;
        }
    
        // Clear the timer counter register (reset to 0)
        // This operation is atomic and doesn't require disabling the timer
        nrfx_timer_clear(timer);
        
        BOARD_DEBUG("Encoder counter reset to 0 successfully (Timer%d)", 
                   (timer == &timer0) ? 0 : 1);
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Initialize GPIOTE module for encoder input
     * @param encoder_config Pointer to encoder configuration structure (contains GPIO and timer info)
     * @return 0 on success, non-zero on failure
     */
    int EncoderGpioteInit(void *encoder_config)
    {        
        nrfx_err_t ret; 
        encoder_config_t *config = (encoder_config_t *)encoder_config;
        
        // Validate input parameters
        if (!config || !config->gpio || !config->timer) {
            BOARD_DEBUG("Error: Invalid encoder_config, gpio, or timer parameter");
            return EXIT_FAILURE;
        }
        
        // Convert gpio_dt_spec to absolute pin number
        uint32_t encoder_pin = gpio_dt_spec_to_absolute_pin(config->gpio);
        if (encoder_pin == UINT32_MAX) {
            BOARD_DEBUG("Error: Failed to convert GPIO specification to pin number");
            return EXIT_FAILURE;
        }
        
        // Initialize GPIOTE instance (may already be initialized by GPIO subsystem)
        ret = nrfx_gpiote_init(&gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
        if (ret != NRFX_SUCCESS && ret != NRFX_ERROR_ALREADY_INITIALIZED) {
            BOARD_DEBUG("Error %d: failed to initialize GPIOTE", ret);
            return EXIT_FAILURE;
        }
    
        // Allocate GPIOTE channel for this encoder
        ret = nrfx_gpiote_channel_alloc(&gpiote, &config->gpiote_channel);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to allocate GPIOTE channel", ret);
            return EXIT_FAILURE;
        }  
    
        // Configure encoder pin with no pull resistors
        static const nrf_gpio_pin_pull_t pull_cfg = NRF_GPIO_PIN_NOPULL;
    
        // Configure trigger for encoder (toggle on both edges)
        nrfx_gpiote_trigger_config_t trigger_cfg = {
            .trigger = NRFX_GPIOTE_TRIGGER_TOGGLE,  // Trigger on both rising and falling edges
            .p_in_channel = &config->gpiote_channel,       // Connect channel for DPPI
        };
    
        // Configure handler (no callback needed for hardware counting)
        static const nrfx_gpiote_handler_config_t handler_cfg = {
            .handler = NULL,        // No interrupt handler needed
            .p_context = NULL       // No context data
        };
    
        // Compose complete pin configuration
        nrfx_gpiote_input_pin_config_t input_cfg = {
            .p_pull_config = &pull_cfg,
            .p_trigger_config = &trigger_cfg,
            .p_handler_config = &handler_cfg,
        };
    
        // Configure the encoder pin with GPIOTE
        ret = nrfx_gpiote_input_configure(&gpiote, encoder_pin, &input_cfg);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to configure GPIOTE pin %d (%s.%d)", 
                       ret, encoder_pin, config->gpio->port->name, config->gpio->pin);
            // Free allocated channel on error
            nrfx_gpiote_channel_free(&gpiote, config->gpiote_channel);
            return EXIT_FAILURE;
        }
    
        // Enable trigger for the encoder pin
        nrfx_gpiote_trigger_enable(&gpiote, encoder_pin, true);
    
        // Mark as initialized
        config->initialized = true;
    
        BOARD_DEBUG("GPIOTE initialized successfully for encoder pin %d (%s.%d) (channel %d)", 
                   encoder_pin, config->gpio->port->name, config->gpio->pin, config->gpiote_channel);
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Setup DPPI connection between GPIOTE event and Timer COUNT task for encoder
     * @param encoder_config Pointer to encoder configuration structure (contains GPIO and timer info)
     * @return 0 on success, non-zero on failure
     */
    int EncoderDppiInit(void *encoder_config)
    {
        encoder_config_t *config = (encoder_config_t *)encoder_config;
        
        // Validate input parameters
        if (!config || !config->gpio || !config->timer) {
            BOARD_DEBUG("Error: Invalid encoder_config, gpio, or timer parameter");
            return EXIT_FAILURE;
        }
        
        // Check if GPIOTE was initialized first
        if (!config->initialized) {
            BOARD_DEBUG("Error: GPIOTE must be initialized before DPPI setup");
            return EXIT_FAILURE;
        }
        
        // Convert gpio_dt_spec to absolute pin number
        uint32_t encoder_pin = gpio_dt_spec_to_absolute_pin(config->gpio);
        if (encoder_pin == UINT32_MAX) {
            BOARD_DEBUG("Error: Failed to convert GPIO specification to pin number");
            return EXIT_FAILURE;
        }
        
        // Initialize DPPI instance
        nrfx_dppi_t dppi = NRFX_DPPI_INSTANCE(0);
        
        // Allocate DPPI channel for encoder connection
        nrfx_err_t err = nrfx_dppi_channel_alloc(&dppi, &config->dppi_channel);
        if (err != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to allocate DPPI channel", err);
            return EXIT_FAILURE;
        }
        
        // Get correct addresses for DPPI connection
        uint32_t gpiote_event_addr = nrfx_gpiote_in_event_address_get(&gpiote, encoder_pin);
        uint32_t timer_count_task_addr = nrfx_timer_task_address_get(config->timer, NRF_TIMER_TASK_COUNT);
        
        // Validate addresses
        if (gpiote_event_addr == 0 || timer_count_task_addr == 0) {
            BOARD_DEBUG("Error: Invalid DPPI endpoint addresses (GPIOTE: 0x%08X, TIMER: 0x%08X)", 
                       gpiote_event_addr, timer_count_task_addr);
            nrfx_dppi_channel_free(&dppi, config->dppi_channel);
            return EXIT_FAILURE;
        }
        
        // Setup DPPI channel endpoints (GPIOTE event ? TIMER COUNT task)
        nrfx_gppi_channel_endpoints_setup(config->dppi_channel, gpiote_event_addr, timer_count_task_addr);
        
        // Enable DPPI channel
        nrfx_gppi_channels_enable(BIT(config->dppi_channel));
        
        BOARD_DEBUG("DPPI channel %d configured for encoder pin %d (%s.%d) ? Timer%d", 
                   config->dppi_channel, encoder_pin, config->gpio->port->name, config->gpio->pin,
                   (config->timer == &timer0) ? 0 : 1);
        BOARD_DEBUG("Encoder events will now increment timer counter");
        BOARD_DEBUG("GPIOTE event addr: 0x%08X, Timer task addr: 0x%08X", 
                   gpiote_event_addr, timer_count_task_addr);
        
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Calculate encoder RPM (Revolutions Per Minute) from timer counter
     * @details Calculates RPM based on encoder pulse differences over time.
     *          Assumes encoder generates 4 edges per revolution.
     *          Each timer instance maintains separate history for accurate multi-encoder support.
     * @param timer Pointer to timer instance used for encoder counting
     * @return RPM value (0-4294967295), or 0 if timer invalid or insufficient time elapsed
     */
    uint32_t EncoderGetRpm(nrfx_timer_t *timer)
    {
        // Structure to store per-timer historical data for RPM calculation
        typedef struct {
            uint32_t last_counter;
            uint32_t last_time;
            bool initialized;
        } encoder_history_t;
        
        // Static history for each timer (Timer0 and Timer1)
        static encoder_history_t timer0_history = {0, 0, false};
        static encoder_history_t timer1_history = {0, 0, false};
        
        // Validate timer parameter
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Invalid timer instance provided to EncoderGetRpm");
            return 0;
        }
        
        // Select appropriate history based on timer instance
        encoder_history_t *history;
        if (timer == &timer0) {
            history = &timer0_history;
        } else if (timer == &timer1) {
            history = &timer1_history;
        } else {
            BOARD_DEBUG("Error: Unknown timer instance in EncoderGetRpm");
            return 0;
        }
        
        // Read current encoder counter and system time
        uint32_t current_counter = EncoderCounterRead(timer);
        uint32_t current_time = GetTick();
        
        // Initialize history on first call for this timer
        if (!history->initialized) {
            history->last_counter = current_counter;
            history->last_time = current_time;
            history->initialized = true;
            return 0; // Return 0 RPM on first call (no previous data)
        }
        
        // Calculate time and counter differences
        uint32_t delta_time = current_time - history->last_time;
        uint32_t delta_counter = current_counter - history->last_counter;
        
        // Calculate RPM only if sufficient time has elapsed (avoid division by zero)
        uint32_t rpm = 0;
        if (delta_time > 0) {
            // RPM calculation:
            // - Encoder generates 4 edges per revolution
            // - RPM = (revolutions / time_in_minutes)
            // - revolutions = delta_counter / 4
            // - time_in_minutes = delta_time / 60000 ms
            // - RPM = (delta_counter / 4) / (delta_time / 60000)
            // - RPM = (delta_counter * 60000) / (4 * delta_time)
            // - RPM = (delta_counter * 15000) / delta_time
            
            // Note: Overflow protection omitted as hardware constraints ensure:
            // - Max motor RPM << 4294967295 (uint32_t limit)
            // - Max delta_counter * 15000 << UINT32_MAX (arithmetic limit)
            rpm = (delta_counter * 15000) / delta_time;
            
            // Update history only after successful calculation
            history->last_counter = current_counter;
            history->last_time = current_time;
        }
        
        return rpm;
    }
    
    int EncoderInit(void)
    {
        // Encoder_01: P0.24 → GPIOTE → DPPI → Timer0
        // Encoder_02: P1.14 → GPIOTE → DPPI → Timer1
    
        // Initialize Timer0 as counter for encoder_01 pin (using global definition)
        if (EncoderCounterInit(&timer0)) {
            BOARD_DEBUG("EncoderCounterInit init failed!");
            return EXIT_FAILURE;
        }
    
        // Initialize GPIOTE for encoder_01
        if (EncoderGpioteInit(&encoder_01_config)) {
            BOARD_DEBUG("EncoderGpioteInit init failed for encoder_01!");
            return EXIT_FAILURE;
        }
    
        // Setup DPPI connection for encoder_01 counting
        if (EncoderDppiInit(&encoder_01_config)) {
            BOARD_DEBUG("EncoderDppiInit init failed for encoder_01!");
            return EXIT_FAILURE;
        }
    
        // Initialize Timer1 as counter for encoder_02 pin (using global definition)
        if (EncoderCounterInit(&timer1)) {
            BOARD_DEBUG("EncoderCounterInit init failed for timer1!");
            return EXIT_FAILURE;
        }
    
        // Initialize GPIOTE for encoder_02
        if (EncoderGpioteInit(&encoder_02_config)) {
            BOARD_DEBUG("EncoderGpioteInit init failed for encoder_02!");
            return EXIT_FAILURE;
        }
    
        // Setup DPPI connection for encoder_02 counting
        if (EncoderDppiInit(&encoder_02_config)) {
            BOARD_DEBUG("EncoderDppiInit init failed for encoder_02!");
            return EXIT_FAILURE;
        }
        
    	return EXIT_SUCCESS;
    }
    
    int main()
    {
        EncoderInit();
        
        while(true)
        {
            ...
            uint16_t counter_1 = (uint16_t)EncoderGetRpm(&timer0);
    		uint16_t counter_2 = (uint16_t)EncoderGetRpm(&timer1);
    		...
        }
    }

    I hope this helps someone.

    Thanks

Reply
  • I solved it using the following code:

    nrfx_timer_t timer0 = NRFX_TIMER_INSTANCE(0);
    nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);
    
    // Il microcontrollore nRF5340 ha solo GPIOTE0
    // I due encoder hanno canali GPIOTE diversi dello stesso modulo GPIOTE0
    static const nrfx_gpiote_t gpiote = NRFX_GPIOTE_INSTANCE(0);
    
    // Encoder configuration structure
    typedef struct {
        const struct gpio_dt_spec *gpio;  // GPIO pin specification from device tree
        nrfx_timer_t *timer;              // Timer instance for this encoder
        uint8_t gpiote_channel;           // GPIOTE channel allocated for this encoder
        uint8_t dppi_channel;             // DPPI channel for GPIOTE?TIMER connection
        bool initialized;                 // Initialization status
    } encoder_config_t;
    
    // Global encoder configurations
    static encoder_config_t encoder_01_config = {
        .gpio = &encoder_01, 
        .timer = &timer0, 
        .initialized = false
    };
    static encoder_config_t encoder_02_config = {
        .gpio = &encoder_02, 
        .timer = &timer1, 
        .initialized = false
    };
    
    /**
     * @brief Initialize timer in counter mode for encoder counting
     * @param p_timer Pointer to timer instance to initialize
     * @return 0 on success, non-zero on failure
     */
    int EncoderCounterInit(nrfx_timer_t *p_timer)
    {
        // Validate input parameter
        if (p_timer == NULL || p_timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Invalid timer instance provided");
            return EXIT_FAILURE;
        }
    
        // Get base frequency for this timer instance
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(p_timer->p_reg);
        
        // Configure timer with default settings based on frequency
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
        
        // Override specific settings for encoder counter mode
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_24;  // 24-bit counter for extended range
        timer_config.mode = NRF_TIMER_MODE_COUNTER;       // Counter mode (not timer mode)
    
        // Initialize timer with counter configuration
        nrfx_err_t ret = nrfx_timer_init(p_timer, &timer_config, NULL);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to initialize timer in counter mode", ret);
            return EXIT_FAILURE;
        }
    
        // Enable the timer counter
        nrfx_timer_enable(p_timer);
        
        BOARD_DEBUG("Timer counter initialized successfully (24-bit, counter mode)");
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Read the current encoder counter value
     * @details Captures the current timer counter value using CC[0] channel.
     *          The counter represents the number of encoder pulses detected.
     * @param timer Pointer to timer instance
     * @return Current counter value (24-bit) or 0 if timer not properly initialized
     */
    uint32_t EncoderCounterRead(nrfx_timer_t *timer)
    {
        // Validate timer instance before use
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Timer instance not initialized");
            return 0;
        }
    
        // Check if timer is enabled and in counter mode
        if (!nrfx_timer_is_enabled(timer)) {
            BOARD_DEBUG("Warning: Timer counter is not enabled");
            return 0;
        }
    
        // Trigger capture of current counter value to CC[0] channel
        nrfx_timer_capture(timer, NRF_TIMER_CC_CHANNEL0);
        
        // Read the captured counter value from CC[0] register
        uint32_t counter_value = nrfx_timer_capture_get(timer, NRF_TIMER_CC_CHANNEL0);
        
        // Mask to 24-bit value (timer configured with 24-bit width)
        counter_value &= 0x00FFFFFF;
        
        return counter_value;
    }
    
    /**
     * @brief Reset the encoder counter to zero
     * @details Clears the timer counter register, resetting the encoder count to 0.
     *          This operation stops counting temporarily and restarts from zero.
     * @param timer Pointer to timer instance
     * @return 0 on success, non-zero on failure
     */
    int EncoderCounterReset(nrfx_timer_t *timer)
    {
        // Validate timer instance before use
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Timer instance not initialized");
            return EXIT_FAILURE;
        }
    
        // Check if timer is enabled before reset
        if (!nrfx_timer_is_enabled(timer)) {
            BOARD_DEBUG("Warning: Timer counter is not enabled");
            return EXIT_FAILURE;
        }
    
        // Clear the timer counter register (reset to 0)
        // This operation is atomic and doesn't require disabling the timer
        nrfx_timer_clear(timer);
        
        BOARD_DEBUG("Encoder counter reset to 0 successfully (Timer%d)", 
                   (timer == &timer0) ? 0 : 1);
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Initialize GPIOTE module for encoder input
     * @param encoder_config Pointer to encoder configuration structure (contains GPIO and timer info)
     * @return 0 on success, non-zero on failure
     */
    int EncoderGpioteInit(void *encoder_config)
    {        
        nrfx_err_t ret; 
        encoder_config_t *config = (encoder_config_t *)encoder_config;
        
        // Validate input parameters
        if (!config || !config->gpio || !config->timer) {
            BOARD_DEBUG("Error: Invalid encoder_config, gpio, or timer parameter");
            return EXIT_FAILURE;
        }
        
        // Convert gpio_dt_spec to absolute pin number
        uint32_t encoder_pin = gpio_dt_spec_to_absolute_pin(config->gpio);
        if (encoder_pin == UINT32_MAX) {
            BOARD_DEBUG("Error: Failed to convert GPIO specification to pin number");
            return EXIT_FAILURE;
        }
        
        // Initialize GPIOTE instance (may already be initialized by GPIO subsystem)
        ret = nrfx_gpiote_init(&gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
        if (ret != NRFX_SUCCESS && ret != NRFX_ERROR_ALREADY_INITIALIZED) {
            BOARD_DEBUG("Error %d: failed to initialize GPIOTE", ret);
            return EXIT_FAILURE;
        }
    
        // Allocate GPIOTE channel for this encoder
        ret = nrfx_gpiote_channel_alloc(&gpiote, &config->gpiote_channel);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to allocate GPIOTE channel", ret);
            return EXIT_FAILURE;
        }  
    
        // Configure encoder pin with no pull resistors
        static const nrf_gpio_pin_pull_t pull_cfg = NRF_GPIO_PIN_NOPULL;
    
        // Configure trigger for encoder (toggle on both edges)
        nrfx_gpiote_trigger_config_t trigger_cfg = {
            .trigger = NRFX_GPIOTE_TRIGGER_TOGGLE,  // Trigger on both rising and falling edges
            .p_in_channel = &config->gpiote_channel,       // Connect channel for DPPI
        };
    
        // Configure handler (no callback needed for hardware counting)
        static const nrfx_gpiote_handler_config_t handler_cfg = {
            .handler = NULL,        // No interrupt handler needed
            .p_context = NULL       // No context data
        };
    
        // Compose complete pin configuration
        nrfx_gpiote_input_pin_config_t input_cfg = {
            .p_pull_config = &pull_cfg,
            .p_trigger_config = &trigger_cfg,
            .p_handler_config = &handler_cfg,
        };
    
        // Configure the encoder pin with GPIOTE
        ret = nrfx_gpiote_input_configure(&gpiote, encoder_pin, &input_cfg);
        if (ret != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to configure GPIOTE pin %d (%s.%d)", 
                       ret, encoder_pin, config->gpio->port->name, config->gpio->pin);
            // Free allocated channel on error
            nrfx_gpiote_channel_free(&gpiote, config->gpiote_channel);
            return EXIT_FAILURE;
        }
    
        // Enable trigger for the encoder pin
        nrfx_gpiote_trigger_enable(&gpiote, encoder_pin, true);
    
        // Mark as initialized
        config->initialized = true;
    
        BOARD_DEBUG("GPIOTE initialized successfully for encoder pin %d (%s.%d) (channel %d)", 
                   encoder_pin, config->gpio->port->name, config->gpio->pin, config->gpiote_channel);
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Setup DPPI connection between GPIOTE event and Timer COUNT task for encoder
     * @param encoder_config Pointer to encoder configuration structure (contains GPIO and timer info)
     * @return 0 on success, non-zero on failure
     */
    int EncoderDppiInit(void *encoder_config)
    {
        encoder_config_t *config = (encoder_config_t *)encoder_config;
        
        // Validate input parameters
        if (!config || !config->gpio || !config->timer) {
            BOARD_DEBUG("Error: Invalid encoder_config, gpio, or timer parameter");
            return EXIT_FAILURE;
        }
        
        // Check if GPIOTE was initialized first
        if (!config->initialized) {
            BOARD_DEBUG("Error: GPIOTE must be initialized before DPPI setup");
            return EXIT_FAILURE;
        }
        
        // Convert gpio_dt_spec to absolute pin number
        uint32_t encoder_pin = gpio_dt_spec_to_absolute_pin(config->gpio);
        if (encoder_pin == UINT32_MAX) {
            BOARD_DEBUG("Error: Failed to convert GPIO specification to pin number");
            return EXIT_FAILURE;
        }
        
        // Initialize DPPI instance
        nrfx_dppi_t dppi = NRFX_DPPI_INSTANCE(0);
        
        // Allocate DPPI channel for encoder connection
        nrfx_err_t err = nrfx_dppi_channel_alloc(&dppi, &config->dppi_channel);
        if (err != NRFX_SUCCESS) {
            BOARD_DEBUG("Error %d: failed to allocate DPPI channel", err);
            return EXIT_FAILURE;
        }
        
        // Get correct addresses for DPPI connection
        uint32_t gpiote_event_addr = nrfx_gpiote_in_event_address_get(&gpiote, encoder_pin);
        uint32_t timer_count_task_addr = nrfx_timer_task_address_get(config->timer, NRF_TIMER_TASK_COUNT);
        
        // Validate addresses
        if (gpiote_event_addr == 0 || timer_count_task_addr == 0) {
            BOARD_DEBUG("Error: Invalid DPPI endpoint addresses (GPIOTE: 0x%08X, TIMER: 0x%08X)", 
                       gpiote_event_addr, timer_count_task_addr);
            nrfx_dppi_channel_free(&dppi, config->dppi_channel);
            return EXIT_FAILURE;
        }
        
        // Setup DPPI channel endpoints (GPIOTE event ? TIMER COUNT task)
        nrfx_gppi_channel_endpoints_setup(config->dppi_channel, gpiote_event_addr, timer_count_task_addr);
        
        // Enable DPPI channel
        nrfx_gppi_channels_enable(BIT(config->dppi_channel));
        
        BOARD_DEBUG("DPPI channel %d configured for encoder pin %d (%s.%d) ? Timer%d", 
                   config->dppi_channel, encoder_pin, config->gpio->port->name, config->gpio->pin,
                   (config->timer == &timer0) ? 0 : 1);
        BOARD_DEBUG("Encoder events will now increment timer counter");
        BOARD_DEBUG("GPIOTE event addr: 0x%08X, Timer task addr: 0x%08X", 
                   gpiote_event_addr, timer_count_task_addr);
        
        return EXIT_SUCCESS;
    }
    
    /**
     * @brief Calculate encoder RPM (Revolutions Per Minute) from timer counter
     * @details Calculates RPM based on encoder pulse differences over time.
     *          Assumes encoder generates 4 edges per revolution.
     *          Each timer instance maintains separate history for accurate multi-encoder support.
     * @param timer Pointer to timer instance used for encoder counting
     * @return RPM value (0-4294967295), or 0 if timer invalid or insufficient time elapsed
     */
    uint32_t EncoderGetRpm(nrfx_timer_t *timer)
    {
        // Structure to store per-timer historical data for RPM calculation
        typedef struct {
            uint32_t last_counter;
            uint32_t last_time;
            bool initialized;
        } encoder_history_t;
        
        // Static history for each timer (Timer0 and Timer1)
        static encoder_history_t timer0_history = {0, 0, false};
        static encoder_history_t timer1_history = {0, 0, false};
        
        // Validate timer parameter
        if (!timer || timer->p_reg == NULL) {
            BOARD_DEBUG("Error: Invalid timer instance provided to EncoderGetRpm");
            return 0;
        }
        
        // Select appropriate history based on timer instance
        encoder_history_t *history;
        if (timer == &timer0) {
            history = &timer0_history;
        } else if (timer == &timer1) {
            history = &timer1_history;
        } else {
            BOARD_DEBUG("Error: Unknown timer instance in EncoderGetRpm");
            return 0;
        }
        
        // Read current encoder counter and system time
        uint32_t current_counter = EncoderCounterRead(timer);
        uint32_t current_time = GetTick();
        
        // Initialize history on first call for this timer
        if (!history->initialized) {
            history->last_counter = current_counter;
            history->last_time = current_time;
            history->initialized = true;
            return 0; // Return 0 RPM on first call (no previous data)
        }
        
        // Calculate time and counter differences
        uint32_t delta_time = current_time - history->last_time;
        uint32_t delta_counter = current_counter - history->last_counter;
        
        // Calculate RPM only if sufficient time has elapsed (avoid division by zero)
        uint32_t rpm = 0;
        if (delta_time > 0) {
            // RPM calculation:
            // - Encoder generates 4 edges per revolution
            // - RPM = (revolutions / time_in_minutes)
            // - revolutions = delta_counter / 4
            // - time_in_minutes = delta_time / 60000 ms
            // - RPM = (delta_counter / 4) / (delta_time / 60000)
            // - RPM = (delta_counter * 60000) / (4 * delta_time)
            // - RPM = (delta_counter * 15000) / delta_time
            
            // Note: Overflow protection omitted as hardware constraints ensure:
            // - Max motor RPM << 4294967295 (uint32_t limit)
            // - Max delta_counter * 15000 << UINT32_MAX (arithmetic limit)
            rpm = (delta_counter * 15000) / delta_time;
            
            // Update history only after successful calculation
            history->last_counter = current_counter;
            history->last_time = current_time;
        }
        
        return rpm;
    }
    
    int EncoderInit(void)
    {
        // Encoder_01: P0.24 → GPIOTE → DPPI → Timer0
        // Encoder_02: P1.14 → GPIOTE → DPPI → Timer1
    
        // Initialize Timer0 as counter for encoder_01 pin (using global definition)
        if (EncoderCounterInit(&timer0)) {
            BOARD_DEBUG("EncoderCounterInit init failed!");
            return EXIT_FAILURE;
        }
    
        // Initialize GPIOTE for encoder_01
        if (EncoderGpioteInit(&encoder_01_config)) {
            BOARD_DEBUG("EncoderGpioteInit init failed for encoder_01!");
            return EXIT_FAILURE;
        }
    
        // Setup DPPI connection for encoder_01 counting
        if (EncoderDppiInit(&encoder_01_config)) {
            BOARD_DEBUG("EncoderDppiInit init failed for encoder_01!");
            return EXIT_FAILURE;
        }
    
        // Initialize Timer1 as counter for encoder_02 pin (using global definition)
        if (EncoderCounterInit(&timer1)) {
            BOARD_DEBUG("EncoderCounterInit init failed for timer1!");
            return EXIT_FAILURE;
        }
    
        // Initialize GPIOTE for encoder_02
        if (EncoderGpioteInit(&encoder_02_config)) {
            BOARD_DEBUG("EncoderGpioteInit init failed for encoder_02!");
            return EXIT_FAILURE;
        }
    
        // Setup DPPI connection for encoder_02 counting
        if (EncoderDppiInit(&encoder_02_config)) {
            BOARD_DEBUG("EncoderDppiInit init failed for encoder_02!");
            return EXIT_FAILURE;
        }
        
    	return EXIT_SUCCESS;
    }
    
    int main()
    {
        EncoderInit();
        
        while(true)
        {
            ...
            uint16_t counter_1 = (uint16_t)EncoderGetRpm(&timer0);
    		uint16_t counter_2 = (uint16_t)EncoderGetRpm(&timer1);
    		...
        }
    }

    I hope this helps someone.

    Thanks

Children
No Data
Related