PER Confusion

I'm using MartinL's basic example code from this thread and it works as expected.

I'm toggling pin 13 up and down directly and looking at it on my scope. Since I'm doing this during an interrupt I get slightly less than the theoretical frequency (i.e. 187.394 kHz instead of 187.5 kHz, but that's not the issue.)

When I change both the main and timer clock divisor's to 1 with the PER at 0xFF I get what's expected: 48E6 / (1 * 1 * 256) = 187.5 kHz.

Confusion: With both CC0 and CC1 set to 0 and only toggling during overflows I would expect the frequency to increase as I decrease PER. But in fact any changes I make to PER have no effect on the output frequency. When I set PER To 0xF0 I would expect the output frequency to become 48E6 / (1 * 1 * 240) = 200 kHz, but it doesn't change, code attached.

I've read through the data sheet several times and gone over forum posts but am still confused. Thank you in advance, I'm still relatively new to electronics and these forums are a huge asset.

int testPin = 13;

void setup() {

  pinMode(testPin, OUTPUT);
  
                                                  // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // 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_COUNT8_CC0 = 0x00;                      // Set the TC4 CC0 register to some arbitary value 55
      while (TC4->COUNT8.STATUS.bit.SYNCBUSY);    // Wait for synchronization
  REG_TC4_COUNT8_CC1 = 0x00;                      // Set the TC4 CC1 register to some arbitary value AA
      while (TC4->COUNT8.STATUS.bit.SYNCBUSY);    // Wait for synchronization



      
  REG_TC4_COUNT8_PER = 0xF0;                       // ************************************************************* 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_DIV1 |      // Set prescaler to 64, 16MHz/64 = 256kHz
                   TC_CTRLA_MODE_COUNT8 |         // Set the counter to 8-bit mode
                   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_PORT_OUT0 |= PORT_PA17;                    // debug pin 13 HIGH
   REG_PORT_OUT0 &= ~PORT_PA17;                   // debug pin 13 LOW
   
   
   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
  }
}

Hi Mechanical_Lumber,

I found the problem.

In my original example (that I've now corrected), I should've set the timer to 8-bit mode before setting the PER register. My apologies for that and thanks for pointing it out.

The following code corrects this:

void setup() {

  REG_PORT_DIRSET0 = PORT_PA17;                   // Set D13 as an output
  
  // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // 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 = 0x00;                      // Set the TC4 CC0 register to some arbitary value 55
      while (TC4->COUNT8.STATUS.bit.SYNCBUSY);    // Wait for synchronization
  REG_TC4_COUNT8_CC1 = 0x00;                      // Set the TC4 CC1 register to some arbitary value AA
      while (TC4->COUNT8.STATUS.bit.SYNCBUSY);    // Wait for synchronization    
  REG_TC4_COUNT8_PER = 0x7F;                      // Set the PER (period) register to half 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_DIV2 |      // 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_PORT_OUTSET0 = PORT_PA17;
   REG_PORT_OUTCLR0 = PORT_PA17;
   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
  }
}

Thank you, I was beginning to think that the PER only applied to built-in waveform generation.

There seems to be an upper limit though, decreasing PER only works to about 400 kHz and then changes make no difference. Is this just a limitation of the processor in this mode?

I also get significant jitter on some settings (see images), that doesn't seem to follow any particular pattern. With such a simple loop I would expect them to be quite stable.

I was doing alright with the Uno and Mega Timers but this Zero has stumped me on many occasions.

Thanks again for your time,

Scott

Hi Scott,

There seems to be an upper limit though, decreasing PER only works to about 400 kHz and then changes make no difference. Is this just a limitation of the processor in this mode?

400kHz is way beyond the capabilites of my ancient multimeter, so I'm unable to test this myself. Have you tried enabling only the overflow (OVF) interrupt and disabling the CC0 and CC1? This way the interrupt handler will only be called one time rather than three times per timer period.

I also get significant jitter on some settings (see images), that doesn't seem to follow any particular pattern. With such a simple loop I would expect them to be quite stable.

If you need a stable jitter free output, as you probably already know, you can do this by using the timer's PWM output.