Produce PWM sequence in NCS v2.9.1 using nrfx driver on nRF52840

Hi,

I want to produce  a pulse with 4 seconds length from p0.25, but i am failed.

#include <zephyr/sys/printk.h>
#include <nrfx_pwm.h>
#include <hal/nrf_gpio.h>
static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(0);
static volatile uint8_t current_seq = 0;
#define motor_pwr_ctrl      NRF_GPIO_PIN_MAP(0, 25)

// PWM sequence
static nrf_pwm_values_individual_t seq_data[4] = {
    {.channel_0 = 250},   // 20% duty (250/1250)
    {.channel_0 = 625},   // 50% duty
    {.channel_0 = 875},   // 70% duty
    {.channel_0 = 1125}   // 90% duty
};

static nrf_pwm_sequence_t sequences[4] = {
    {   // sequence0
        .values.p_individual = &seq_data[0],
        .length = NRF_PWM_VALUES_LENGTH(seq_data[0]),
        .repeats = 0, 
        .end_delay = 0
    },
    {   // sequence1
        .values.p_individual = &seq_data[1],
        .length = NRF_PWM_VALUES_LENGTH(seq_data[1]),
        .repeats = 0,
        .end_delay = 0
    },
    {   // sequence2
        .values.p_individual = &seq_data[2],
        .length = NRF_PWM_VALUES_LENGTH(seq_data[2]),
        .repeats = 0,
        .end_delay = 0
    },
    {   // sequence3
        .values.p_individual = &seq_data[3],
        .length = NRF_PWM_VALUES_LENGTH(seq_data[3]),
        .repeats = 0,
        .end_delay = 0
    }
};
// PWM handler
static void pwm_handler(nrfx_pwm_evt_type_t event_type, void* pContext)
{
    if (event_type == NRFX_PWM_EVT_FINISHED) {
        if (current_seq < 4) {
            // play next sequence
            current_seq++;
            nrfx_pwm_simple_playback(&m_pwm, &sequences[current_seq], 100, NRFX_PWM_FLAG_STOP); //repeat 100 times =1 second
            printk("pwm_handler,current_seq=%d.\n", current_seq);
        } else {
            //stop and relese pwm
            nrfx_pwm_stop(&m_pwm, false);
            nrfx_pwm_uninit(&m_pwm);
            printk("nrfx_pwm_uninit.\n");
        }
    }
}
// PWM initial
void init_pwm(void)
{
    nrfx_pwm_config_t config = {
        .output_pins = {
            motor_pwr_ctrl,  // channel 0
            0xff,   //not used
            0xff,
            0xff
        },
        .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
        .base_clock = NRF_PWM_CLK_125kHz,
        .count_mode = NRF_PWM_MODE_UP,
        .top_value = 1250,           // 10ms (1250/125kHz)
        .load_mode = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode = NRF_PWM_STEP_AUTO
    };

    nrfx_pwm_init(&m_pwm, &config, (nrfx_pwm_handler_t)pwm_handler, NULL);
    current_seq = 0;
   
    //play the sequence0 
    nrfx_pwm_simple_playback(&m_pwm, &sequences[0], 100, NRFX_PWM_FLAG_STOP);//repeat 100 times =1 second
}
void PWM_Go(void)
{
#if defined(__ZEPHYR__)
    IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_PWM0), IRQ_PRIO_LOWEST, pwm_handler, 0, 0);
#endif
    init_pwm();
}
void main(void)
{
    ... ...
    PWM_Go();
    while (1) {
        k_sleep(K_FOREVER); 
    }
}
prj.conf:
CONFIG_NRFX_PWM0=y
Next is the actual pulse:
Parents
  • Hi,

    I do not imediately see the problem as you play the sequence 100 times, and that works. Based on that and taht you get 1 second, it seems nrfx_pwm_simple_playback() is only called once from the init functions, but not from the pwm_handler()? Can you confirm by logging or debugging?

    PS: There are samples here that can be used as a reference: https://github.com/zephyrproject-rtos/hal_nordic/blob/13ac55b5b52c905642e9c54f069109d188aa5840/nrfx/samples/src/nrfx_pwm 

  • No,  From the log you can see nrfx_pwm_simple_playback is called for four times,

    p0.25 output sequences[[0] for 100 times only,

    But it don`t output sequences[[1~3] .

  • Hi,

    The finished event comes after the first secuence, so you are effectively starting the new before the previous has completed, and this fails. The driver lacks a good way to solve this with this approach, so I woudl instead suggest that you alternate between sequeces as shown in this sample (and update in between). This also has the benefit that interrupt latency does not matter that much, as you prepare for the switch before it actually happens.

    PS: there were some issues with this test code that prevented it from running that was easily catched with asserts, so I on a generic basis recommend testing with asserts (CONFIG_ASSERT=y). With that addressed, I the modified code I tested looks like this:

    #include <zephyr/sys/printk.h>
    #include <nrfx_pwm.h>
    #include <hal/nrf_gpio.h>
    #include <zephyr/kernel.h>
    
    static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(0);
    static volatile uint8_t current_seq = 0;
    #define motor_pwr_ctrl      NRF_GPIO_PIN_MAP(0, 25)
    
    // PWM sequence
    nrf_pwm_values_individual_t seq_data[4] = {
        {.channel_0 = 250},   // 20% duty (250/1250)
        {.channel_0 = 625},   // 50% duty
        {.channel_0 = 875},   // 70% duty
        {.channel_0 = 1125}   // 90% duty
    };
    
    static nrf_pwm_sequence_t sequences[4] = {
        {   // sequence0
            .values.p_individual = &seq_data[0],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[0]),
            .repeats = 0, 
            .end_delay = 0
        },
        {   // sequence1
            .values.p_individual = &seq_data[1],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[1]),
            .repeats = 0,
            .end_delay = 0
        },
        {   // sequence2
            .values.p_individual = &seq_data[2],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[2]),
            .repeats = 0,
            .end_delay = 0
        },
        {   // sequence3
            .values.p_individual = &seq_data[3],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[3]),
            .repeats = 0,
            .end_delay = 0
        }
    };
    // PWM handler
    static void pwm_handler(nrfx_pwm_evt_type_t event_type, void* pContext)
    {
        static int count = 0;
        if (event_type != 0) {
            printk("count: %i, evt: %i\n", count, event_type);
        }
        if (event_type == NRFX_PWM_EVT_FINISHED) {
        	current_seq++;
            if (current_seq < 4) {
    	    printk("pwm_handler,current_seq=%d.\n", current_seq);
                nrfx_pwm_simple_playback(&m_pwm, &sequences[current_seq], 100, NRFX_PWM_FLAG_STOP);//repeat 100 times =1 second
            } else {
                //stop and relese pwm
                nrfx_pwm_stop(&m_pwm, false);
                nrfx_pwm_uninit(&m_pwm);
                printk("nrfx_pwm_uninit.\n");
            }
        }
    }
    // PWM initial
    void init_pwm(void)
    {
        nrfx_pwm_config_t config = {
            .output_pins = {
                motor_pwr_ctrl, // channel 0
            },
            .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .base_clock = NRF_PWM_CLK_125kHz,
            .count_mode = NRF_PWM_MODE_UP,
            .top_value = 1250,           // 10ms (1250/125kHz)
            .load_mode = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode = NRF_PWM_STEP_AUTO
        };
    
        nrfx_pwm_init(&m_pwm, &config, (nrfx_pwm_handler_t)pwm_handler, NULL);
        current_seq = 0;
    
        //play the sequence0 
        nrfx_pwm_simple_playback(&m_pwm, &sequences[0], 100, NRFX_PWM_FLAG_STOP); //repeat 100 times =1 second
    }
    void PWM_Go(void)
    {
    #if defined(__ZEPHYR__)
        IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_PWM0), IRQ_PRIO_LOWEST, pwm_handler, 0, 0);
    #endif
        init_pwm();
    }
    int main(void)
    {
        PWM_Go();
        while (1) {
            k_sleep(K_FOREVER); 
        }
    
        return 0;
    }
    

Reply
  • Hi,

    The finished event comes after the first secuence, so you are effectively starting the new before the previous has completed, and this fails. The driver lacks a good way to solve this with this approach, so I woudl instead suggest that you alternate between sequeces as shown in this sample (and update in between). This also has the benefit that interrupt latency does not matter that much, as you prepare for the switch before it actually happens.

    PS: there were some issues with this test code that prevented it from running that was easily catched with asserts, so I on a generic basis recommend testing with asserts (CONFIG_ASSERT=y). With that addressed, I the modified code I tested looks like this:

    #include <zephyr/sys/printk.h>
    #include <nrfx_pwm.h>
    #include <hal/nrf_gpio.h>
    #include <zephyr/kernel.h>
    
    static nrfx_pwm_t m_pwm = NRFX_PWM_INSTANCE(0);
    static volatile uint8_t current_seq = 0;
    #define motor_pwr_ctrl      NRF_GPIO_PIN_MAP(0, 25)
    
    // PWM sequence
    nrf_pwm_values_individual_t seq_data[4] = {
        {.channel_0 = 250},   // 20% duty (250/1250)
        {.channel_0 = 625},   // 50% duty
        {.channel_0 = 875},   // 70% duty
        {.channel_0 = 1125}   // 90% duty
    };
    
    static nrf_pwm_sequence_t sequences[4] = {
        {   // sequence0
            .values.p_individual = &seq_data[0],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[0]),
            .repeats = 0, 
            .end_delay = 0
        },
        {   // sequence1
            .values.p_individual = &seq_data[1],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[1]),
            .repeats = 0,
            .end_delay = 0
        },
        {   // sequence2
            .values.p_individual = &seq_data[2],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[2]),
            .repeats = 0,
            .end_delay = 0
        },
        {   // sequence3
            .values.p_individual = &seq_data[3],
            .length = NRF_PWM_VALUES_LENGTH(seq_data[3]),
            .repeats = 0,
            .end_delay = 0
        }
    };
    // PWM handler
    static void pwm_handler(nrfx_pwm_evt_type_t event_type, void* pContext)
    {
        static int count = 0;
        if (event_type != 0) {
            printk("count: %i, evt: %i\n", count, event_type);
        }
        if (event_type == NRFX_PWM_EVT_FINISHED) {
        	current_seq++;
            if (current_seq < 4) {
    	    printk("pwm_handler,current_seq=%d.\n", current_seq);
                nrfx_pwm_simple_playback(&m_pwm, &sequences[current_seq], 100, NRFX_PWM_FLAG_STOP);//repeat 100 times =1 second
            } else {
                //stop and relese pwm
                nrfx_pwm_stop(&m_pwm, false);
                nrfx_pwm_uninit(&m_pwm);
                printk("nrfx_pwm_uninit.\n");
            }
        }
    }
    // PWM initial
    void init_pwm(void)
    {
        nrfx_pwm_config_t config = {
            .output_pins = {
                motor_pwr_ctrl, // channel 0
            },
            .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .base_clock = NRF_PWM_CLK_125kHz,
            .count_mode = NRF_PWM_MODE_UP,
            .top_value = 1250,           // 10ms (1250/125kHz)
            .load_mode = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode = NRF_PWM_STEP_AUTO
        };
    
        nrfx_pwm_init(&m_pwm, &config, (nrfx_pwm_handler_t)pwm_handler, NULL);
        current_seq = 0;
    
        //play the sequence0 
        nrfx_pwm_simple_playback(&m_pwm, &sequences[0], 100, NRFX_PWM_FLAG_STOP); //repeat 100 times =1 second
    }
    void PWM_Go(void)
    {
    #if defined(__ZEPHYR__)
        IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_PWM0), IRQ_PRIO_LOWEST, pwm_handler, 0, 0);
    #endif
        init_pwm();
    }
    int main(void)
    {
        PWM_Go();
        while (1) {
            k_sleep(K_FOREVER); 
        }
    
        return 0;
    }
    

Children
No Data
Related