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

How to synchronize PWM with external event

As for dimmer applications neccessary, a pwm signal with variable duty-cycle must be generated to drive a triac. But to be synchronized with the mains' zero crossing, the pwm cycle must be started by a GPIO event from a zero-crossing sensor (optocoupler). What would be the best approach to solve this problem? Has anybody done a similar thing before?

Parents
  • Finally I found a solution in heavy use of PPI. I don't use PWM any longer, but use a chain of 6 ppi channels. First one assigns the zero-crossing event with the start of a phase-shift timer, This one will be disabled, if the load should be switched off. The timer controls the delay from the zero-crossing event until the end of a half-wave. The second timer is triggered by the overrun of the first to define the pulse length of the triac firing pulse. I would appreciate any improvements of the following code.

    const nrf_drv_timer_t phaseshift_timer  = NRF_DRV_TIMER_INSTANCE(1);
    const nrf_drv_timer_t pulselength_timer = NRF_DRV_TIMER_INSTANCE(2);
    static nrf_ppi_channel_t                m_zerocrossing_channel;
    
    void zerocrossing_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {}
    void trigger_event_handler(nrf_timer_event_t event_type, void* p_context){}
    
    /** @brief Function for timer initialization, which will be started by zero-crossing using PPI.
    */
    static void high_resolution_timer_init(void)
    {
        uint32_t err_code = NRF_SUCCESS;
        nrf_drv_timer_config_t timer_config;
        timer_config.frequency = NRF_TIMER_FREQ_1MHz;
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_16;
        timer_config.interrupt_priority = NRF_APP_PRIORITY_LOW;
        timer_config.mode = NRF_TIMER_MODE_TIMER;
        timer_config.p_context = NULL;
        err_code = nrf_drv_timer_init(&phaseshift_timer, &timer_config, trigger_event_handler);
        APP_ERROR_CHECK(err_code);
        // 10000 cycles at 1Mhz makes 0.01 sec - the duration of one half wave at 50Hz mains AC frequency
        nrf_drv_timer_extended_compare(&phaseshift_timer, NRF_TIMER_CC_CHANNEL0, 0x2710, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        err_code = nrf_drv_timer_init(&pulselength_timer, &timer_config, trigger_event_handler);
        APP_ERROR_CHECK(err_code);
        // the length of the triac firing pulse is 0.001 sec
        nrf_drv_timer_extended_compare(&pulselength_timer, NRF_TIMER_CC_CHANNEL0, 0x03E8UL, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    }
    
    /** @brief Function for initializing the PPI peripheral.
    */
    static void peripheral_init(void)
    {
        uint32_t err_code = NRF_SUCCESS;
        nrf_ppi_channel_t ppi_channel2;
        nrf_ppi_channel_t ppi_channel3;
        nrf_ppi_channel_t ppi_channel4;
        nrf_ppi_channel_t ppi_channel5;
        nrf_ppi_channel_t ppi_channel6;
        uint32_t zerocrossing_addr;
        uint32_t load_addr;
    
        // init the phase-shift and pulse-length timer
        high_resolution_timer_init();
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        if (!nrf_drv_gpiote_is_init())
        {
            err_code = nrf_drv_gpiote_init();
            if(err_code != NRF_SUCCESS)
            {
                APP_ERROR_CHECK(err_code);
                return;
            }
        }
    
        // create the zero-crossing event
        nrf_drv_gpiote_in_config_t zero_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
        zero_config.pull = NRF_GPIO_PIN_PULLUP;
        err_code = nrf_drv_gpiote_in_init(ZERO, &zero_config, zerocrossing_event_handler);
        APP_ERROR_CHECK(err_code);
        zerocrossing_addr = nrf_drv_gpiote_in_event_addr_get(ZERO);
        nrf_drv_gpiote_in_event_enable(ZERO, true);
        // assign zero-crossing event to start of phase-shift timer
        // start timer on zero-crossing event
        err_code = nrf_drv_ppi_channel_alloc(&m_zerocrossing_channel);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(m_zerocrossing_channel,
                                              zerocrossing_addr,
                                              nrf_drv_timer_task_address_get(&phaseshift_timer, NRF_TIMER_TASK_START));
        APP_ERROR_CHECK(err_code);
        // create the load control task
        nrf_drv_gpiote_out_config_t load_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
        load_config.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW;
        err_code = nrf_drv_gpiote_out_init(LOAD, &load_config);
        APP_ERROR_CHECK(err_code);
        load_addr = nrf_drv_gpiote_out_task_addr_get(LOAD);
        nrf_drv_gpiote_out_task_enable(LOAD);
        // assign the end of the phase-shift timer to the load control task
        // toggle load control if timer overruns
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel2);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel2,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              load_addr);
        APP_ERROR_CHECK(err_code);
        // assign the end of the phase-shift timer to the start of the pulse-length timer
        // start pulse_length timer with the end of the phase-shift timer
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel3);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel3,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&pulselength_timer, NRF_TIMER_TASK_START));
        APP_ERROR_CHECK(err_code);
        // assign the end of the phase-shift timer to stop the phase-shift timer
        // end of phase-shift timer stops itself
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel4);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel4,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&phaseshift_timer, NRF_TIMER_TASK_STOP));
        APP_ERROR_CHECK(err_code);
        // assign end of pulse-length timer to the load control task
        // toggle load control with the end of pulse-length timer
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel5);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel5,
                                              nrf_drv_timer_event_address_get(&pulselength_timer, NRF_TIMER_EVENT_COMPARE0),
                                              load_addr);
        APP_ERROR_CHECK(err_code);
        // assign end of pulse-length timer to stop itself
        // end of pulse-length timer stops itself
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel6);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel6,
                                              nrf_drv_timer_event_address_get(&pulselength_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&pulselength_timer, NRF_TIMER_TASK_STOP));
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel2);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel3);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel4);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel5);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel6);
        APP_ERROR_CHECK(err_code);
    }
    
    void set_brightness(uint16_t brightness)
    {
        uint32_t err_code = NRF_SUCCESS;
        if(brightness > 0) {
            // enable zero-crossing event
            err_code = nrf_drv_ppi_channel_enable(m_zerocrossing_channel);
            APP_ERROR_CHECK(err_code);
            uint16_t counter;
            counter = 10000 - 100*brightness;
            // the min ticks is 4 - represents full cycle load
            nrf_drv_timer_extended_compare(&phaseshift_timer, NRF_TIMER_CC_CHANNEL0, counter+4, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        }
        else
        {
            // disable zero-crossing event
            err_code = nrf_drv_ppi_channel_disable(m_zerocrossing_channel);
            APP_ERROR_CHECK(err_code);
        }
    }
    
Reply
  • Finally I found a solution in heavy use of PPI. I don't use PWM any longer, but use a chain of 6 ppi channels. First one assigns the zero-crossing event with the start of a phase-shift timer, This one will be disabled, if the load should be switched off. The timer controls the delay from the zero-crossing event until the end of a half-wave. The second timer is triggered by the overrun of the first to define the pulse length of the triac firing pulse. I would appreciate any improvements of the following code.

    const nrf_drv_timer_t phaseshift_timer  = NRF_DRV_TIMER_INSTANCE(1);
    const nrf_drv_timer_t pulselength_timer = NRF_DRV_TIMER_INSTANCE(2);
    static nrf_ppi_channel_t                m_zerocrossing_channel;
    
    void zerocrossing_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {}
    void trigger_event_handler(nrf_timer_event_t event_type, void* p_context){}
    
    /** @brief Function for timer initialization, which will be started by zero-crossing using PPI.
    */
    static void high_resolution_timer_init(void)
    {
        uint32_t err_code = NRF_SUCCESS;
        nrf_drv_timer_config_t timer_config;
        timer_config.frequency = NRF_TIMER_FREQ_1MHz;
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_16;
        timer_config.interrupt_priority = NRF_APP_PRIORITY_LOW;
        timer_config.mode = NRF_TIMER_MODE_TIMER;
        timer_config.p_context = NULL;
        err_code = nrf_drv_timer_init(&phaseshift_timer, &timer_config, trigger_event_handler);
        APP_ERROR_CHECK(err_code);
        // 10000 cycles at 1Mhz makes 0.01 sec - the duration of one half wave at 50Hz mains AC frequency
        nrf_drv_timer_extended_compare(&phaseshift_timer, NRF_TIMER_CC_CHANNEL0, 0x2710, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        err_code = nrf_drv_timer_init(&pulselength_timer, &timer_config, trigger_event_handler);
        APP_ERROR_CHECK(err_code);
        // the length of the triac firing pulse is 0.001 sec
        nrf_drv_timer_extended_compare(&pulselength_timer, NRF_TIMER_CC_CHANNEL0, 0x03E8UL, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    }
    
    /** @brief Function for initializing the PPI peripheral.
    */
    static void peripheral_init(void)
    {
        uint32_t err_code = NRF_SUCCESS;
        nrf_ppi_channel_t ppi_channel2;
        nrf_ppi_channel_t ppi_channel3;
        nrf_ppi_channel_t ppi_channel4;
        nrf_ppi_channel_t ppi_channel5;
        nrf_ppi_channel_t ppi_channel6;
        uint32_t zerocrossing_addr;
        uint32_t load_addr;
    
        // init the phase-shift and pulse-length timer
        high_resolution_timer_init();
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        if (!nrf_drv_gpiote_is_init())
        {
            err_code = nrf_drv_gpiote_init();
            if(err_code != NRF_SUCCESS)
            {
                APP_ERROR_CHECK(err_code);
                return;
            }
        }
    
        // create the zero-crossing event
        nrf_drv_gpiote_in_config_t zero_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
        zero_config.pull = NRF_GPIO_PIN_PULLUP;
        err_code = nrf_drv_gpiote_in_init(ZERO, &zero_config, zerocrossing_event_handler);
        APP_ERROR_CHECK(err_code);
        zerocrossing_addr = nrf_drv_gpiote_in_event_addr_get(ZERO);
        nrf_drv_gpiote_in_event_enable(ZERO, true);
        // assign zero-crossing event to start of phase-shift timer
        // start timer on zero-crossing event
        err_code = nrf_drv_ppi_channel_alloc(&m_zerocrossing_channel);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(m_zerocrossing_channel,
                                              zerocrossing_addr,
                                              nrf_drv_timer_task_address_get(&phaseshift_timer, NRF_TIMER_TASK_START));
        APP_ERROR_CHECK(err_code);
        // create the load control task
        nrf_drv_gpiote_out_config_t load_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
        load_config.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW;
        err_code = nrf_drv_gpiote_out_init(LOAD, &load_config);
        APP_ERROR_CHECK(err_code);
        load_addr = nrf_drv_gpiote_out_task_addr_get(LOAD);
        nrf_drv_gpiote_out_task_enable(LOAD);
        // assign the end of the phase-shift timer to the load control task
        // toggle load control if timer overruns
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel2);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel2,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              load_addr);
        APP_ERROR_CHECK(err_code);
        // assign the end of the phase-shift timer to the start of the pulse-length timer
        // start pulse_length timer with the end of the phase-shift timer
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel3);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel3,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&pulselength_timer, NRF_TIMER_TASK_START));
        APP_ERROR_CHECK(err_code);
        // assign the end of the phase-shift timer to stop the phase-shift timer
        // end of phase-shift timer stops itself
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel4);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel4,
                                              nrf_drv_timer_event_address_get(&phaseshift_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&phaseshift_timer, NRF_TIMER_TASK_STOP));
        APP_ERROR_CHECK(err_code);
        // assign end of pulse-length timer to the load control task
        // toggle load control with the end of pulse-length timer
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel5);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel5,
                                              nrf_drv_timer_event_address_get(&pulselength_timer, NRF_TIMER_EVENT_COMPARE0),
                                              load_addr);
        APP_ERROR_CHECK(err_code);
        // assign end of pulse-length timer to stop itself
        // end of pulse-length timer stops itself
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel6);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel6,
                                              nrf_drv_timer_event_address_get(&pulselength_timer, NRF_TIMER_EVENT_COMPARE0),
                                              nrf_drv_timer_task_address_get(&pulselength_timer, NRF_TIMER_TASK_STOP));
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel2);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel3);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel4);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel5);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel6);
        APP_ERROR_CHECK(err_code);
    }
    
    void set_brightness(uint16_t brightness)
    {
        uint32_t err_code = NRF_SUCCESS;
        if(brightness > 0) {
            // enable zero-crossing event
            err_code = nrf_drv_ppi_channel_enable(m_zerocrossing_channel);
            APP_ERROR_CHECK(err_code);
            uint16_t counter;
            counter = 10000 - 100*brightness;
            // the min ticks is 4 - represents full cycle load
            nrf_drv_timer_extended_compare(&phaseshift_timer, NRF_TIMER_CC_CHANNEL0, counter+4, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        }
        else
        {
            // disable zero-crossing event
            err_code = nrf_drv_ppi_channel_disable(m_zerocrossing_channel);
            APP_ERROR_CHECK(err_code);
        }
    }
    
Children
Related