This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nrf52840 PWM Driver update duty cycle

Hello,

SDK: 15.3.0

Hardware: nRF52840-DK

I am attempting to make a simple dimmer with 2 pins/LEDs. I have used the following post.

https://devzone.nordicsemi.com/f/nordic-q-a/38607/nrf5-sdk15-0---pwm-driver-just-set-a-fixed-duty-cycle

https://devzone.nordicsemi.com/f/nordic-q-a/39314/pwm-update_function/152441#152441

I am attempting to do a very similar task, but software may need to adjust the duty cycle (sequence value) at any point to adjust the brightness of the LEDs. I have made several attempts. in all attempts I am using nrf_delay_ms(2000) to delay the program calls, and act as a time delay between when the program would call different update functions. pwm would always be on, and the LED would be off at seq_value = 0, and 100% Duty Cycle (Fully On) at seq_value = .top_value

Q1: There are various built in update functions like nrfx_pwm_sequence_values_update, both legacy, and not. Am I using the correct function to complete this goal?

Q2: Which method that I have attempted is the preferred method, and is there an alternative method that may be better?

Attempt 1: fails to build due to the 3rd param of nrfx_pwm_sequence_values_update(&m_pwm0,0, pwm_count); not matching

static void update_LED(uint16_t duty_cycle)
{
    //Attempt 1
    uint16_t pwm_count = 32768 *(duty_cycle / 100);
    nrfx_pwm_sequence_values_update(&m_pwm0,0, pwm_count);
}

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);
}

int main(void)
{
...
    demo3();
    nrf_delay_ms(2000);
    update_LED(100);
    nrf_delay_ms(2000);
    update_LED(25);
...
}

Attempt 2: still fails to build due to the 3rd param of nrfx_pwm_sequence_values_update(&m_pwm0,0, pwm_count); not matching

static void update_LED(uint16_t duty_cycle)
{ 
    //Attempt 2
    uint16_t pwm_count = 32768 *(duty_cycle / 100);
    nrf_pwm_values_common_t const *p_pwm_count = pwm_count;
    nrfx_pwm_sequence_values_update(&m_pwm0,0, p_pwm_count);
}

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);

}

int main(void)
{
...
    demo3();
    nrf_delay_ms(2000);
    update_LED(100);
    nrf_delay_ms(2000);
    update_LED(25);
...
}

Attempt 3: Build and runs, but not as expected, it is as if the update_LED function has updated the value at the same time, and the two duty cycles are overlapping. The result is a 2 second delay (expected), with a rapid blinking LED (not expected), followed by LED off.

nrf_pwm_values_individual_t seq_values_test[] = { 16384};
nrf_pwm_sequence_t const seq_test =
{
     .values.p_common = seq_values_test,
     .length          = NRF_PWM_VALUES_LENGTH(seq_values_test),
     .repeats         = 0,
     .end_delay       = 0
};


static void update_LED(uint16_t duty_cycle)
{
    //Attempt 3
    seq_values_test->channel_0 = pwm_count;
    nrf_drv_pwm_simple_playback(&m_pwm0, &seq_test, 1, NRF_DRV_PWM_FLAG_LOOP);
}

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);
}

int main(void)
{
...
    demo3();
    nrf_delay_ms(2000);
    update_LED(100);
    nrf_delay_ms(2000);
    update_LED(25);
...
}

Parents
  • Hi,

    I printed the duty cycle from the update_pwm function. It seems the equation output returned 0 a lot of times. 
    This is because dividing two integers where duty_cycle is between 0 and 100, with 100 will result in 0 (when duty_cycle is less than 100).
    Either use float/double or do as my example where I devide PWM_TOP with 100 first, then multiply with duty_cycle.

    Could you try me code example attached and see if this suit your needs?

    /**
     * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     * @defgroup pwm_example_main main.c
     * @{
     * @ingroup pwm_example
     *
     * @brief PWM Example Application main file.
     *
     * This file contains the source code for a sample application using PWM.
     */
    
    #include <stdio.h>
    #include <string.h>
    #include "nrf_drv_pwm.h"
    #include "app_util_platform.h"
    #include "app_error.h"
    #include "boards.h"
    #include "bsp.h"
    #include "app_timer.h"
    #include "nrf_drv_clock.h"
    
    #include "nrf_delay.h"
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define PWM_TOP_VALUE 1000
    
    static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
    
    static nrf_pwm_values_individual_t m_LED_seq_values;
    static nrf_pwm_sequence_t const m_LED_seq =
    {
        .values.p_individual = &m_LED_seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(m_LED_seq_values),
        .repeats         = 0,
        .end_delay       = 0
    };
    
    static void update_LED(uint8_t duty_cycle)
    {
        uint16_t pwm_count = PWM_TOP_VALUE/100;
        if(duty_cycle <= 100)
            {
                pwm_count = pwm_count * duty_cycle;
            }
        else
        {
            NRF_LOG_ERROR("Duty cycle input not valid");
        }
        
        //nrf_pwm_values_common_t const *p_pwm_count = pwm_count;
        //nrfx_pwm_sequence_values_update(&m_pwm0,0, p_pwm_count);
        NRF_LOG_INFO("pwm_count: %d", pwm_count);
        m_LED_seq_values.channel_0 = pwm_count;
        //nrf_drv_pwm_simple_playback(&m_pwm0, &m_LED_seq, 1, NRF_DRV_PWM_FLAG_LOOP);
    }
    
    
    static void single_LED_dimmer_init(void)
    {
        NRF_LOG_INFO("LED dimmer init");
    
        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,  //< Interrupt priority.
            .base_clock   = NRF_PWM_CLK_2MHz,         //< Base clock frequency.
            .count_mode   = NRF_PWM_MODE_UP,          //< Operating mode of the pulse generator counter.
            .top_value    = PWM_TOP_VALUE,            //< Value up to which the pulse generator counter counts.
            .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,  //< Mode of loading sequence data from RAM.
            .step_mode    = NRF_PWM_STEP_AUTO         //< Mode of advancing the active sequence.
        };
        
        m_LED_seq_values.channel_0 = PWM_TOP_VALUE; // Initially set to 100 %
        APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
        nrf_drv_pwm_simple_playback(&m_pwm0, &m_LED_seq, 1, NRF_DRV_PWM_FLAG_LOOP);
    }
    
    
    int main(void)
    {
        APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
    
        NRF_LOG_INFO("PWM example started.");
    
        // Start with Demo 1, then switch to another one when the user presses
        // button 1 or button 2 (see the 'bsp_evt_handler' function).
    
        single_LED_dimmer_init();
        nrf_delay_ms(2000);
        update_LED(10);
        nrf_delay_ms(2000);
        update_LED(50);
        nrf_delay_ms(2000);
        update_LED(100);
        nrf_delay_ms(2000);
        update_LED(0);
    
    
        for (;;)
        {
            // Wait for an event.
            __WFE();
    
            // Clear the event register.
            __SEV();
            __WFE();
    
            NRF_LOG_FLUSH();
        }
    }
    
    
    /** @} */
    

    Best regards,
    Håkon

Reply
  • Hi,

    I printed the duty cycle from the update_pwm function. It seems the equation output returned 0 a lot of times. 
    This is because dividing two integers where duty_cycle is between 0 and 100, with 100 will result in 0 (when duty_cycle is less than 100).
    Either use float/double or do as my example where I devide PWM_TOP with 100 first, then multiply with duty_cycle.

    Could you try me code example attached and see if this suit your needs?

    /**
     * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     * @defgroup pwm_example_main main.c
     * @{
     * @ingroup pwm_example
     *
     * @brief PWM Example Application main file.
     *
     * This file contains the source code for a sample application using PWM.
     */
    
    #include <stdio.h>
    #include <string.h>
    #include "nrf_drv_pwm.h"
    #include "app_util_platform.h"
    #include "app_error.h"
    #include "boards.h"
    #include "bsp.h"
    #include "app_timer.h"
    #include "nrf_drv_clock.h"
    
    #include "nrf_delay.h"
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define PWM_TOP_VALUE 1000
    
    static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
    
    static nrf_pwm_values_individual_t m_LED_seq_values;
    static nrf_pwm_sequence_t const m_LED_seq =
    {
        .values.p_individual = &m_LED_seq_values,
        .length          = NRF_PWM_VALUES_LENGTH(m_LED_seq_values),
        .repeats         = 0,
        .end_delay       = 0
    };
    
    static void update_LED(uint8_t duty_cycle)
    {
        uint16_t pwm_count = PWM_TOP_VALUE/100;
        if(duty_cycle <= 100)
            {
                pwm_count = pwm_count * duty_cycle;
            }
        else
        {
            NRF_LOG_ERROR("Duty cycle input not valid");
        }
        
        //nrf_pwm_values_common_t const *p_pwm_count = pwm_count;
        //nrfx_pwm_sequence_values_update(&m_pwm0,0, p_pwm_count);
        NRF_LOG_INFO("pwm_count: %d", pwm_count);
        m_LED_seq_values.channel_0 = pwm_count;
        //nrf_drv_pwm_simple_playback(&m_pwm0, &m_LED_seq, 1, NRF_DRV_PWM_FLAG_LOOP);
    }
    
    
    static void single_LED_dimmer_init(void)
    {
        NRF_LOG_INFO("LED dimmer init");
    
        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,  //< Interrupt priority.
            .base_clock   = NRF_PWM_CLK_2MHz,         //< Base clock frequency.
            .count_mode   = NRF_PWM_MODE_UP,          //< Operating mode of the pulse generator counter.
            .top_value    = PWM_TOP_VALUE,            //< Value up to which the pulse generator counter counts.
            .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,  //< Mode of loading sequence data from RAM.
            .step_mode    = NRF_PWM_STEP_AUTO         //< Mode of advancing the active sequence.
        };
        
        m_LED_seq_values.channel_0 = PWM_TOP_VALUE; // Initially set to 100 %
        APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
        nrf_drv_pwm_simple_playback(&m_pwm0, &m_LED_seq, 1, NRF_DRV_PWM_FLAG_LOOP);
    }
    
    
    int main(void)
    {
        APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
    
        NRF_LOG_INFO("PWM example started.");
    
        // Start with Demo 1, then switch to another one when the user presses
        // button 1 or button 2 (see the 'bsp_evt_handler' function).
    
        single_LED_dimmer_init();
        nrf_delay_ms(2000);
        update_LED(10);
        nrf_delay_ms(2000);
        update_LED(50);
        nrf_delay_ms(2000);
        update_LED(100);
        nrf_delay_ms(2000);
        update_LED(0);
    
    
        for (;;)
        {
            // Wait for an event.
            __WFE();
    
            // Clear the event register.
            __SEV();
            __WFE();
    
            NRF_LOG_FLUSH();
        }
    }
    
    
    /** @} */
    

    Best regards,
    Håkon

Children
No Data
Related