Arduino Zero/M0 Pro clock/counter questions

Hi,

I am trying to use some counters as clocks. I need a high precision clock that needs to count real time seconds with better than 0.5 µs precision and can count up to a few thousand seconds. Also two clocks that are software gated with the same precision. I am planning to use the 48 MHz PLL locked clock and with a divisor of 9 that gives me the required precision (5.33333... MHz) and I presume, its locked to a 32. 768 kHz watch crystal, good accuracy too.

I shamelessly hacked Marcus Balder's/MartinL's code (http://forum.arduino.cc/index.php?topic=341655.msg2363464#msg2363464) and made the attached sketch which basically is a 16 counter that takes the clock and produces an interrupt every 1.2288000E-2 s (supertick) that are counted and printed.

This works fine on TC3 with the attached sketch (I needed to make one small modification as some definition was not made for GCLK.) I really want to minimise dead time spent in interrupt servicing so it would be a good idea to use 32-bit rather than 16 bit counters to generate the interrupts so that the dead-time is reduced. As TC4 and TC5 use a common clock this blocks TC5 for me. TC6 and TC7 are available it seems. However, I cannot get the sketch to work with TC6 because the compiler claims TC6 is not defined. I am using the 1.7.7 IDE from Arduino.org and a M0 PRO.

So to my humble, and perhaps dumb, questions:

  1. Which TCC and TC are defined in the Arduino IDE?

  2. Which TCC and TC can be reassigned and used safely? (TC4 seems to be used for delayMicroseconds() as this shares a common 48 MHz clock with TC5 I presume this is blocked too. ) I don not intend PWM for analogue output.

  3. How to I define TC6 and TC7 so I can use them as a 32-bit counter pair?

Harry J Whitlow

/////////////////////////////////////////////////////////////////////////
//
//
//   Real time clock with better than 0.5 µs accuracy
//
//
//
/////////////////////////////////////////////////////////////////////////

volatile uint32_t realTcount = 0x0 ;   // Counter for superticks (overflow interrupts)


void setup() {
Serial.begin(9600);

 setclockTC3();
 setCounterTC3();


}


/*
This is test code that counts to 10 s then resets and waits 3s to test 
reset, start and stop functions on TC3. 
*/
void loop() {
  
  delay(250);
  
    Serial.print(realTime(), 7);
    Serial.println();
    double val = realTime();
   
    if( int(val) >= 10)
    {
  resetStartTC3();  // reset so never gets above 10 s
      stopTC3();
    delay(3000); //wait 3 sec
    startTC3();
    }
    
}

void TC3_Handler()  // Interrupt on overflow
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    realTcount++;                    // Increment the supertick register
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
//  }
}

/*
Get the real time in seconds.
*/
double realTime() 
{   
double  realTime =  (realTcount * 1.2288E-2) + (REG_TC3_COUNT16_COUNT * 1.875E-7) ;;
return realTime;
}


/*
  Setup the Generic clock register
*/

void setclockTC3()
{
  // Setup and enable clock for TC
    REG_GCLK_GENDIV = GCLK_GENDIV_DIV(9) |    // Divide the 48MHz system clock by 9 = 5.33333MHz
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  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 GCLK 5
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     // Hack to overcome define problem in next line!!!
                     0x1B;                        // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
}

void setCounterTC3()
{
  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1;   // Set perscaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Interrupts
  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable overfollow interrup


  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn);

  // Enable TC
  TC->CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

}



void resetStartTC3()
{
   TcCount16* TC = (TcCount16*) TC3; // get timer struct
   realTcount = 0x00;                // Zero superclicks
    TC->CTRLBSET.reg |= TC_CTRLBCLR_CMD_RETRIGGER;   // restart
}

void stopTC3()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_STOP;   // Stop counter
}


void startTC3()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_RETRIGGER;   //  Start
}

I believe that the Arduino Zero core uses the Systick counter for millis(), micros(), delay() and delayMicroseconds(), so all the counters: TC3, TC4, TC5, TCC0, TCC1 and TCC2 are initially free to use. Their registers can all be accessed directly using the Arduino IDE, however manipulating them will break functions that use the timers, such as analogWrite().

TC6 and TC7 are not included in the SAMD21G18A microcontroller used on the Zero, they are only available on the larger SAMD21J variant.

Although TC4 and TC5 share the same generic clock (GCLK), they do have separate prescalers in their Control A (REG_TC4_CTRLA/REG_TC5_CTRLA) registers that can be used to independently divide the GCLK frequncy down further. In 8-bit counter mode, there's also a period (PER) register used to fine tune the counter frequency. 16 and 32-bit counter modes use the compare capture 0 (CC0) register instead, (in the datasheet it's described as match frequency operation).

You could also look at using the RTC peripheral for your clock. It's almost certainly not already in use (even for PWM), and works more naturally (IMO) for the sort of things you are doing.

https://github.com/WestfW/SAMD10-experiments/blob/master/D10-LED_TOGGLE0/src/UserSource/ticker_rtc.c

Hi, Thanks for the information. I investigated a bit and found that TC4 is used for the delay function in delay.c. I need to use delayMicroseconds so it is best for me best to leave the clock GCLK for TC4 untouched.

I don't need to use PCM analogue outputs so I planned to use the other TCCs and TCs for real time clocks.

I have run into the same problem as reported previously reported: http://forum.arduino.cc/index.php?topic=341655.0 namely, that I could not get the reading the count registers in TCC0, TCC1 and TCC2 to work. I am generating interrupts at these registers at the correct rate ( i.e. the count register is counting correctly ) but cannot I read the count values.

There is an errata on the TCC in the manual - but it only effects the event system. I did not find in the manual that some special procedure is needed to read these registers. Does anybody have any ideas?

I will probably end up using the RTC for time stamping and TC3 and TC5 to measure the deadtimes for my application. It would be simpler to work with the 24 bit, TCC0/1 however.

I attach the code that shows the problem.

Harry J. Whitlow

volatile long superTicks = 0x00 ;         // Supertick register


void setup() {
  Serial.begin(9600);

   // Setup and enable clock for TC
    REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) |    // Divide the 48MHz system clock by 4 = 12 MHz
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  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 GCLK 5
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     // Hack to overcome define problem in next line!!!
                     0x1A;                        // Feed the GCLK5 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization



  // Enable standard clock for TC0_TCC1
// REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 0x1A  ) ;
// while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync


  // The type cast must fit with the selected timer
  Tcc* TC = (Tcc*) TCC0; // get timer struct

  TC->CTRLA.reg &= ~TCC_CTRLA_ENABLE;   // Disable TC
  while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync

   TC->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1;   // Set prescaler

  TC->WAVE.reg |= TCC_WAVE_WAVEGEN_NFRQ;   // Set wave form normal freq.
  while (TC->SYNCBUSY.bit.WAVE == 1); // wait for sync

 TC->PER.reg = 0xFFFF;              // Set counter Top using the PER register 
 while (TC->SYNCBUSY.bit.PER == 1); // wait for sync

//  TC->CC[0].reg = 0xFFF;
 // while (TC->SYNCBUSY.bit.CC0 == 1); // wait for sync

  // Interrupts
  TC->INTENSET.reg = 0;                 // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable only overfollow

  // Enable InterruptVector
  NVIC_EnableIRQ(TCC0_IRQn);

  // Enable TC
  TC->CTRLA.reg |= TCC_CTRLA_ENABLE ;
  while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync

}

void loop() {
  // dummy
  delay(250);
  Serial.println(realTime(), 7);
}

void TCC0_Handler()
{
  Tcc* TC = (Tcc*) TCC0;       // get timer struct
  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    superTicks++ ;                  // Increlent superTick register
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf fla
  }
}



double realTime()
{
   Tcc* TC = (Tcc*) TCC0;       // get timer struct
   while (TC->SYNCBUSY.bit.PER == 1); // wait for sync
   uint32_t count = REG_TCC0_COUNT;
   Serial.println(count, HEX);
  
   count = count>> 4; 
   double  t = (8.33333333333333333333E-08 * count) + (1.3981013333333333333E+00 * superTicks);
   return t;
}

I investigated a bit and found that TC4 is used for the delay function in delay.c.

Interesting. The arduino.org code for M0/etc uses TC4 for delayMicroseconds() The arduino.cc code for Zero/etc uses a spin loop. Both use the Systick timer for the regular delay() function.