#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();
}/ {
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";
};