Generating varying frequencies on pin PA21 for ATSAMD21G18AU

Hello,

I am quite new to programming for Arduino. I need to generate varying clock frequencies on pin PA 21 ranging from 500 kHz down to 3906.25 Hz.

I have tried to generate code for 500 kHz but as of now I cannot make it work.

Here is my code,

void setup()
// Setup the program
{

  GCLK->GENCTRL.reg = GCLK_GENCTRL_OE |                // Enable GCLK5 output
                      GCLK_GENCTRL_IDC |               // Improve duty-cycle to 50%
                      GCLK_GENCTRL_GENEN |             // Enable generic clock
                      GCLK_GENCTRL_SRC_OSC8M  |       // Select 8 MHz as source   \
                      GCLK_GENDIV_DIV(16)             // Divide by 16 to produce 500 kHz         
                      GCLK_GENCTRL_ID(5);              // Set the GCLK ID to GCLK5
  while (GCLK->STATUS.bit.SYNCBUSY);                   // Wait for synchronization
 
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;        // Switch on port pin PA21's multiplexer
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= PORT_PMUX_PMUXE_H;  // Switch the PA21's port multiplexer to GCLK IO
}

void loop()
{
}

For some reason, testing this with an oscilloscope doesn't produce anything. It only produces noise.

Chip used is ATSAMD21G18A for Spark fun Dev

Please follow the advice given in the link below when posting code . Use code tags when posting code here to make it easier to read and copy for examination

Apologies UKHeliBob, I hope that fixes the issue.

@mnsc Here's some example code that generates PWM at 500Hz on PA21 and switches between 25% and 75% duty-cycles every second:

// Set up TCC0 channel 3 to output single slope PWM on PA21 at 500Hz
void setup()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |         // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                     GCLK_GENDIV_ID(4);           // Select Generic Clock (GCLK) 4  

  GCLK->GENCTRL.reg = 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

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK 4 to timers TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;

  // Enable the port multiplexer for the TCC0 PWM channel 3 (digital pin D7), SAMD21 pin PA21
  //PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  
  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  //PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXO_F;
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXO_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Set up single slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Formula: PWM frequency = freq(GCLK) / (timer prescaler * (PER + 1)) 
  // Therefore PWM frequency = 48MHz / (1 * (95999 + 1)) = 500Hz
  TCC0->PER.reg = 95999;                          // Set the frequency of the PWM on TCC0 to 500Hz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

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

  //TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |   // Set the TCC0 timer to overflow on the next prescaler clock (rather than GCLK)
  //                  TCC_CTRLA_PRESCALER_DIV1;     // Divide the GLCK by the timer prescaler

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

void loop() 
{ 
  TCC0->CCB[3].reg = 24000;                        // Set the duty-cycle to 25% using buffered CCBx register
  while (TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization           
  delay(1000);                                     // Wait for 1 second
  TCC0->CCB[3].reg = 72000;                        // Set the duty-cycle to 75% using buffered CCBx register
  while (TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  delay(1000);                                     // Wait for 1 second
}

@mnsc Also, if you need to change PWM frequency and duty-cycle during operation:

  TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;         // Set the Lock Update bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization
  TCC0->PERB.reg = 47999;                         // Set the frequency of the PWM on TCC0 to 1000Hz
  while(TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization
  TCC0->CCB[3].reg = 24000;                       // Set the duty-cycle to 50%
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;         // Clear the Lock Update bit
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization

Hello Martin,

Based from my understanding, this code sets the Pulse width modulation frequency to 500 Kilo Hertz,

void setup()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |         // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                     GCLK_GENDIV_ID(4);           // Select Generic Clock (GCLK) 4  

  GCLK->GENCTRL.reg = 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

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK 4 to timers TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;

  // Enable the port multiplexer for the TCC0 PWM channel 3 (digital pin D7), SAMD21 pin PA21
  //PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  
  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  //PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXO_F;
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXO_F;

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Set up single slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Formula: PWM frequency = freq(GCLK) / (timer prescaler * (PER + 1)) 
  // Therefore PWM frequency = 48MHz / (1 * (95 + 1)) = 500 KHz
  TCC0->PER.reg = 95;                          // Set the frequency of the PWM on TCC0 to 500 KHz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

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

  //TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |   // Set the TCC0 timer to overflow on the next prescaler clock (rather than GCLK)
  //                  TCC_CTRLA_PRESCALER_DIV1;     // Divide the GLCK by the timer prescaler

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

void loop()
{
}

For clarification purposes, I hope this is right. Basically, changing the TCC0->PER.reg parameter is for changing the frequency and changing the TCCO->CC[3].reg changes the duty cycle.

Many thanks for your reply by the way.

A follow up question also, do I have to set the pin as OUTPUT with pinMode?

Hi @mnsc

Based from my understanding, this code sets the Pulse width modulation frequency to 500 Kilo Hertz,

Yes that's right, setting the PER register to 95 will set the timer to run at 500kHz.

If you require more resolution than this, it's possible to invoke the SAMD21's 96MHz Fractional Digital Phase Locked Loop (FDPLL96M). This will essentially double your resolution. Timers TCC0, TCC1, TCC2 and TC3 can operate at this higher clock frequency. I can provide the code for that, if required?

For clarification purposes, I hope this is right. Basically, changing the TCC0->PER.reg parameter is for changing the frequency and changing the TCCO->CC[3].reg changes the duty cycle.

Yes that's right, changes to the CCx register will change the duty-cycle and likewise changes to the PER register the period (or frequency).

However, changes to the unbuffered PER and CCx registers take effect immediately at timer's output. This can lead to glitches on the output waveform. Using the buffered PERB and CCBx registers prevent this by updating the timer only at the end of the timer cycle, (or in other words, timer overflow when the timer COUNT reaches PER register value + 1).

To prevent the small possibility of writes to PERB and CCBx registers from occuring on different timer cycles, the timer's updates can be blocked by setting the LUPD bit in the timer's CTRLBSET (set) register prior to writing to the PERB/CCBx registers then releasing it again by setting the LUPD bit in the CTRLBCLR (clear) register afterwards.

A follow up question also, do I have to set the pin as OUTPUT with pinMode?

No, unlike the AVR Arduino boards, on the SAMD21 it isn't necessary to specify that the pin is an OUTPUT with the pinMode() function.

@mnsc To use the 96MHz Fractional Digital Phase Locked Loop (FDPLL96M) just add these lines to the beginning of your sketch:

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock
                      GCLK_CLKCTRL_GEN_GCLK1 |     // Select GCLK1 using either external XOSC32K or internal OSC32K oscillator depending on the board
                      //GCLK_CLKCTRL_GEN_GCLK2 |     // Select GCLK2 using the OSCULP32K ultra low power 32k oscillator
                      GCLK_CLKCTRL_ID_FDPLL;       // Connect GCLK1 to GCLK_DPLL input
  
  SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_REFCLK_GCLK;     // Select GCLK_DPLL as the clock source
  
  SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(11) |    // Generate a 96MHz DPLL clock source from the external 32kHz crystal
                           SYSCTRL_DPLLRATIO_LDR(2928);       // Frequency = 32.768kHz * (2928 + 1 + 11/16) = 96MHz
  
  SYSCTRL->DPLLCTRLA.reg = SYSCTRL_DPLLCTRLA_ENABLE;          // Enable the Digital Phase Locked Loop (DPLL)
  while (!SYSCTRL->DPLLSTATUS.bit.LOCK);                      // Wait for the DPLL to achieve lock

Then change the clock source for GCLK4 from the 48MHz DFLL (GCLK_GENCTRL_SRC_DFLL48M) to the 96MHz FDPLL (GCLK_GENCTRL_SRC_FDPLL):

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |        // Enable the generic clock
                      GCLK_GENCTRL_SRC_FDPLL |    // Set the clock source to FDPLL96M at 96MHz
                      GCLK_GENCTRL_ID(4);         // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

Thank you for providing the code @MartinL . I really appreciate it.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.