Controlling LED through pwm using nrfx or other offloading way for nrf54l15

Hi,

I am trying to do pwm a rgb led using nrf54l15dk and custom board using nrf54l15 on port1 with pin connections on (p1.10, p1.8 and p1.13 for custom) or on (p1.11, p1.12 and p1.10 for nrf54l15dk).

So, on nrf54l15dk I am able to do pwm using pwm_set_dt command, but it consumes CPU cycles so, I want to move that to nrf_pwm_sequence_t command which works fine in nrf52840. When I tried nrf_pwm_sequence_t it did not modulate pulse which it should do. 

I tried doing using both common and individual load, still no luck.

#define TOP_VAL_FADE 250
#define STEP_COUNT_FADE 25

static nrfx_pwm_t m_pwm20 = NRFX_PWM_INSTANCE(20);
static nrfx_pwm_t m_pwm21 = NRFX_PWM_INSTANCE(21);

static nrf_pwm_values_common_t fade_in_out_values[2 * STEP_COUNT_FADE];
static nrf_pwm_sequence_t led_on_seq;
static nrf_pwm_sequence_t led_off_seq;
static nrf_pwm_values_common_t stay_off_values[2] = {0};

void set_fade_values_common(void)
{
    for (uint32_t i = 0; i < STEP_COUNT_FADE; i++)
    {
        uint16_t value = (TOP_VAL_FADE * (i + 1)) / STEP_COUNT_FADE;
        fade_in_out_values[i] = value;
        fade_in_out_values[STEP_COUNT_FADE + i] = TOP_VAL_FADE - value;
        LOG_INF("value of fade %d: %d", i, value);
    }
}

void start_pwm_pulse(void)
{    
    // Configure fade in/out sequence
    led_on_seq.values.p_common = fade_in_out_values;
    led_on_seq.length = NRF_PWM_VALUES_LENGTH(fade_in_out_values);
    led_on_seq.repeats = 160; // All we have to do is update the repeats! Less repeats = faster fade in/out
    led_on_seq.end_delay = 0;
    
    // Configure off sequence
    led_off_seq.values.p_common = stay_off_values;
    led_off_seq.length = NRF_PWM_VALUES_LENGTH(stay_off_values);
    led_off_seq.repeats = 30;
    led_off_seq.end_delay = 0;
    
    // Start playback
    nrfx_pwm_complex_playback(&m_pwm20, &led_on_seq, &led_off_seq, 1, NRFX_PWM_FLAG_LOOP);
    nrfx_pwm_complex_playback(&m_pwm21, &led_on_seq, &led_off_seq, 1, NRFX_PWM_FLAG_LOOP);
    
    LOG_INF("PWM pulse started");
}

static int ktd2026_init(const struct device *dev) {
    LOG_DBG("LED Softblink init");

    nrfx_pwm_config_t config20 = {
        // .output_pins = { RED_PIN, GREEN_PIN, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .output_pins = { RED_PIN, 0xFF, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .irq_priority = 7,
        .base_clock   = NRF_PWM_CLK_1MHz,
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = TOP_VAL_FADE,
        .load_mode    = NRF_PWM_LOAD_COMMON,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    nrfx_pwm_config_t config21 = {
        .output_pins = { GREEN_PIN, 0xFF, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .irq_priority = 7,
        .base_clock   = NRF_PWM_CLK_1MHz,
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = TOP_VAL_FADE,
        .load_mode    = NRF_PWM_LOAD_COMMON,
        .step_mode    = NRF_PWM_STEP_AUTO
    };

    if (!nrfx_pwm_init_check(&m_pwm20)) {
        nrfx_err_t err = nrfx_pwm_init(&m_pwm20, &config20, NULL, NULL);
        if (err != NRFX_SUCCESS) {
            LOG_ERR("PWM20 init failed: %d", err);
            return -EIO;
        }
    } else {
        LOG_INF("PWM20 already initialized");
    }

    if (!nrfx_pwm_init_check(&m_pwm21)) {
        nrfx_err_t err = nrfx_pwm_init(&m_pwm21, &config21, NULL, NULL);
        if (err != NRFX_SUCCESS) {
            LOG_ERR("PWM21 init failed: %d", err);
            return -EIO;
        }
    } else {
        LOG_INF("PWM21 already initialized");
    }

    set_fade_values_common();

    return 0;
}


and with individual
#define TOP_VAL_FADE 250
#define STEP_COUNT_FADE 25

static nrfx_pwm_t m_pwm20 = NRFX_PWM_INSTANCE(20);
static nrfx_pwm_t m_pwm21 = NRFX_PWM_INSTANCE(21);

static nrf_pwm_values_individual_t fade_in_out_values[2 * STEP_COUNT_FADE];
static nrf_pwm_sequence_t led_on_seq;
static nrf_pwm_sequence_t led_off_seq;
static nrf_pwm_values_individual_t stay_off_values[2] = {0};

void set_fade_values_individual(void)
{
    for (uint32_t i = 0; i < STEP_COUNT_FADE; i++)
    {
        uint16_t value = (TOP_VAL_FADE * (i + 1)) / STEP_COUNT_FADE;
        
        // Fade in
        fade_in_out_values[i].channel_0 = value; // Red
        fade_in_out_values[i].channel_1 = value;     // Green
        fade_in_out_values[i].channel_2 = 0;     // Blue
        fade_in_out_values[i].channel_3 = 0;
        
        // Fade out
        fade_in_out_values[STEP_COUNT_FADE + i].channel_0 = TOP_VAL_FADE - value;
        fade_in_out_values[STEP_COUNT_FADE + i].channel_1 = TOP_VAL_FADE - value;
        fade_in_out_values[STEP_COUNT_FADE + i].channel_2 = 0;
        fade_in_out_values[STEP_COUNT_FADE + i].channel_3 = 0;
        LOG_INF("value of fade %d: %d", i, value);
    }
}

void start_pwm_pulse(uint32_t ramp_time_ms)
{    
    // Configure fade in/out sequence
    led_on_seq.values.p_individual = fade_in_out_values;
    led_on_seq.length = NRF_PWM_VALUES_LENGTH(fade_in_out_values);
    led_on_seq.repeats = 160; // All we have to do is update the repeats! Less repeats = faster fade in/out
    led_on_seq.end_delay = 0;
    
    // Configure off sequence
    led_off_seq.values.p_individual = stay_off_values;
    led_off_seq.length = NRF_PWM_VALUES_LENGTH(stay_off_values);
    led_off_seq.repeats = 30;
    led_off_seq.end_delay = 0;
    
    // Start playback
    nrfx_pwm_complex_playback(&m_pwm20, &led_on_seq, &led_off_seq, 1, NRFX_PWM_FLAG_LOOP);
    nrfx_pwm_complex_playback(&m_pwm21, &led_on_seq, &led_off_seq, 1, NRFX_PWM_FLAG_LOOP);
    
    LOG_INF("PWM pulse started");
}

static int ktd2026_init(const struct device *dev) {
    LOG_DBG("LED Softblink init");

    nrfx_pwm_config_t config20 = {
        // .output_pins = { RED_PIN, GREEN_PIN, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .output_pins = { RED_PIN, 0xFF, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .irq_priority = 7,
        .base_clock   = NRF_PWM_CLK_1MHz,
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = RED_PWM_PERIOD_NS,
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_AUTO
    };
    nrfx_pwm_config_t config21 = {
        .output_pins = { GREEN_PIN, 0xFF, 0xFF, 0xFF }, // 0xFF = NRFX_PWM_PIN_NOT_USED
        .irq_priority = 7,
        .base_clock   = NRF_PWM_CLK_1MHz,
        .count_mode   = NRF_PWM_MODE_UP,
        .top_value    = RED_PWM_PERIOD_NS,
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_AUTO
    };

    if (!nrfx_pwm_init_check(&m_pwm20)) {
        nrfx_err_t err = nrfx_pwm_init(&m_pwm20, &config20, NULL, NULL);
        if (err != NRFX_SUCCESS) {
            LOG_ERR("PWM20 init failed: %d", err);
            return -EIO;
        }
    } else {
        LOG_INF("PWM20 already initialized");
    }

    if (!nrfx_pwm_init_check(&m_pwm21)) {
        nrfx_err_t err = nrfx_pwm_init(&m_pwm21, &config21, NULL, NULL);
        if (err != NRFX_SUCCESS) {
            LOG_ERR("PWM21 init failed: %d", err);
            return -EIO;
        }
    } else {
        LOG_INF("PWM21 already initialized");
    }

    set_fade_values_individual();

    return 0;
}


Parents
  • Hi!

    on nrf54l15dk I am able to do pwm using pwm_set_dt command, but it consumes CPU cycles so,

    Could you elaborate on this? You need to call pwm_set_dt() very often to get the desired signal?

  • Yes, when I use pwm_set_dt with different duty cycle, I am able to see different led intensity and when placed inside a while or for loop with changing duty cycle, I am able to get a pulse led for a desired time and speed. 

    Like I am able to set led pulse of 3 sec running for 10 sec or desired time or the loop is true.

    But, using this method for pwm, CPU needs to update and send that command to pin which is power consuming. So, I found a way that in nrf52840, but using nrfx_sequence, I can reduce power consumption and CPU can work on other tasks.

    So, while porting to nrf54l15, I am unable to load off that task to nrfx_sequence. So, I am trying to find a way to work with nrfx_sequence in nrf54l15.

  • Hi Sigurd,

    Here is my dts/binding

    description: Custom binding for an RGB LED controlled by three PWM channels
    
    compatible: "led,nrf54_rgb_pwm"
    
    properties:
      pwms:
        type: phandle-array
        required: true
        description: |
          List of PWM specifiers for the RGB channels.
          The order should be: red, green, blue.
      pwm-names:
        type: string-array
        required: true
        description: |
          List the names of the pwm lines for RGB.


    and my .overlay
    /{
            leds: led {
            compatible = "led,nrf54_rgb_pwm";
            status = "okay";
            pwms = <&pwm20 0 PWM_MSEC(1) PWM_POLARITY_NORMAL>,
                   <&pwm20 1 PWM_MSEC(1) PWM_POLARITY_NORMAL>,
                   <&pwm20 2 PWM_MSEC(1) PWM_POLARITY_NORMAL>;
            pwm-names = "red_pwms", "green_pwms", "blue_pwms";
        };
    
        aliases {
            rgb-led     = &leds;
        };
        
        pwm20_default: pwm20_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 11)>,
                        <NRF_PSEL(PWM_OUT1, 1, 12)>,
                        <NRF_PSEL(PWM_OUT2, 1, 13)>;
            };
        };
        pwm20_sleep: pwm20_sleep {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 11)>,
                        <NRF_PSEL(PWM_OUT1, 1, 12)>,
                        <NRF_PSEL(PWM_OUT2, 1, 13)>;
                low-power-enable;
            };
        };
    };
    
    &pwm20 {
    	status = "okay";
    	pinctrl-0 = <&pwm20_default>;
    	pinctrl-1 = <&pwm20_sleep>;
    	pinctrl-names = "default","sleep";
    };


    I am still unable to pulse the led using nrf_pwm_sequence_t. Can you help me out?

  • Hi!

    Try this main.c 

    /*
     * Copyright (c) 2012-2014 Wind River Systems, Inc.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <stdio.h>
    #include <zephyr/kernel.h>
    #include <zephyr/sys/util.h>
    #include <zephyr/irq.h>
    #include <nrfx_pwm.h>
    #include <hal/nrf_gpio.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(pwm_example, LOG_LEVEL_INF);
    
    /* PWM output pins on PORT 1 for RGB LED */
    #define RED_PIN   NRF_GPIO_PIN_MAP(1, 11)  /* P1.11 */
    #define GREEN_PIN NRF_GPIO_PIN_MAP(1, 12)  /* P1.12 */
    #define BLUE_PIN  NRF_GPIO_PIN_MAP(1, 13)  /* P1.13 */
    
    /* PWM fade parameters */
    #define TOP_VAL_FADE 250
    #define STEP_COUNT_FADE 25
    
    /* PWM instances */
    static nrfx_pwm_t m_pwm20 = NRFX_PWM_INSTANCE(20);
    
    /* Fade in/out sequence values */
    static nrf_pwm_values_individual_t fade_in_out_values[2 * STEP_COUNT_FADE];
    static nrf_pwm_sequence_t led_on_seq;
    static nrf_pwm_sequence_t led_off_seq;
    static nrf_pwm_values_individual_t stay_off_values[2];
    
    static uint16_t fade_value_for_step(uint32_t step_idx)
    {
        const uint32_t steps_per_phase = STEP_COUNT_FADE;
        const uint32_t total_steps = 2U * steps_per_phase;
        step_idx %= total_steps;
    
        uint32_t phase_pos = step_idx % steps_per_phase;
        uint16_t level = (TOP_VAL_FADE * (phase_pos + 1U)) / steps_per_phase;
    
        if (step_idx < steps_per_phase) {
            return level;
        }
    
        return TOP_VAL_FADE - level;
    }
    
    /**
     * @brief Set up fade in/out values.
     * 
     * Creates a smooth fade pattern by calculating incrementing values
     * for fade-in and decrementing values for fade-out.
     */
    void set_fade_values_common(void)
    {
        const uint32_t total_steps = ARRAY_SIZE(fade_in_out_values);
        const uint32_t green_phase = total_steps / 3U;
        const uint32_t blue_phase  = (2U * total_steps) / 3U;
    
        for (uint32_t i = 0; i < total_steps; i++)
        {
            fade_in_out_values[i].channel_0 = fade_value_for_step(i);
            fade_in_out_values[i].channel_1 = fade_value_for_step((i + green_phase) % total_steps);
            fade_in_out_values[i].channel_2 = fade_value_for_step((i + blue_phase) % total_steps);
            fade_in_out_values[i].channel_3 = 0;
    
            LOG_INF("Fade step %d -> R:%d G:%d B:%d",
                    i,
                    fade_in_out_values[i].channel_0,
                    fade_in_out_values[i].channel_1,
                    fade_in_out_values[i].channel_2);
        }
    
        for (size_t i = 0; i < ARRAY_SIZE(stay_off_values); i++) {
            stay_off_values[i].channel_0 = 0;
            stay_off_values[i].channel_1 = 0;
            stay_off_values[i].channel_2 = 0;
            stay_off_values[i].channel_3 = 0;
        }
    }
    
    /**
     * @brief Start PWM pulse with fade in/out effect.
     * 
     * Configures two sequences: one for fading in/out and one for staying off.
     * Uses complex playback to alternate between the sequences in a loop.
     */
    void start_pwm_pulse(void)
    {    
        // Configure fade in/out sequence
        led_on_seq.values.p_individual = fade_in_out_values;
        led_on_seq.length = NRF_PWM_VALUES_LENGTH(fade_in_out_values);
        led_on_seq.repeats = 160; // Less repeats = faster fade in/out
        led_on_seq.end_delay = 0;
        
        // Configure off sequence
        led_off_seq.values.p_individual = stay_off_values;
        led_off_seq.length = NRF_PWM_VALUES_LENGTH(stay_off_values);
        led_off_seq.repeats = 30;
        led_off_seq.end_delay = 0;
        
    
        uint32_t task_addr = nrfx_pwm_complex_playback(&m_pwm20, &led_on_seq, &led_off_seq, 1, NRFX_PWM_FLAG_LOOP);
        if (task_addr != 0) {
            LOG_INF("PWM playback prepared; trigger task at 0x%08x to start", task_addr);
        } else {
            LOG_INF("PWM pulse started with fade in/out effect");
        }
    }
    
    /**
     * @brief Initialize PWM20 for fade effect.
     * 
     * @return 0 on success, negative error code on failure.
     */
    static int pwm_fade_init(void)
    {
        LOG_INF("PWM Fade example init - RGB LED");
    
        nrfx_pwm_config_t config20 = {
            .output_pins = { RED_PIN, GREEN_PIN, BLUE_PIN, NRF_PWM_PIN_NOT_CONNECTED },
            .irq_priority = 7,
            .base_clock   = NRF_PWM_CLK_1MHz,
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = TOP_VAL_FADE,
            .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
    
        if (!nrfx_pwm_init_check(&m_pwm20)) {
            nrfx_err_t err = nrfx_pwm_init(&m_pwm20, &config20, NULL, NULL);
            if (err != NRFX_SUCCESS) {
                LOG_ERR("PWM20 init failed: %d", err);
                return -EIO;
            }
            LOG_INF("PWM20 initialized successfully");
        } else {
            LOG_INF("PWM20 already initialized");
        }
    
        set_fade_values_common();
    
        return 0;
    }
    
    
    int main(void)
    {
    	int ret;
    	
    	LOG_INF("PWM Fade Example for nRF54L15! %s\n", CONFIG_BOARD_TARGET);
    	LOG_INF("Using PWM20 instance with fade in/out effect on PORT 1 pins\n");
    
    	/* Initialize PWM with fade configuration */
    	ret = pwm_fade_init();
    	if (ret != 0) {
    		LOG_ERR("PWM fade initialization failed: %d", ret);
    		return ret;
    	}
    	
    	/* Start PWM pulse with fade effect */
    	start_pwm_pulse();
    	
    	LOG_INF("PWM fade effect started on RGB LED:\n");
    	LOG_INF("  Red   (P1.11): Breathing LED effect\n");
    	LOG_INF("  Green (P1.12): Breathing LED effect\n");
    	LOG_INF("  Blue  (P1.13): Breathing LED effect\n");
    	LOG_INF("Running indefinitely...\n");
    
    
    	while (1) {
    		k_sleep(K_SECONDS(100));
    	}
    
    	return 0;
    }

  • Hi Sigurd,

    Thank you for the response.

    I figured out the problem, the code and everything is correct except for the overlay.

    The documents says that the nrfx and zephyr does not do together, so if using nrfx do not use pwm node.

    This is my updated .overlay and it works

    &pwm20 {
    	status = "disabled";
    	pinctrl-0 = <&pwm20_default>;
    	pinctrl-1 = <&pwm20_sleep>;
    	pinctrl-names = "default","sleep";
    };
    
    /{
    leds: led {
            compatible = "led,nrf54_rgb_pwm";
            status = "okay";
            pwms = <&pwm20 0 PWM_MSEC(1) PWM_POLARITY_NORMAL>,
                   <&pwm20 1 PWM_MSEC(1) PWM_POLARITY_NORMAL>,
                   <&pwm20 2 PWM_MSEC(1) PWM_POLARITY_NORMAL>;
            pwm-names = "red_pwms", "green_pwms", "blue_pwms";
        };
    };
    
    &pinctrl{
    pwm20_default: pwm20_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 10)>,
                        <NRF_PSEL(PWM_OUT1, 1, 8)>,
                        <NRF_PSEL(PWM_OUT2, 1, 13)>;
            };
        };
        pwm20_sleep: pwm20_sleep {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 10)>,
                        <NRF_PSEL(PWM_OUT1, 1, 8)>,
                        <NRF_PSEL(PWM_OUT2, 1, 13)>;
                low-power-enable;
            };
        };
    };



    and .conf
    CONFIG_PWM=n
    CONFIG_PWM_NRFX=n
    CONFIG_NRFX_PWM20=y

  • Hi Sigurd,

    There is one thing which is odd, that when tried to provide 0 duty cycle via zephyr method through

    pwm_set_dt(&cfg->red, PWM_PERIOD_NS, 0);
    pwm_set_dt(&cfg->green, PWM_PERIOD_NS, 0);
    pwm_set_dt(&cfg->blue, PWM_PERIOD_NS, 0);

    there is complete shutdown or turn off of led;

    But when provided 0 duty cycle via nrfx_pwm method through

    static nrf_pwm_values_individual_t rgb_values;
    static nrf_pwm_sequence_t const rgb_seq = {
        .values.p_individual = &rgb_values,
        .length = sizeof(rgb_values) / sizeof(uint16_t),
        .repeats = 0,
        .end_delay = 0,
    };
    rgb_values.channel_0 = PWM_ACTIVE(0);
    rgb_values.channel_1 = PWM_ACTIVE(0);
    rgb_values.channel_2 = PWM_ACTIVE(0);
    rgb_values.channel_3 = PWM_ACTIVE(0);

    I can see a faint red led at all times, during pulsing red or other led. Even tried providing 0 duty cycle still can see faint red led.

    So, do you have any suggestion to solve this issue or is there any other firmware workaround for it?

    Thanks.
Reply
  • Hi Sigurd,

    There is one thing which is odd, that when tried to provide 0 duty cycle via zephyr method through

    pwm_set_dt(&cfg->red, PWM_PERIOD_NS, 0);
    pwm_set_dt(&cfg->green, PWM_PERIOD_NS, 0);
    pwm_set_dt(&cfg->blue, PWM_PERIOD_NS, 0);

    there is complete shutdown or turn off of led;

    But when provided 0 duty cycle via nrfx_pwm method through

    static nrf_pwm_values_individual_t rgb_values;
    static nrf_pwm_sequence_t const rgb_seq = {
        .values.p_individual = &rgb_values,
        .length = sizeof(rgb_values) / sizeof(uint16_t),
        .repeats = 0,
        .end_delay = 0,
    };
    rgb_values.channel_0 = PWM_ACTIVE(0);
    rgb_values.channel_1 = PWM_ACTIVE(0);
    rgb_values.channel_2 = PWM_ACTIVE(0);
    rgb_values.channel_3 = PWM_ACTIVE(0);

    I can see a faint red led at all times, during pulsing red or other led. Even tried providing 0 duty cycle still can see faint red led.

    So, do you have any suggestion to solve this issue or is there any other firmware workaround for it?

    Thanks.
Children
No Data
Related