nrf54l15 pwm capture on sdk v3.3.1

I have a sensor that outputs PWM that I need to interface to. All I need is an input GPIO pin and using GPIOTE + GPPI + timer capture I would be able to determine the duty cycle ratio.

I found a sample program that gives me an idea on how to go about it. From its README: "Zephyr GPIO driver is disabled to prevent it from registering its own handler for the GPIOTE interrupt."  The driver is disable by placing "CONFIG_GPIO=n" in prj.conf. 

The thing is I do need the GPIO driver to be enabled for other functions. Can it be done with the GPIO driver enabled? Is there an example I could use? If not, documentation?

Thanks

Regards

Sammy

Parents
  • From the nrfx 4.0 migration notes: 

    "Driver instance interrupt handler now has to be explicitly defined by user and call nrfx_gpiote_irq_handler with pointer to the driver instance as an argument. Removed NRFX_GPIOTE_INST_HANDLER_GET macro.
    Action : Use NRFX_INSTANCE_IRQ_HANDLER_DEFINE."

    What does this actually mean? Are there any examples?

    Thanks

    Regards

    Sammy

  • Hi,

    That migration note is about how you register the GPIOTE interrupt handler in nrfx 4.0 when your application owns GPIOTE itself. But in your case it doesn't apply. Since you're keeping CONFIG_GPIO=y, the Zephyr GPIO driver already initializes GPIOTE and connects its interrupt at boot. So you don't need NRFX_INSTANCE_IRQ_HANDLER_DEFINE.

    That note only becomes relevant if you run without the GPIO driver (CONFIG_GPIO=n).

    Best Regards,
    Syed Maysum

Reply
  • Hi,

    That migration note is about how you register the GPIOTE interrupt handler in nrfx 4.0 when your application owns GPIOTE itself. But in your case it doesn't apply. Since you're keeping CONFIG_GPIO=y, the Zephyr GPIO driver already initializes GPIOTE and connects its interrupt at boot. So you don't need NRFX_INSTANCE_IRQ_HANDLER_DEFINE.

    That note only becomes relevant if you run without the GPIO driver (CONFIG_GPIO=n).

    Best Regards,
    Syed Maysum

Children
  • Hi

    Thanks for replying.

    So, if I understood you correctly if I keep CONFIG_GPIO=y I should not call IRQ_CONNECT nor nrfx_gpiote_init(). In the migration note it states:

    Action : Declare single driver instance within the system and share it among dependent software components, e.g. by using global scope.

    How do I access this driver interface?

    I did write code and if I set CONFIG_GPIO=n it works but if I set it to y it doesn't:

    LOG_MODULE_REGISTER(CurrentSensor, LOG_LEVEL_DBG);
    
    static nrfx_timer_t timer = NRFX_TIMER_INSTANCE(NRF_TIMER20);
    static nrfx_gpiote_t gpiote =  NRFX_GPIOTE_INSTANCE(NRF_GPIOTE20);
    static nrfx_gppi_t gppi;
    
    void pwm_capture_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t trigger, void *context) {
        pwm_capture_data_t *ch = (pwm_capture_data_t *)context;
        uint32_t ts = nrfx_timer_capture(&timer, ch->cc_channel);
        int pin_high = nrf_gpio_pin_read(ch->pin);
    
        if (ch->index == 0 && pin_high) {
            ch->timestamps[0] = ts;
            ch->index = 1;
        } else if (ch->index == 1 && !pin_high) {
            ch->timestamps[1] = ts;
            ch->index = 2;
        } else if (ch->index == 2 && pin_high) {
            ch->timestamps[2] = ts;
    
            // Calculate raw values for this specific pulse
            uint32_t p = ch->timestamps[2] - ch->timestamps[0];
            uint32_t w = ch->timestamps[1] - ch->timestamps[0];
    
            // Add to accumulators (for averaging)
            ch->acc_period += p;
            ch->acc_pulse_width += w;
            ch->sample_count++;
    
            // Reset for next cycle
            ch->timestamps[0] = ts;
            ch->index = 1;
        }
    }
    
    void pwm_capture_init_timer(void) {
        nrfx_timer_config_t config = NRFX_TIMER_DEFAULT_CONFIG(8000000);
        config.bit_width = NRF_TIMER_BIT_WIDTH_32;
        nrfx_timer_init(&timer, &config, NULL);
        nrfx_timer_enable(&timer);
    }
    
    void pwm_capture_init_gpiote(void)
    {
       int err;
       //IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE20), IRQ_PRIO_LOWEST, nrfx_gpiote_irq_handler, &gpiote, 0);
    #ifndef CONFIG_GPIO
       IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE20)+1, IRQ_PRIO_LOWEST, nrfx_gpiote_irq_handler, &gpiote, 0);
       err = nrfx_gpiote_init(&gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
       if (err != 0) {
          LOG_ERR("Failed to initialize GPIOTE driver: %x", err);
       }
    #endif
       //irq_enable(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE20));
    }
    void pwm_gppi_init(void)
    {
       nrfx_gppi_init(&gppi);
    }
    
    
    int pwm_capture_init_channel(pwm_capture_data_t *data, uint32_t pin, uint8_t cc_chan) {
        int err;
        uint8_t gpiote_ch;
    
        data->pin = pin;
        data->cc_channel = cc_chan;
        data->index = 0;
    
        nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL);
    
        // Allocate GPIOTE channel
        err = nrfx_gpiote_channel_alloc(&gpiote, &gpiote_ch);
        if (err != 0) {
            LOG_ERR("Failed to allocate GPIOTE channel for pin %d. Err: %x", pin, err);
            return -1;
        }
    
        // Configure Trigger
        nrfx_gpiote_trigger_config_t trig = {
            .trigger = NRFX_GPIOTE_TRIGGER_TOGGLE,
            .p_in_channel = &gpiote_ch
        };
        nrfx_gpiote_handler_config_t hand = {
            .handler = pwm_capture_handler,
            .p_context = data
        };
        nrfx_gpiote_input_pin_config_t cfg = {
            .p_trigger_config = &trig,
            .p_handler_config = &hand
        };
    
        err = nrfx_gpiote_input_configure(&gpiote, pin, &cfg);
        if (err != 0) {
            LOG_ERR("GPIOTE config failed for pin %d", pin);
            return -1;
        }
    
        nrfx_gpiote_trigger_enable(&gpiote, pin, true);
    
        // PPI Setup
        nrfx_gppi_handle_t gppi_h;
        err = nrfx_gppi_conn_alloc (
            nrfx_gpiote_in_event_address_get(&gpiote, pin),
            nrfx_timer_capture_task_address_get(&timer, cc_chan),
            &gppi_h);
    
        if (err != 0) {
            LOG_ERR("PPI alloc failed for pin %d", pin);
            return -1;
        }
        nrfx_gppi_conn_enable(gppi_h);
        LOG_INF("PWM Capture Init Success: Pin %d on CC%d", pin, cc_chan);
        return 0;
    }
    
    pwm_results_t pwm_capture_get_averages(pwm_capture_data_t *data) {
        pwm_results_t results = {0};
    
        // Lock interrupts to safely snapshot 64-bit values and reset
        unsigned int key = irq_lock();
        uint64_t total_p = data->acc_period;
        uint64_t total_w = data->acc_pulse_width;
        uint32_t count = data->sample_count;
    
        // Reset accumulators for the next 1s window
        data->acc_period = 0;
        data->acc_pulse_width = 0;
        data->sample_count = 0;
        irq_unlock(key);
    
        if (count > 0) {
            results.avg_period = (uint32_t)(total_p / count);
            results.avg_pulse_width = (uint32_t)(total_w / count);
        }
    
        return results;
    }
    
    void pwm_capture_log_debug(const char *label, pwm_results_t *res) {
        if (res->avg_period == 0) {
            //LOG_WRN("[%s] No pulses detected (Signal may be static 0%% or 100%%)", label);
            return;
        }
    
        // Timer is at 8MHz -> 8,000,000 ticks per second
        double timer_freq = 8000000.0;
    
        double period_ms = (res->avg_period / timer_freq) * 1000.0;
        double width_ms = (res->avg_pulse_width / timer_freq) * 1000.0;
        double freq_hz = timer_freq / res->avg_period;
        double duty_cycle = ((double)res->avg_pulse_width / res->avg_period) * 100.0;
    
        LOG_INF("--- PWM Debug [%s] ---", label);
        LOG_INF("  Avg Period: %u ticks (%.3f ms)", res->avg_period, period_ms);
        LOG_INF("  Avg Width:  %u ticks (%.3f ms)", res->avg_pulse_width, width_ms);
        LOG_INF("  Frequency:  %.2f Hz", freq_hz);
        LOG_INF("  Duty Cycle: %.2f %%", duty_cycle);
    }
    
    void pwm_capture_init(void)
    {
       pwm_capture_init_timer();
       pwm_capture_init_gpiote();
    }
    
    

    and main.c:

    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <hal/nrf_gpio.h>
    #include <zephyr/drivers/sensor.h>
    
    #include "currentSensor.h"
    
    #define LOG_MODULE_NAME main
    //LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_LOG_DEFAULT_LEVEL);
    LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_DBG);
    
    pwm_capture_data_t currentsensor;
    pwm_results_t currentData;
    
    int main(void)
    {
       int dutyCycle = 0;
       pwm_capture_init();
       pwm_capture_init_channel(&currentsensor, NRF_GPIO_PIN_MAP(1, 9), NRF_TIMER_CC_CHANNEL3);
       while (1) {
          currentData = pwm_capture_get_averages(&currentsensor);
          if (currentData.avg_period != 0) {
             dutyCycle = (100 * currentData.avg_pulse_width) / currentData.avg_period;
          }
          LOG_DBG("Current A: period: %d width: %d, duty: %d", currentData.avg_period, currentData.avg_pulse_width, dutyCycle);
          k_msleep(1000);
       }
       return 0;
    }
    

    Thanks

    Regards

    Sammy

Related