Combine several timer/counters

As the title mention, I try to combine several timers/counters on Arduino Zero, this is what I have working so far:

  • Oneshot counter (GCLK_CLKCTRL_ID_TCC0_TCC1)
  • 50us timer ISR (GCLK_CLKCTRL_ID_TC4_TC5)
  • 100us timer ISR (GCLK_CLKCTRL_ID_TCC2_TC3)

The oneshot timer:

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  ////////////////////////////////////////////////////////////////////////////////////////
  // Genric Clock Initialisation
  ////////////////////////////////////////////////////////////////////////////////////////
 
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  ////////////////////////////////////////////////////////////////////////////////////////
  // TCC0 Initialisation - output 10us oneshot pulse upon receiving an event on PA22
  ////////////////////////////////////////////////////////////////////////////////////////
 
  // Enable the port multiplexer on port pin PA22
  PORT->Group[PORTA].PINCFG[22].bit.PMUXEN = 1;
  // Set-up the pin as TCC0/WO[4] peripheral on PA22
  PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set the TCC0 timer to normal PWM mode (NPWM)
  while(TCC0->SYNCBUSY.bit.WAVE);                    // Wait for synchronization           
 
  TCC0->PER.reg = 96;                                 // Set the period (PER) register for oneshot pulse width
  while(TCC0->SYNCBUSY.bit.PER);                     // Wait for synchronization

  TCC0->CC[0].reg = 48;                              // Set the counter compare 0 (CC0) register for a pulse width of 1us
  while(TCC0->SYNCBUSY.bit.CC0);                     // Wait for synchronization
 
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable oneshot operation
  while(TCC0->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization

  TCC0->DRVCTRL.reg |= TCC_DRVCTRL_NRE4;             // Activate the Non Recoverable Fault Enable to drive pin low after oneshot
 
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Event System Initialisation - set up to allow TCC0 to receive incoming pulses on PA18
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
 
  // Enable the port multiplexer on port pin PA18
  PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on PA18
  PORT->Group[PORTA].PMUX[18 >> 1].reg |= PORT_PMUX_PMUXE_A;
 
  //EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO2;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_FALL;                            // Set event detecting a FALLING edge
  //EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT2;                              // Disable interrupts on external interrupt 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 0
                   
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous                     
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_2) |    // Set event generator (sender) as external interrupt 2
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TCC0->EVCTRL.reg |= TCC_EVCTRL_TCEI0 |                                   // Enable TCC0 event 0 inputs                               
                      TCC_EVCTRL_EVACT0_RETRIGGER;                         // Retrigger timer TCC0 on receiving event
                                 
  TCC0->CTRLA.bit.ENABLE = 1;                                              // Enable TCC0 by default!
  while (TCC0->SYNCBUSY.bit.ENABLE);                                       // Wait for synchronization

The 50us timer:

// Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  REG_TC4_COUNT16_CC0 = 0x12C0;                   // Set the TC4 CC0 register as the TOP value in match frequency mode
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 1);                  // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);                       // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_INTFLAG |= TC_INTFLAG_OVF;              // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_OVF;             // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_OVF;          // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1024 = 46.875kHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{
//code 
}

The 100us timer:

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable the generic clock...
                    GCLK_CLKCTRL_GEN_GCLK0 |      // ....on GCLK0 at 48MHz
                    GCLK_CLKCTRL_ID_TCC2_TC3;     // Feed the GCLK0 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_TC3_COUNT16_CC0 = 0x12C0;                   // Set the TC3 CC0 register as the TOP value in match frequency mode
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC3_IRQn, 2);                  // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 1 (medium)
  NVIC_EnableIRQ(TC3_IRQn);                       // Connect TC3 to Nested Vector Interrupt Controller (NVIC)

  REG_TC3_INTFLAG |= TC_INTFLAG_OVF;              // Clear the interrupt flags
  REG_TC3_INTENSET = TC_INTENSET_OVF;             // Enable TC3 interrupts
  // REG_TC3_INTENCLR = TC_INTENCLR_OVF;          // Disable TC3 interrupts
 
  REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1024 = 46.875kHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC3 into match frequency (MFRQ) mode
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization   

void TC3_Handler()                              // Interrupt Service Routine (ISR) for timer TC3
{
// code
}

Now, as an addition to the already implemented timers, I would like to have a pulse counter as described here: Link!

Is this possible at TC5 for instance? I would like to have a hardware pulse counter to determine the frequency of digital input: D10, this digital input is also used in the oneshot timer. The input signal will have freq around the 50KHz.

In the meantime I made some progress, the setup is as follow:

  • Oneshot counter (GCLK_CLKCTRL_ID_TCC0_TCC1)
  • 50us timer ISR (TC4)
  • 100us timer ISR (TC5)

Now I have set the pulsecounter on TC3:

// Generic Clock /////////////////////////////////////////////////////////////////////////
 
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |    // On GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Route GCLK0 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Port Configuration ///////////////////////////////////////////////////////////////////
/*
  // Enable the port multiplexer on port pin PA22
  PORT->Group[PORTA].PINCFG[22].bit.PMUXEN = 1;
  // Set-up the pin as TCC0/WO[4] peripheral on PA22
  PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_F;
*/
  // External Interrupt Controller (EIC) ///////////////////////////////////////////////////

  //EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO2;                                // Enable event output on external interrupt 2
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_FALL;                           // Set event detecting a HIGH level on interrupt 2
  //EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT2;                               // Disable interrupts on interrupt 2
  EIC->CTRL.bit.ENABLE = 1;                                               // Enable the EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization

  // Event System //////////////////////////////////////////////////////////////////////////

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;                                  // Switch on the event system peripheral

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                               // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);               // Set the event user (receiver) as timer TC3
 
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |               // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                  // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_2) |   // Set event generator (sender) as external interrupt 2
                       EVSYS_CHANNEL_CHANNEL(0);                          // Attach the generator (sender) to channel 0                                 
 
  // Timer Counter TC4 /////////////////////////////////////////////////////////////////////

  TC3->COUNT16.EVCTRL.reg |= TC_EVCTRL_TCEI |              // Enable asynchronous events on the TC timer
                             TC_EVCTRL_EVACT_COUNT;        // Increment the TC timer each time an event is received

  TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16;          // Configure TC4 together with TC5 to operate in 32-bit mode
                     
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;                       // Enable TC4
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization

  TC3->COUNT16.READREQ.reg = TC_READREQ_RCONT;        // Offset of the 32-bit COUNT register
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization

The only thing left is that Digital input D10 is used in the Oneshot counter (see first post), as well the pulsecounter. At certain point, I would like to disable the D10 as trigger for the oneshot counter, but still be able to fire the oneshotcounter manually (what already works), at the moment I'm using this:

EIC->EVCTRL.reg &= ~EIC_EVCTRL_EXTINTEO2;
while (EIC->STATUS.bit.SYNCBUSY);

But as you problaby can see, this will also disable the pulsecounter on TC3. I would like to have the pulsecounter be enabled all the time. Who can point me in the right direction?

This method (provided by MartinL: OneShot timer on pin rising edge - #12 by MartinL - Arduino Zero - Arduino Forum) doesn't work for some reason:

TCC0->EVCTRL.reg &= ~TCC_EVCTRL_EVACT0_Msk;  // Turn off the action on event 0
and
TCC0->EVCTRL.reg |= TCC_EVCTRL_EVACT0_RETRIGGER;  // Turn on the retrigger action on event 0

Then the system freezes.

Hi LeCrAm,

Here's an example of using TC timer capture on TC5 on D12 (PA19 on the Arduino Zero), with a 50kHz PWM test signal on D7 (PA21 on the Arduino Zero):

// Setup TC5 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;

void setup()   {                
  SerialUSB.begin(115200);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open
  
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK0 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0
                      GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK0 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer on digital pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D7
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F;

  TCC0->WAVE.reg =  TCC_WAVE_WAVEGEN_NPWM;          // Enable Normal PWM (NPWM) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                  // Wait for synchronization
  
  TCC0->PER.reg = 959;                               // Set the timer to output a 50kHz
  while (TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization

  TCC0->CC[3].reg = 480;                             // Set CC3 duty-cycle to 50%
  while (TCC0->SYNCBUSY.bit.CC3);                   // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                       // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization

  // Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Disable interrupts on external interrupt 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
  
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC5_EVU);                // Set the event user (receiver) as timer TC5

  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC5->COUNT16.EVCTRL.reg |= TC_EVCTRL_TCEI |              // Enable the TC event input
                             //TC_EVCTRL_TCINV |             // Invert the event input
                             TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
                  
  TC5->COUNT16.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC5->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (write) synchronization

  NVIC_SetPriority(TC5_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC5 to 0 (highest)
  NVIC_EnableIRQ(TC5_IRQn);           // Connect the TC5 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC5->COUNT16.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
  
  TC5->COUNT16.CTRLA.bit.ENABLE = 1;                       // Enable TC5
  while (TC5->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization
}

void loop() { 
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                  
    pulsewidth = isrPulsewidth;
    interrupts();
    SerialUSB.print("P ");
    SerialUSB.println(period);
    SerialUSB.print("PW ");
    SerialUSB.println(pulsewidth);
    periodComplete = false;                       // Start a new period
  }
}

void TC5_Handler()                                // Interrupt Service Routine (ISR) for timer TC5
{    
  // Check for match counter 0 (MC0) interrupt
  if (TC5->COUNT16.INTFLAG.bit.MC0)            
  {
    TC5->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x18);      // Offset address of the CC0 register
    while (TC5->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPeriod = TC5->COUNT16.CC[0].reg;                    // Copy the period  
    periodComplete = true;                                 // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC5->COUNT16.INTFLAG.bit.MC1)          
  {
    TC5->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x1A);      // Offset address of the CC1 register
    while (TC5->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPulsewidth = TC5->COUNT16.CC[1].reg;                // Copy the pulse-width
  }
}

Hi Martin, Thanks for your reply, I have things up and running now, only issue is with the shared D10 input by the Oneshot counter and the pulsecounter. You showed me this in another thread:

TCC0->EVCTRL.reg &= ~TCC_EVCTRL_EVACT0_Msk;  // Turn off the action on event 0
and
TCC0->EVCTRL.reg |= TCC_EVCTRL_EVACT0_RETRIGGER;  // Turn on the retrigger action on event 0

To temporary disable the D10 trigger on the oneshot counter, but for some reason, it doesnt work, the system freezes.