16Mhz clock from Nano IOT?

I'm trying to find the easiest way to get a 16Mhz clock from the Nano IOT.  I found code on this forum and I've copied it below and I believe it does the job.  However, as a newbie, it seems like overkill.  I thought there would be a simpler way to get the clock, divide the cycles by 3, push it to TCC0 and TCC1.  I used GCLK4 b/c that is what I've seen, but thought there was a way to use GCLK0...

   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);

     REG_GCLK_GENCTRL = 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 TCC0 and TCC1
         GCLK_CLKCTRL_GEN_GCLK4 |                       // Select GCLK4
         GCLK_CLKCTRL_ID_TCC0_TCC1;                     // Route GCLK4 to TCC0 and TCC1
     while (GCLK->STATUS.bit.SYNCBUSY);                 // Wait for synchronization

Hi @tecroswell56

The following code outputs GCLK4 at 16MHz on (the Nano 33 IoT's) D9 (PA20):

// Output GCLK4 at 16MHz on PA20 (Nano 33 IoT digital pin D9)
void setup() 
{
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |         // Divide the 48MHz clock source by divisor 1: 48MHz/3=16MHz
                     GCLK_GENDIV_ID(4);           // Select GCLK4

  GCLK->GENCTRL.reg = GCLK_GENCTRL_OE |           // Enable GCLK4 output
                      GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |        // Enable GCLK3
                      GCLK_GENCTRL_SRC_DFLL48M |  // Set the 48MHz DFLL48M clock source
                      GCLK_GENCTRL_ID(4);         // Select GCLK4's ID
  
  PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;               // Switch on the port multiplexer on PA20
  PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_H;  // Activate the GCLK_IO[4] on this pin 
}

void loop() {}

MartinL;

Thanks so much for the response. I tweaked the code you provided and have something that works. In doing so, I have another problem. Basically, I'm trying to understand PWM on the SAMD21. I have sample code that turns on LEDs in a timed way. First pin 5 goes on, then pin 6. The timer counts up to TOP (PER) and then counts down and pin 6 goes off, followed by pin 5. It seems to work as expected.

However, I tried to add an ISR to see if I understood how that worked and that's where I ran into trouble. The ISR does properly handle (I think) the OVF condition which should be at DSBOTTOM. In addition the ISR seems to catch the compare/match on the upcount, when the LEDs are turned on. But it doesn't appear that I am receiving an interrupt compare/match on the down count for some reason. I must be missing something simple. I attached the code as it seemed to be too long.

PWMDCCDualSlope1SEC.3.ino (5.5 KB)

Hi @tecroswell56

You're not missing anything. The behaviour that you're observing is consistent with the SAMD21's datasheet.

The paragraph containing this piece of information is at the bottom of section 31.6.2.5.6 Dual-Slope PWM Generation and says:

Note: In DSTOP, DSBOTTOM and DSBOTH operation, when TOP is lower than MAX/2, the CCx MSB bit defines the ramp on which the CCx Match interrupt or event is generated. (Rising if CCx[MSB] = 0, falling if CCx[MSB] = 1.)

Looking at your code's PER register, the compare match interrupts will only be generated on the rising ramp of the dual slope.

I saw that note, but confess I didn't really understand it. Thanks for letting me know.

MartinL
Apologies - I could not figure out how to set the MSB for CCx to 1. Can you offer a quick line of code that will do this? I successfully hung my NANO messing about... LOL.

Out of curiosity, is there also a way to set it so that the match compare happens on both the upcount and downcount? In my case, I would need to have it on the downcount but was wondering.

Lastly - in the code that I attached previously, I printed out the value of COUNT in the ISR. When it hits bottom and overflows, COUNT is actually about 4000 or so. I assume that this is because it has begun to upcount again. If I wanted to change the PER, and if the frequence was much higher than in my example code, this seems like it could be a problem. I was thinking that instead of altering PER/PERB at BOTTOM, I would alter it during the downcount when the match occurred. If I understand it correctly, setting PERB at that point, would automatically change PER when it hits bottom. I have not looked at much code at this point that tries to change PER/PERB during operation...

I removed a bunch of print statements and that reduced the COUNT dramatically, to less than 20 or so during the ISR caused by an OVF. Makes sense.

Hi @tecroswell56

Yes it's possible. The TCC timer's Dual-Slop Critical (DSCRITICAL) mode can be used gain control of both the rising and falling edge of the ramps.

In DSCRITICAL mode channels CC0 and CC2 are used to control the rising and falling ramps respectively, with output on CC0. Likewise for channels CC1 and CC3, with output on CC1.

Here's an example of using DSCRITCAL mode with CC0/CC2 output on the Nano 33 IoT's D6 and CC1/CC3 on D5. I've also include reference pulse on D7 everytime an overflow occurs to act as a reference point, as sometimes it can be difficult to pinpoint where the time cycles starts and finishes. I've set the period to 1 second:

// Setup TCC0 for dual-slop critical (DSCRTICAL) PWM on digital pins D5 and D6 with reference pulse on D7
void setup() 
{ 
  SerialUSB.begin(115200);                         // Intialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open

  pinMode(7, OUTPUT);                              // Set D7 to an output to act as reference point for DSCRITICAL waveform
  
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz signal by 3: 48MHz/3 = 16MHz
                     GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (GCLK) 4
  
  GCLK->GENCTRL.reg = //GCLK_GENCTRL_OE |            // Enable the GCLK output
                      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 DFLL
                      GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   
  
  // Enable the port multiplexer for pins D5 and D6
  PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
  //PORT->Group[g_APinDescription[A3].ulPort].PINCFG[g_APinDescription[A3].ulPin].bit.PMUXEN = 1;
  //PORT->Group[g_APinDescription[A4].ulPort].PINCFG[g_APinDescription[A4].ulPin].bit.PMUXEN = 1;
  
  // D5 is on ODD port PA05, D6 is on EVEN port pin PA04, both on multiplexer seitch E
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  //PORT->Group[g_APinDescription[A4].ulPort].PMUX[g_APinDescription[A4].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
  
  NVIC_SetPriority(TCC0_IRQn, 0);                  // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);                       // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TCC0->INTENSET.reg = TCC_INTENSET_OVF |          // Enable OVF, MC2 and MC0 interrupts
                       //TCC_INTENSET_MC3 | 
                       TCC_INTENSET_MC2 |           
                       //TCC_INTENSET_MC1 | 
                       TCC_INTENSET_MC0;            
  
  TCC0->WAVE.reg = //TCC_WAVE_POL(0xF) |             // Change the polarity of TCC channels 0, 1, 2 and 3
                   TCC_WAVE_WAVEGEN_DSCRITICAL;    // Enable Dual-Slope Critical PWM (DSCRITIAL) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
  
  TCC0->PER.reg = 500000;                          // Set the timer output frequency to 1Hz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
  
  TCC0->CC[0].reg = 250000;                        // Set the timer output to change after 250ms on the rising ramp
  while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
  
  TCC0->CC[2].reg = 375000;                        // Set the timer output to change after 625ms on the falling ramp
  while (TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization

  TCC0->CC[1].reg = 250000;                        // Set the timer output to change after 250ms on the rising ramp
  while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
  
  TCC0->CC[3].reg = 125000;                        // Set the timer output to change after 875ms on the falling ramp
  while (TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization
  
  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |    // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV16;     // Set prescaler to 16, 16MHz/16 = 1Mhz
                    
  TCC0->CTRLA.bit.ENABLE = 1;                      // 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->INTFLAG.bit.OVF = 1;                     // Clear the interrupt flag
    digitalWrite(7, HIGH);                         // Set the reference pulse HIGH
    SerialUSB.println(F("OVF"));
  }
  
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    TCC0->INTFLAG.bit.MC0 = 1;                     // Clear the interrupt flag
    SerialUSB.println(F("MC0"));
  }

  // Check for match counter 2 (MC2) interrupt
  if (TCC0->INTFLAG.bit.MC2)           
  {
    TCC0->INTFLAG.bit.MC2 = 1;                     // Clear the interrupt flag
    SerialUSB.println(F("MC2"));
  }
  digitalWrite(7, LOW);                            // Clear the reference pulse LOW
}

Note that I tested this on my Arduino Zero, as I don't own a Nano 33 IoT, hence the commented out references to the other pins, plus the SerialUSB.

Strictly speaking the Serial output shouldn't be used in an interrupt service routine, but at 1 second period and for this example it works OK.

Here's the example's output, D7 overflow reference pulse (yellow), D6 CC0 channel (cyan), D5 CC1 channel (pink):

MartinL
Thanks for all your help.

Just to be pedantic - per your suggestion, the following code changed the setting so that the downcount match occurred. At least it seems to be working for me. In the code in the attached file, CCO was set to 250000 and CC1 was set to 150000. Setting the most significant bit changed this to a match on down count.

REG_TCC0_CC0 |= (1<<23);
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization

...

REG_TCC0_CC1 |= (1<<23);
while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization