Start PWMs synchronous

Hello there,

I have configured the 4 PWM peripherals to generate a certain waveform. I need to start all 4 pwm peripherals simultaneous.

I thought of achieving this through usage of the PPI + EGU.
EGU channel 0 event triggers EGU channel 1 and channel 2 tasks.

EGU channel 1 event triggers PWM0/1 SEQSTART tasks.

EGU channel 2 event triggers PWM2/3 SEQSTART tasks.

This is written in the code below:

static void setup_ppi(nrf_ppi_channel_t *channel, uint32_t event, uint32_t task1, uint32_t task2)
{
    nrfx_err_t err = nrfx_ppi_channel_alloc(channel);
    assert(err == NRFX_SUCCESS);
    err = nrfx_ppi_channel_assign(*channel, event, task1);
    assert(err == NRFX_SUCCESS);
    err = nrfx_ppi_channel_fork_assign(*channel, task2);
    assert(err == NRFX_SUCCESS);
    err = nrfx_ppi_channel_enable(*channel);
    assert(err == NRFX_SUCCESS);
}

static void start_pwms(const nrfx_pwm_t *pwms)
{
    nrf_ppi_channel_t channel[3];

    setup_ppi(&channel[0], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0),
              nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER1),
              nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER2));
    setup_ppi(&channel[1], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED1),
              nrfx_pwm_task_address_get(&pwms[0], NRF_PWM_TASK_SEQSTART1),
              nrfx_pwm_task_address_get(&pwms[1], NRF_PWM_TASK_SEQSTART1));
    setup_ppi(&channel[2], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED2),
              nrfx_pwm_task_address_get(&pwms[2], NRF_PWM_TASK_SEQSTART1),
              nrfx_pwm_task_address_get(&pwms[3], NRF_PWM_TASK_SEQSTART1));

    nrf_egu_task_trigger(NRF_EGU0, NRF_EGU_TASK_TRIGGER0);

    for (size_t k = 0; k < ARRAY_SIZE(channel); k++) {
        nrfx_err_t err = nrfx_ppi_channel_free(channel[k]);
        assert(NRFX_SUCCESS == err);
    }
}

The following 4 signals should rise at the same time, however they are 1.85 us out of sync.



Triggering the PWM SEQSTART tasks manually has the same result:

    nrf_pwm_task_trigger(NRF_PWM0, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM1, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM3, NRF_PWM_TASK_SEQSTART1);

Strangely enough, if I reverse the order in which I start the PWMS:

    nrf_pwm_task_trigger(NRF_PWM3, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM1, NRF_PWM_TASK_SEQSTART1);
    nrf_pwm_task_trigger(NRF_PWM0, NRF_PWM_TASK_SEQSTART1);

The PWM signals are still generated in order PWM0->PWM3



Can anyone explain this? Is there no way to start the PWMs at the same time?

I am using NCS V2.5.0.

Parents
  • Hi!

    Do you have different duty cycles on these 4 signals? It could be that they started at the same time, but what you see is just a offset due to different duty cycles?

    If this is not the case, then please provide more info about how you have configured the PWM sequences. A example that reproduces the issue would be helpful.

  • I have configured the same duty cycle (0xFFFF).
    I have included a snippet below. The actual code makes a more elaborate pattern, but I removed that for simplicity.

    #include "pwm_controller.h"
    
    #include <zephyr/kernel.h>
    #include <assert.h>
    
    #include <nrfx_pwm.h>
    #include <nrfx_ppi.h>
    #include <nrfx_egu.h>
    
    #define NUM_AVAIL_PWMS 4
    #define NRFX_PWM_PIN_NOT_USED (0xff)
    
    typedef struct {
        nrf_pwm_values_wave_form_t pwm_values[NUM_AVAIL_PWMS][STIMENGINE_PATTERN_STEPS_MAX];
        struct k_event event_done;
    } pwm_controller_data_t;
    
    typedef struct {
        nrfx_pwm_t pwm_instance[NUM_AVAIL_PWMS];
    } pwm_controller_cfg_t;
    
    static pwm_controller_data_t pwm_controller_data;
    static pwm_controller_cfg_t pwm_controller_config = {
        .pwm_instance = { NRFX_PWM_INSTANCE(0), NRFX_PWM_INSTANCE(1), NRFX_PWM_INSTANCE(2), NRFX_PWM_INSTANCE(3), },
    };
    
    void pwm_controller_init(void)
    {
        IRQ_DIRECT_CONNECT(PWM0_IRQn, 0, nrfx_pwm_0_irq_handler, 0);
        IRQ_DIRECT_CONNECT(PWM1_IRQn, 0, nrfx_pwm_1_irq_handler, 0);
        IRQ_DIRECT_CONNECT(PWM2_IRQn, 0, nrfx_pwm_2_irq_handler, 0);
        IRQ_DIRECT_CONNECT(PWM3_IRQn, 0, nrfx_pwm_3_irq_handler, 0);
    }
    
    static uint16_t bool_to_pwm(bool high)
    {
        return high ? 0xFFFF : 0x7FFF;
    }
    
    static void generate_pattern(pwm_controller_data_t *data)
    {
        const bool pattern[4][3] = {
            { true, false, false },
            { false, true, false },
            { true, false, true },
            { false, false, false },
        };
        const uint16_t timing_us[4] = { 10, 50, 10, 5 };
        for (int step = 0; step < 4; step++) {
            for (int pwm = 0; pwm < NUM_AVAIL_PWMS; pwm++) {
                data->pwm_values[pwm][step].channel_0 = bool_to_pwm(pattern[step][0]);
                data->pwm_values[pwm][step].channel_1 = bool_to_pwm(pattern[step][1]);
                data->pwm_values[pwm][step].channel_2 = bool_to_pwm(pattern[step][2]);
                data->pwm_values[pwm][step].counter_top = timing_us[step];
            }
        };
    }
    
    static void pwm_handler(nrfx_pwm_evt_type_t event_type, void *ctx)
    {
        if (event_type != NRFX_PWM_EVT_FINISHED) {
            return;
        }
        const nrfx_pwm_t *pwm = (const nrfx_pwm_t *) ctx;
        k_event_set(&(pwm_controller_data.event_done), (1 << pwm->instance_id));
    }
    
    static void prepare_pwms(const nrfx_pwm_t *pwm, nrf_pwm_values_wave_form_t *wave, const uint8_t *pins,
                             size_t wave_form_len)
    {
        const nrfx_pwm_config_t pwm_config = {
                .output_pins = {
                    pins[0],
                    pins[1],
                    pins[2],
                    NRFX_PWM_PIN_NOT_USED,
                },
                .pin_inverted = {false, false, false, false},
                .irq_priority = 4,
                .base_clock = NRF_PWM_CLK_1MHz,
                .count_mode = NRF_PWM_MODE_UP,
                .top_value = 0,
                .load_mode = NRF_PWM_LOAD_WAVE_FORM,
                .step_mode = NRF_PWM_STEP_AUTO,
                .skip_gpio_cfg = false,
                .skip_psel_cfg = false,
            };
        const nrfx_err_t err = nrfx_pwm_init(pwm, &pwm_config, pwm_handler, (void *) pwm);
        assert(NRFX_SUCCESS == err);
        const uint32_t flags = NRFX_PWM_FLAG_START_VIA_TASK | NRFX_PWM_FLAG_STOP;
        const nrf_pwm_sequence_t seq = {
            .values.p_wave_form = wave,
            .length = 4 * wave_form_len,
            .repeats = 0,
            .end_delay = 0,
        };
        nrfx_pwm_simple_playback(pwm, &seq, 1, flags);
    }
    
    static void setup_ppi(nrf_ppi_channel_t *channel, uint32_t event, uint32_t task1, uint32_t task2)
    {
        nrfx_err_t err = nrfx_ppi_channel_alloc(channel);
        assert(err == NRFX_SUCCESS);
        err = nrfx_ppi_channel_assign(*channel, event, task2);
        assert(err == NRFX_SUCCESS);
        err = nrfx_ppi_channel_fork_assign(*channel, task1);
        assert(err == NRFX_SUCCESS);
        err = nrfx_ppi_channel_enable(*channel);
        assert(err == NRFX_SUCCESS);
    }
    
    static void start_pwms(const nrfx_pwm_t *pwms)
    {
        nrf_ppi_channel_t channel[3];
    
        setup_ppi(&channel[0], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0),
                  nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER1),
                  nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER2));
        setup_ppi(&channel[1], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED1),
                  nrfx_pwm_task_address_get(&pwms[0], NRF_PWM_TASK_SEQSTART1),
                  nrfx_pwm_task_address_get(&pwms[1], NRF_PWM_TASK_SEQSTART1));
        setup_ppi(&channel[2], nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED2),
                  nrfx_pwm_task_address_get(&pwms[2], NRF_PWM_TASK_SEQSTART1),
                  nrfx_pwm_task_address_get(&pwms[3], NRF_PWM_TASK_SEQSTART1));
    
        nrf_egu_task_trigger(NRF_EGU0, NRF_EGU_TASK_TRIGGER0);
    
        for (size_t k = 0; k < ARRAY_SIZE(channel); k++) {
            nrfx_err_t err = nrfx_ppi_channel_free(channel[k]);
            assert(NRFX_SUCCESS == err);
        }
    }
    
    void pwm_controller_generate(void)
    
    {
        const pwm_controller_cfg_t *p_cfg = &pwm_controller_config;
        pwm_controller_data_t *p_data = &pwm_controller_data;
    
        generate_pattern(p_data);
    
        const uint8_t pins[12] = {
            13, 19, 25, 10, 5 + 32, 3 + 32, 9 + 32, 11, 1 + 32, 4 + 32, 28, 0xff,
        };
    
        for (int pwm = 0; pwm < NUM_AVAIL_PWMS; pwm++) {
            prepare_pwms(&p_cfg->pwm_instance[pwm], p_data->pwm_values[pwm], &pins[3 * pwm], 4);
        }
    
        start_pwms(p_cfg->pwm_instance);
    
        uint32_t events = k_event_wait(&(p_data->event_done), 0xF, true, K_FOREVER);
        if (events == 0) {
            printk("timeout\n");
        }
        for (size_t k = 0; k < NUM_AVAIL_PWMS; k++) {
            nrfx_pwm_uninit(&(p_cfg->pwm_instance[k]));
        }
    }
    

  • Quite; and I just realised I was testing on the wrong board when I posted that last update, Common has the exact same issue as Grouped. I'll edit the last post.

  • Response from Susheel at Nordic:

    "I got some information and yes there is a change in nRF52833 compared in nRF52832.
    There is now a two stage AMLI bus system in nRF52833 instead of one (nRF52832). The reason we did not publish this is because we thought there should be no use cases where the application should directly see the difference. But you genius found the right use case with the right peripheral (PWM) where multi instance trigger at the same time is normal.
    So the change is effective to all peripherals with EasyDMA using the 16MHz clock."

  • Nice to know. Does the nRF52840 also use the two stage AMLI bus?

  • No, same as nRF52832. Yes, nRF52840 is the same as the nRF52833. Not sure about nRF54L15 yet

Reply Children
Related