PPI and timer glitches

I have a problem with timer 3 of nrf52820.

It concerns a trailing edge dimmer implementation based on timer 3. A gpiote is used to sync the zero crossing.

I use ppi to clear a pin on CC[2] and to set on CC[3] . The timer is cleared (also through ppi) with a gpiote event on another pin. This event also creates on interrupt, and in the interrupt routine CC[3] is re-written. CC[2] is fixed on value 2. 

Everything works fine as long as CC[3] is written with value greater than 128. When a value below 128 is used, the output pins remains low for one timer cycle. It seems that CC[3] is missing a cycle. Next cycle every thing is OK again, even when values lower than 128 are written. 

The timer is running on 4MHz clock and the cycle time is 10ms (counter is approx. 40000 when cleared). 

i isolated the problem in a small program that is attached.

/**
 ******************************************************************************
 *   
 * Evaluation SW for solving light flicker bug
 *
 * HoC 21-11-25
 * 
 * Timer 2 is used to emulate the 10ms mains frequency zero crossing  (normally zero crossing is sensed by a GPIOTE )
 * Timer 1 is used as a 2  seconds on/off switching of the light
 * Timer 3 is the main system timer that regulates the dimmer PWM.
 * The output of the system is Pin_DON (3)
 *
 * The actual setting of the dimmer is done in the while loop :  every 10ms zcflag is set and a new value is written in the CC of timer 3.
 * the value being pushed is dimval. 
 *    If dimpush goes lower than 128, a ppi cycle is somehow missed and Pin_DON will be high for 10ms.
 *    If dimpush is capped at 128, the effect dissappears.
 * 
 *
 ******************************************************************************
 */

#include "nrf_drv_ppi.h"
#include "nrfx_gpiote.h"
#include "nrfx_ppi.h"
#include "nrfx_timer.h"

#define Pin_DON  6

nrf_ppi_channel_t ppi_channel1;
nrf_ppi_channel_t ppi_channel2;
nrf_ppi_channel_t ppi_channel3;
nrf_ppi_channel_t ppi_channel4;
nrf_ppi_channel_t ppi_channel5;

static nrfx_timer_t m_timer1 = NRFX_TIMER_INSTANCE(1); //5 sec on / off timer
static nrfx_timer_t m_timer2 = NRFX_TIMER_INSTANCE(2);  //10 ms timer
static nrfx_timer_t m_timer3 = NRFX_TIMER_INSTANCE(3);  //dimmer timer

uint8_t zcflag =0;
uint8_t  dimflag=0;  //1:  light up  2: light down
uint16_t dimval=0;
uint16_t dimpush=0;
uint16_t dimcount=0;
uint16_t dimstore=3;
uint16_t dimstart=0;
uint16_t dimstop=0;
uint16_t dimspeed=517;

////////////////////////////////////////////////////////////
static void timer3_handler(nrf_timer_event_t event_type, void * p_context)
{
}
//////////////////////////////////////////////////////////////////
static void timer1_handler(nrf_timer_event_t event_type, void * p_context)
{
  if (event_type == NRF_TIMER_EVENT_COMPARE1 )  
  { 
   dimflag = 1;
   dimstop = 40000;
  }
  if (event_type == NRF_TIMER_EVENT_COMPARE2 )   
 { 
   dimflag = 2;
   dimstop =0;
   nrfx_timer_clear(&m_timer1);
  }
}
////////////////////////////////////////////////////////////
static void timer2_handler(nrf_timer_event_t event_type, void * p_context)
{
  if (event_type == NRF_TIMER_EVENT_COMPARE1 )  
  {
    nrfx_timer_clear(&m_timer2);
    zcflag =1;
  }
}
//////////////////////////////////////////////////////
void timer3_init(void)  //50-60 HZ phase timer
{
  nrfx_timer_config_t tmr_config = NRFX_TIMER_DEFAULT_CONFIG;
  tmr_config.frequency = (nrf_timer_frequency_t)NRF_TIMER_FREQ_4MHz;
  tmr_config.mode = (nrf_timer_mode_t)NRF_TIMER_MODE_TIMER;
  tmr_config.bit_width = (nrf_timer_bit_width_t)NRF_TIMER_BIT_WIDTH_32;
  nrfx_timer_init(&m_timer3, &tmr_config, timer3_handler);
  nrfx_timer_capture(&m_timer3, NRF_TIMER_CC_CHANNEL0);
  nrfx_timer_compare(&m_timer3, NRF_TIMER_CC_CHANNEL1, 1, false);
  nrfx_timer_compare(&m_timer3, NRF_TIMER_CC_CHANNEL2, 2, false);
}
////////////////////////////////////////////////////

void timer2_init(void)  //10ms test timer
{
  nrfx_timer_config_t tmr_config = NRFX_TIMER_DEFAULT_CONFIG;
  tmr_config.frequency = (nrf_timer_frequency_t)NRF_TIMER_FREQ_1MHz;
  tmr_config.mode = (nrf_timer_mode_t)NRF_TIMER_MODE_TIMER;
  tmr_config.bit_width = (nrf_timer_bit_width_t)NRF_TIMER_BIT_WIDTH_32;
  nrfx_timer_init(&m_timer2, &tmr_config, timer2_handler);
  nrfx_timer_compare(&m_timer2, NRF_TIMER_CC_CHANNEL1, 10000, true);  //10ms
}
////////////////////////////////////////////////////
void timer1_init(void)  //light on - off timer
{
  nrfx_timer_config_t tmr1_config = NRFX_TIMER_DEFAULT_CONFIG;
  tmr1_config.frequency = (nrf_timer_frequency_t)NRF_TIMER_FREQ_1MHz;
  tmr1_config.mode = (nrf_timer_mode_t)NRF_TIMER_MODE_TIMER;
  tmr1_config.bit_width = (nrf_timer_bit_width_t)NRF_TIMER_BIT_WIDTH_32;
  nrfx_timer_init(&m_timer1, &tmr1_config, timer1_handler);
  nrfx_timer_compare(&m_timer1, NRF_TIMER_CC_CHANNEL1, 2000000, true);  //2 seconds
  nrfx_timer_compare(&m_timer1, NRF_TIMER_CC_CHANNEL2, 4000000, true);  //4 seconds
}
///////////////////////////////////////
void fault_handler (void) 
{
    NVIC_SystemReset();
}
/////////////////////////////////////////////////////////////
static void ppi_init(void)   //MOSFET switching  and mainteance
{
  uint32_t gpiote_task_addr_set;
  uint32_t gpiote_task_addr_clr;
  uint32_t timer_task_clr;
  uint32_t timer_task_cap;
  uint32_t timer_evt_addr1;
  uint32_t timer_evt_addr2;
  uint32_t timer_evt_addr3;

// Configure GPIOTE IN event
  nrf_drv_ppi_init();
  nrf_drv_ppi_channel_alloc(&ppi_channel1);
  nrf_drv_ppi_channel_alloc(&ppi_channel2);
  nrf_drv_ppi_channel_alloc(&ppi_channel3);

  nrfx_gpiote_out_config_t out_config = NRFX_GPIOTE_CONFIG_OUT_TASK_HIGH;
  nrfx_gpiote_out_init(Pin_DON, &out_config);   //MOSFET off
 
  gpiote_task_addr_set = nrfx_gpiote_set_task_addr_get(Pin_DON);
  gpiote_task_addr_clr = nrfx_gpiote_clr_task_addr_get(Pin_DON);
  
  timer_evt_addr1 = nrfx_timer_event_address_get(&m_timer3,NRF_TIMER_EVENT_COMPARE1);
  timer_evt_addr2 = nrfx_timer_event_address_get(&m_timer3,NRF_TIMER_EVENT_COMPARE2);
  timer_evt_addr3= nrfx_timer_event_address_get(&m_timer2, NRF_TIMER_EVENT_COMPARE1);
  timer_task_clr = nrfx_timer_task_address_get(&m_timer3, NRF_TIMER_TASK_CLEAR);
  timer_task_cap = nrfx_timer_task_address_get(&m_timer3, NRF_TIMER_TASK_CAPTURE0);
 
  nrfx_ppi_channel_assign(ppi_channel1,timer_evt_addr3, timer_task_clr);
  nrfx_ppi_channel_assign(ppi_channel2, timer_evt_addr1, gpiote_task_addr_clr);  
  nrfx_ppi_channel_assign(ppi_channel3, timer_evt_addr2, gpiote_task_addr_set);

  nrfx_ppi_channel_enable(ppi_channel1);   // capture and clear timer  on zero cross
  nrfx_ppi_channel_enable(ppi_channel2);   //MOSFET on (CC1)
  nrfx_ppi_channel_enable(ppi_channel3);   //MOSFET off  (CC2)

  nrfx_gpiote_out_task_enable(Pin_DON);
}

/////////////////////////////////////////////////////////////////////////////////////////////
int main(void)
{
 nrfx_gpiote_init();
 timer1_init();
 timer2_init();
 timer3_init(); 
 ppi_init();
 nrfx_timer_enable(&m_timer3); //MOSFET switching timer
 nrfx_timer_enable(&m_timer2);
 nrfx_timer_enable(&m_timer1);
 
while (true)
 {
  if (zcflag > 0 ) //every mains cycle adjust light
  {
    zcflag =0;
    if (dimflag == 2 )  //lights down
      {
       if (dimval > dimstop && dimval > 641) dimval-=dimspeed;
       else dimflag =0;
      }
    if (dimflag == 1 )  //lights up
      {
       if (dimval < dimstop ) dimval +=dimspeed;
       else dimflag =0;
      }
      dimpush = dimval;
  //  if (dimval <641 ) dimpush =128; //minimum value that causes no flicker
    if (dimval <641 ) dimpush =7; //gives flicker
     
     m_timer3.p_reg->CC[2]=dimpush;
  } 
 }
}

  • Hello @ skeletal shenanigans, you are missing the compare event because CC is updated too late. When dimpush < ~128, TIMER3 has already counted past the new CC value after the PPI CLEAR, so the COMPARE event does not occur for that cycle. Result: the pin stays low for one full 10 ms period, then recovers next cycle. This is expected nRF52 behavior.

    I think you should ensure CC is written before the timer starts counting (or atomically with CLEAR), or clamp the minimum CC value (e.g. ≥150 ticks), or stop/clear the timer before updating CC.

Related