I've been working with the nRF54L15 for a bit now and it's been great, but trying to get a bit fancy now and running into problems. I'm developing with the latest NCS v3.0.2 release.
Below, find code I've written trying to set up pwm control so I can provide PWM dimming of an LED. To make it extra complicated, this is for a battery powered device and I need to maintain low power operation and keep HFClk disabled as much as possible.
I believe my 'task' is set up correctly, and calling 'nrfx_gpiote_clr_task_trigger' and 'nrfx_gpiote_clr_task_trigger' manually I'm able to toggle the output pin state, but it doesn't seem to be firing correctly when hooked up to run automatically.
Additionally, if I define a temporary 'nrfx_grtc_cc_handler_t' handler and pass that into one of the 'nrfx_grtc_syscounter_cc_absolute_set' calls with 'enable_irq' set to True, I do see that the callback is being hit, but seemingly only two times following initialization then never again.
pwm_led.c
#include <zephyr/kernel.h> #include <zephyr/devicetree.h> #include <zephyr/drivers/gpio.h> #include <nrfx_grtc.h> #include <nrfx_gpiote.h> #include <helpers/nrfx_gppi.h> #include <zephyr/logging/log.h> LOG_MODULE_REGISTER(led_app, LOG_LEVEL_INF); #define LED_NODE DT_ALIAS(led0) #define GPIO_PORT(node) DT_PROP_BY_PHANDLE_IDX(node, gpios, 0, port) #define GPIO_PIN_NUM(node) DT_GPIO_PIN(node, gpios) #define LED_PIN_RAW NRF_GPIO_PIN_MAP(GPIO_PORT(LED_NODE), GPIO_PIN_NUM(LED_NODE)) /* GPIOTE instance */ static const nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(20); /* Kernel timer for 1s pulse */ static struct k_timer pulse_duration_timer; /* GPPI channels for SET, CLR, and GRTC reset */ static uint8_t gppi_ch_set; static uint8_t gppi_ch_clr; static uint8_t gppi_ch_reset_timer; /* GRTC CC channels for period and duty */ static uint8_t m_cc_period; static uint8_t m_cc_duty; /* GPIOTE event channel */ static uint8_t gpiote_ch; /* PWM constants using the 1 MHz SYSCOUNTER frequency */ #define PWM_SYSCOUNTER_FREQ 1000000U // #define PWM_FREQUENCY_HZ 256U #define PWM_PERIOD_TICKS (PWM_SYSCOUNTER_FREQ / PWM_FREQUENCY_HZ) #define DUTY_CYCLE_PERCENT 80U #define PWM_DUTY_TICKS ((PWM_PERIOD_TICKS * DUTY_CYCLE_PERCENT) / 100U) /** * Stop PWM, disable GPPI and turn LED off */ static void pwm_stop(struct k_timer *timer_id) { ARG_UNUSED(timer_id); // Disable only the GPPI channels controlling the GPIO pin nrfx_gppi_channels_disable(BIT(gppi_ch_set) | BIT(gppi_ch_clr)); nrfx_gpiote_clr_task_trigger(&gpiote_inst, LED_PIN_RAW); } /** * Trigger a 1-second PWM pulse on the LED. */ void pulse_led_for_one_second(void) { // Enable the GPPI channels to start the PWM output nrfx_gppi_channels_enable(BIT(gppi_ch_set) | BIT(gppi_ch_clr)); k_timer_start(&pulse_duration_timer, K_SECONDS(1), K_NO_WAIT); } /** * Initialize GRTC and GPPI for self-running PWM. */ static void pwm_generator_init(void) { nrfx_err_t err; /* Initialize GRTC */ if (!nrfx_grtc_init_check()) { err = nrfx_grtc_init(NRFX_GRTC_DEFAULT_CONFIG_IRQ_PRIORITY); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_grtc_init error: %d", err); } } /* Initialize GPIOTE */ if (!nrfx_gpiote_init_check(&gpiote_inst)) { err = nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gpiote_init error: %d", err); } } /* Allocate GRTC channels */ err = nrfx_grtc_channel_alloc(&m_cc_period); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_grtc_channel_alloc (period) error: %d", err); } err = nrfx_grtc_channel_alloc(&m_cc_duty); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_grtc_channel_alloc (duty) error: %d", err); } /* Allocate GPIOTE channel */ err = nrfx_gpiote_channel_alloc(&gpiote_inst, &gpiote_ch); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gpiote_channel_alloc error: %d", err); } /* Allocate GPPI channels */ err = nrfx_gppi_channel_alloc(&gppi_ch_set); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gppi_channel_alloc (set) error: %d", err); } err = nrfx_gppi_channel_alloc(&gppi_ch_clr); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gppi_channel_alloc (clr) error: %d", err); } err = nrfx_gppi_channel_alloc(&gppi_ch_reset_timer); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gppi_channel_alloc (reset_timer) error: %d", err); } /* Configure GPIOTE task */ nrfx_gpiote_output_config_t out_cfg = NRFX_GPIOTE_DEFAULT_OUTPUT_CONFIG; nrfx_gpiote_task_config_t task_cfg = { .task_ch = gpiote_ch, .polarity = NRF_GPIOTE_POLARITY_TOGGLE, .init_val = NRF_GPIOTE_INITIAL_VALUE_LOW, }; err = nrfx_gpiote_output_configure(&gpiote_inst, LED_PIN_RAW, &out_cfg, &task_cfg); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gpiote_output_configure error: %d", err); } nrfx_gpiote_out_task_enable(&gpiote_inst, LED_PIN_RAW); /* Set absolute compare values */ nrfx_grtc_channel_t period_ch = { .channel = m_cc_period }; err = nrfx_grtc_syscounter_cc_absolute_set(&period_ch, PWM_PERIOD_TICKS, false); NRFX_ASSERT(err == NRFX_SUCCESS); nrfx_grtc_channel_t duty_ch = { .channel = m_cc_duty }; err = nrfx_grtc_syscounter_cc_absolute_set(&duty_ch, PWM_DUTY_TICKS, false); NRFX_ASSERT(err == NRFX_SUCCESS); /* Publish the GRTC events so GPPI can subscribe to them */ nrf_grtc_event_t evt_period_enum = nrf_grtc_sys_counter_compare_event_get(m_cc_period); nrf_grtc_event_t evt_duty_enum = nrf_grtc_sys_counter_compare_event_get(m_cc_duty); nrf_grtc_publish_set(NRF_GRTC, evt_period_enum, gppi_ch_set); nrf_grtc_publish_set(NRF_GRTC, evt_period_enum, gppi_ch_reset_timer); nrf_grtc_publish_set(NRF_GRTC, evt_duty_enum, gppi_ch_clr); /* Map GRTC events to tasks via GPPI endpoints */ uint32_t evt_period_addr = nrfx_grtc_event_address_get(evt_period_enum); uint32_t evt_duty_addr = nrfx_grtc_event_address_get(evt_duty_enum); uint32_t task_set = nrfx_gpiote_set_task_address_get(&gpiote_inst, LED_PIN_RAW); uint32_t task_clr = nrfx_gpiote_clr_task_address_get(&gpiote_inst, LED_PIN_RAW); uint32_t task_grtc_clear = nrfx_grtc_task_address_get(NRF_GRTC_TASK_CLEAR); nrfx_gppi_channel_endpoints_setup(gppi_ch_set, evt_period_addr, task_set); nrfx_gppi_channel_endpoints_setup(gppi_ch_clr, evt_duty_addr, task_clr); nrfx_gppi_channel_endpoints_setup(gppi_ch_reset_timer, evt_period_addr, task_grtc_clear); /* Enable the compare channel event generation */ nrf_grtc_sys_counter_compare_event_enable(NRF_GRTC, m_cc_period); nrf_grtc_sys_counter_compare_event_enable(NRF_GRTC, m_cc_duty); /* Enable the self-resetting mechanism (permanently) */ nrfx_gppi_channels_enable(BIT(gppi_ch_reset_timer)); /* Initialize kernel timer to stop the pulse */ k_timer_init(&pulse_duration_timer, pwm_stop, NULL); /* Attempt to clear the gtrc to get a clean edge to start from */ err = nrfx_grtc_action_perform(NRFX_GRTC_ACTION_CLEAR); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_grtc_action_perform fail: %d", err); } } /** * Initialize the low power PWM LED controller, then use 'pulse_led_for_one_second' to trigger it * to be on with the specified duty cycle for 1s. */ void led_init(void) { pwm_generator_init(); }
I am using a custom board, but including "<nordic/nrf54l15_cpuapp.dtsi>" which enables all the 'dppic??' and 'ppib??' peripherals. I also have the following relevant parts in my dts file:
/ { aliases { led0 = &p1_7_led; }; leds { compatible = "gpio-leds"; p1_7_led: p1_7_led { gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>; label = "GPPI-Controlled LED"; }; }; }; &grtc { owned-channels = <0 1 2 3 4 5 6 7 8 9 10 11>; /* Channels 7-11 reserved for Zero Latency IRQs, 3-4 for FLPR */ child-owned-channels = <3 4 7 8 9 10 11>; status = "okay"; };
If anyone has any insight as to what might be going on here, I would love to figure this out.
Thanks,
Jeremy