Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nRF5 SDK15.0 - PWM Driver: Just set a fixed duty cycle?

The PWM Driver has all sorts of fancy stuff for dealing with clever sequences - but I can't see anything to just set a simple, steady, fixed duty cycle!

How does one just set a fixed duty cycle?

What I actually want is to be able to fade an LED up to a certain brightness; so what I really want is to be able to specify an increasing sequence, and then a steady duty cycle to last indefinitely once the fade-up sequence completes.

Is there a simple way to do that?

  • Hello,

    I would agree to some degree of what you are saying. The PWM driver example is good for fancy PWM LEDs, but if you want a simple PWM with a fixed period and a dimmed LED that is easily controllable, it might be easier to use the PPI module with a timer.

    Attached is a simple PPI example that does this. If you run it on an nRF52DK, it shouldn't need any modifications. It should dim 2 LEDs in a pattern.

    The PPI setup might look a bit scary, but the function that is actually controlling the LEDs duty cycle is controlled from the main() function. Note that for simplicity, the example uses nrf_delay_ms(), which is not a good approach, power consumption vise. 

    ppi_double_channel_pwm.zip

    Note that the pwm[0/1]_set_duty_cycle() doesn't need to use the sin_scaled() function. You can set it to any value between 1 and 1024 (TIMER_RELOAD), which is the number of ticks in the current setup.

    You can change the TIMER_RELOAD to change the period of your PWM signal.

    In the zip-folder attached, only a Keil project is included. But you should be able to change any of the main.c files from the projects in SDK\examples\periperal with this one, if you want to use a different IDE.

    Let me know if anything is unclear, or if you have any questions regarding the PPI, or if you want to use the PWM driver instead of this.

    Best regards,

    Edvin

  • OK -  thanks.

    So, for completeness, is it possible to use the PWM peripheral to give a simple fixed duty cycle that will continue indefinitely?

  • Yes. This is basically what the pwm driver does.

    See the attached version of demo3 in examples\peripheral\pwm_driver.

    If you replace demo3() with this, it will have the LED in a dimmed position.

    static void demo3(void)
    {
        NRF_LOG_INFO("Demo 3");
    
        /*
         * This demo uses only one channel, which is reflected on LED 1.
         * The LED blinks three times (200 ms on, 200 ms off), then it stays off
         * for one second.
         * This scheme is performed three times before the peripheral is stopped.
         */
    
        nrf_drv_pwm_config_t const config0 =
        {
            .output_pins =
            {
                BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
                NRF_DRV_PWM_PIN_NOT_USED,             // channel 1
                NRF_DRV_PWM_PIN_NOT_USED,             // channel 2
                NRF_DRV_PWM_PIN_NOT_USED,             // channel 3
            },
            .irq_priority = APP_IRQ_PRIORITY_LOWEST,
            .base_clock   = NRF_PWM_CLK_2MHz,//NRF_PWM_CLK_125kHz,
            .count_mode   = NRF_PWM_MODE_UP,
            .top_value    = 32768, // = 0x8000
            .load_mode    = NRF_PWM_LOAD_COMMON,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
        APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
        m_used |= USED_PWM(0);
    
        // This array cannot be allocated on stack (hence "static") and it must
        // be in RAM (hence no "const", though its content is not changed).
        static uint16_t /*const*/ seq_values[] =
        {
            0x3000,
    //        0x2000,
    //        0x4000,
    //        0x6000,
    //        0x7FFF,
    //        0x8000
        };
        nrf_pwm_sequence_t const seq =
        {
            .values.p_common = seq_values,
            .length          = NRF_PWM_VALUES_LENGTH(seq_values),
            .repeats         = 0,
            .end_delay       = 0
        };
    
        (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 3, NRF_DRV_PWM_FLAG_LOOP);
    }

    You see that if you remove all but 1 seq_values[], this value will be used. 

    As you see, you can adjust the frequency of the base clock, which will change the tick rate. The seq_values[] value will be the number of ticks before it changes.

    The .top_value is the number of ticks before the clock will reset the counter (.base clock *  .top_value = pwm period).

    If you look in the description of the sec_values, you see that the first bit will be the polarity of the PWM signal, as described in nrf_pwm.h, line 248 (in SDK15.0.0).

    The most significant bit (bit 15) will decide the polarity of the output, while bit 0-14 will be the duty cycle.

    I changed nrf_pwm_sequence_t.repeats to 0, but changed the flag to NRF_PWM_FLAG_LOOP, so that it will repeat the signal forever.

    Best regards,

    Edvin

  • I changed nrf_pwm_sequence_t.repeats to 0, but changed the flag to NRF_PWM_FLAG_LOOP, so that it will repeat the signal forever

    I think that's the key piece of information that's missing from all the documentation!

    I did wonder if there was an option like this, but could not find it anywhere - so what I did was to set repeats to UINT16_MAX. With my other settings, this meant that it only needed attention every 20 minutes or so.

    With repeats set to zero, and the LOOP flag set, does this mean that a NRF_DRV_PWM_EVT_FINISHED event is never generated?

  • So what's the difference between the nrf_pwm_sequence_t.repeats setting, and the playback_count parameter in nrfx_pwm_simple_playback() ?

Related