Create a timer function with frequency

Hi. I use this code I found here, to create a timer which works.

int pin_ovf_led = 13;  // debug pin for overflow led 
int pin_mc0_led = 5;  // debug pin for compare led 
unsigned int loop_count = 0;
unsigned int irq_ovf_count = 0;

void setup() {

  pinMode(pin_ovf_led, OUTPUT);   // for debug leds
  digitalWrite(pin_ovf_led, LOW); // for debug leds
  pinMode(pin_mc0_led, OUTPUT);   // for debug leds
  digitalWrite(pin_mc0_led, LOW); // for debug leds

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

  // 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_DIV256;   // Set perscaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync 
  
  // TC->PER.reg = 0xFF;   // Set counter Top using the PER register but the 16/32 bit timer counts allway to max  
  // while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync 

  TC->CC[0].reg = 0xFFF;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync 
  
  // Interrupts 
  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable overfollow
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0

  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn);

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

}

void loop() {
  // dummy
  delay(250);
}

void TC3_Handler()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
    digitalWrite(pin_mc0_led, HIGH); // for debug leds
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
    irq_ovf_count++;                 // for debug leds
  }
  
  if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
    digitalWrite(pin_mc0_led, LOW);  // for debug leds
    TC->INTFLAG.bit.MC0 = 1;    // writing a one clears the flag ovf flag
  }
}

The led is blink, but I am trying to understand where you set: 1. Top counting point 2. Pre scales

So, changing this :

TC->CC[0].reg = 0xFFF;

To other values, such as 000, will not do anything. Also I am trying to understand where and how to set the pre scale,is it here?:

TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;

Then, last thing, what normal frequency on this row even means:

TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ;

Bottom line I am trying to make a simple method that will get frequency and provide a timer callbacks.

Any help understanding things would be great.

Here's some code I posted in an earlier thread. It's an example of using Timer Counter 4 (TC4) in 8-bit mode on the SAMD21:

void setup() {
   // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    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_CTRLA |= TC_CTRLA_MODE_COUNT8;           // Set the counter to 8-bit mode
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  REG_TC4_COUNT8_CC0 = 0x55;                      // Set the TC4 CC0 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_CC1 = 0xAA;                      // Set the TC4 CC1 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_PER = 0xFF;                      // Set the PER (period) register to its maximum value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);    // 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_MC1 | TC_INTFLAG_MC0 | TC_INTFLAG_OVF;        // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0 | TC_INTENSET_OVF;     // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0 | TC_INTENCLR_OVF;     // Disable TC4 interrupts

  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV64 |     // Set prescaler to 64, 16MHz/64 = 256kHz
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
}

void loop() {
  // put your main code here, to run repeatedly:

}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{     
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT8.INTFLAG.bit.OVF && TC4->COUNT8.INTENSET.bit.OVF)             
  {
    // Put your timer overflow (OVF) code here:     
    // ...
   
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0)             
  {
    // Put your counter compare 0 (CC0) code here:
    // ...
   
    REG_TC4_INTFLAG = TC_INTFLAG_MC0;         // Clear the MC0 interrupt flag
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC1 && TC4->COUNT8.INTENSET.bit.MC1)           
  {
    // Put your counter compare 1 (CC1) code here:
    // ...
   
    REG_TC4_INTFLAG = TC_INTFLAG_MC1;        // Clear the MC1 interrupt flag
  }
}

In this example the timer overflows 1000 timer per second, (48MHz/3= 16MHz, 16MHz/64=256kHz, 256kHz/(255 + 1)=1kHz).

The PER (period) register only sets the timer's TOP value in 8-bit mode. In 16-bit mode the PER register has no effect, therefore in this mode you lose the ability to choose the top counting point. However, you can still change the timer's clock frequency, by either altering the generic clock divisor (GCLK_GENDIV_DIV(x)), or the using the timer's own prescaler.

Martin thanks again (I really appreciate the fact that you can get all of this from the data sheet) So whats the different between your timer (in general) and the one I'v posted in the other thread ? (except from the pre scale of 64)

So whats the different between your timer (in general) and the one I'v posted in the other thread ?

The example I provide uses generic clock 4 (GCLK4), this clock (unlike GCLK 0 to 3) is unused by the Arduino core code, so you can set its source to whatever you require, (in this case 48MHz) and divide it down further using the generic clock divisor, (in this case 48MHz/3=16MHz). It just gives you a little bit of extra control over your timer frequency. You can then use the timer prescaler to divide down the GCLK further.

Also, in the interrupt handler I've checked both the interrupt flag and the interrupt enable:

// Check for match counter 0 (MC0) interrupt
if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0)

If you don't do this and just use the interrupt flag only, the overflow (OVF), counter compare 0 (CC0), and counter compare (CC1) routines can interfere with each other.

Great thanks, so this arise a very interesting question, if you want to generate a sine wave, and change frequencies along the way( fast enough, even every 2pi), there are 2 math approaches:

  1. Create the fastest timer possible, and treat it as a continues clock, then change the array of your sine wave points according to a wanted frequency(e.g bigger array(0-2pi) means lower frequency)

2.Create one sine wave array, a general sine form, then change the timer frequency to plot it out in different rates according to wanted frequencies .

Math speaking,if in option 1 the timer is fast enough relative to the wanted sine frequency, you get a pure sine signal, exactly the same as option 2, where you change the sample rate .

Am i right? which you prefer ?

which you prefer ?

I guess it depends on your requirements for the sine wave's frequency, sample rate and amplitude resolution, but if possible I'd certainly try to use only a single look-up table, or alternatively if speed isn't an issue, just use the "sin" function.

Hi BenStlr,

I haven't tested it, but the TCC0 code will go something like this:

// Setup TCC0 in normal frequency mode (NFRQ)
void setup() 
{ 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by 1 = 48MHz
                    GCLK_GENDIV_ID(4);      // Set division on Generic Clock Generator (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 GCLK 4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz 
                     GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

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

  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);         // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)
  
  REG_TCC0_INTFLAG = TCC_INTFLAG_MC3 | TCC_INTFLAG_MC2 | 
                     TCC_INTFLAG_MC1 | TCC_INTFLAG_MC0 | TCC_INTFLAG_OVF;        // Clear the interrupt flags
  REG_TCC0_INTENSET = TCC_INTENSET_MC3 | TCC_INTENSET_MC2 | 
                      TCC_INTENSET_MC1 | TCC_INTENSET_MC0 | TCC_INTENSET_OVF;     // Enable TCC0 interrupts
  // REG_TCC0_INTENCLR = TCC_INTENCLR_MC3 | TCC_INTENCLR_MC2 | 
  //                    TCC_INTENCLR_MC1 | TCC_INTENCLR_MC0 | TCC_INTENCLR_OVF;     // Disable TCC0 interrupts
  
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NFRQ;         // Setup normal frequency operation on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  REG_TCC0_PER = 0xFFFFFF;                        // Set the frequency of the timer
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  REG_TCC0_CC0 = 0x000000;                        // Set counter compare 0
  while(TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
  REG_TCC0_CC1 = 0x000000;                        // Set counter compare 1
  while(TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
  REG_TCC0_CC2 = 0x000000;                        // Set counter compare 2
  while(TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization
  REG_TCC0_CC3 = 0x000000;                        // Set counter compare 3
  while(TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization
  
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Set prescaler to 1, 48Mhz
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {}

void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for overflow (OVF) interrupt
  if (TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)             
  {
    // Put your timer overflow (OVF) code here:     
    // ...
   
    REG_TCC0_INTFLAG = TCC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0 && TCC0->INTENSET.bit.MC0)             
  {
    // Put your counter compare 0 (CC0) code here:
    // ...
   
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC0;         // Clear the MC0 interrupt flag
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1 && TCC0->INTENSET.bit.MC1)           
  {
    // Put your counter compare 1 (CC1) code here:
    // ...
   
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC1;        // Clear the MC1 interrupt flag
  }

  // Check for match counter 2 (MC2) interrupt
  if (TCC0->INTFLAG.bit.MC2 && TCC0->INTENSET.bit.MC2)             
  {
    // Put your counter compare 2 (CC2) code here:
    // ...
   
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC2;         // Clear the MC0 interrupt flag
  }

  // Check for match counter 3 (MC3) interrupt
  if (TCC0->INTFLAG.bit.MC3 && TCC0->INTENSET.bit.MC3)           
  {
    // Put your counter compare 3 (CC3) code here:
    // ...
   
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC3;        // Clear the MC1 interrupt flag
  }
}

I have made a sophisticated timer library for the Arduino SAMD that makes life easier: http://www.avdweb.nl/arduino/libraries/samd21-timer.html

The library is easy to use; the following code generates a square wave of 1Hz to pin 5 of the Arduino Zero: SAMDtimer mytimer1 = SAMDtimer(3, TC_COUNTER_SIZE_16BIT, 5, 1e6);