SAMD21 PWM with the FDPLL

Arduino Zero (SAMD21) FDPLL with CMSIS

One of the coolest, tucked-away features in the SAMD21 is the fractional digital phase locked loop (FDPLL). I still only have a vague idea of how phase locked loops (PLLs) work, but nonetheless, we can use the SAMD21’s PLL to create a 96 MHz clock.

The bad news is that, to the best of my knowledge, you cannot use this clock signal as the main clock source for the SAMD21 (essentially overclocking it). However, we can use it to power some other peripherals, like the generic clocks, timers, and pulse width modulation (PWM) signals.

If you are not familiar with how the clocks on the SAMD21 work, I recommend reading my previous post. You will also need to refer to the SAMD21 datasheet.

Let’s take a look at how you use the FDPLL on the SAMD21.

Step 1: Select a Clock Source

The FDPLL isn’t a standalone clock. You’ll need to select a source to feed the PLL, and the source clock needs to be in the 32 kHz to 2 MHz range. According to section 16.8.19 (page 208) of the datasheet, there are only 3 acceptable clock sources for the FDPLL: XOSC32, XOSC, and a specific GCLK (known as GCLK_DPLL). You can set the clock source for the PLL in the DPLLCTRLB register.

Clock sources for DPLL on SAMD21

Step 2: Configure FDPLL

Next, you will need to set the frequency multiplier in DPLLRATIO register. There are two numbers you must configure: the ratio (LDR bits) and the ratio fractional part (LDRFRAC bits). The equation below (from section 16.6.8.3, page 158) shows how to calculate the output frequency of the FDPLL. fclk_fdpll96m is the output frequency and fclk_fdpll96m_ref is the reference clock (the input clock source).

SAMD21 FDPLL equation

So, if we want to turn a 1 MHz clock source into a 96 MHz output, we would set LDR to 95 and LDRFRAC to 0.

Step 3: Turn It On

By default, the FDPLL is disabled on reset. To enable it, we just write a 1 to the ENABLE bit in the DPLLCTRLA register.

Step 4: Use It!

The FDPLL can be used as a clock source for any of the generic clocks found in the generic clock controller. This includes the USB clock, watchdog timer, clock channels, SERCOMs, etc. Section 14.8.3 (page 107) has a table (too big to copy here) that gives you an idea of the available GCLKs. If you use one of the GCLK channels, you can use it to clock a peripheral, like PWM!

Example: A More Complicated 1 MHz Signal

In my last post, I showed you how to create a 1 MHz signal using GCLK4, TCC2, and a PWM peripheral. Let’s do the same thing, but in a much more roundabout way!

We’ll feed GCLK4 from the main 48 MHz clock, divide the clock by 48, feed that signal to GCLK_DPLL, which is then sent to the FDPLL. The DPLL will create a 96 MHz clock signal, which we’ll send to GCLK5 to be used as the basis for TCC2 and our PWM signal. This diagram should offer a much better explanation of what’s going on:

SAMD21 PWM with the FDPLL

Because our PWM clock is running twice as fast as in the previous example, we’ll need to adjust the TOP to be twice as high. We’ll use 95 instead of 47 to achieve our 1 MHz signal.

Here is the Arduino code to accomplish this:

// I have no idea why these definitions were left out of the Arduino headers
#define GCLK_GENCTRL_SRC_DPLL96M_Val 0x8ul  // (GCLK_GENCTRL) DPLL96M output
#define GCLK_GENCTRL_SRC_DPLL96M (GCLK_GENCTRL_SRC_DPLL96M_Val << \
                                  GCLK_GENCTRL_SRC_Pos)

// Number to count to with PWM (TOP value). Frequency can be calculated by
// freq = GCLK4_freq / (TCC2_prescaler * (1 + TOP_value))
// With TOP of 95, we get a 1 MHz square wave in this example.
// TCC2 is 16 bits
uint16_t period = 96 - 1;
 
void setup() {

  // Flash wait states NVMCTRL->CTRLB.bit.RWS set to 1 by Arduino framework

  // Configure generic clock generator 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DFLL48M |  // Select 48MHz as source
                      GCLK_GENCTRL_ID(4);         // Apply to GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 48 to generic clock generator 4 (1 MHz)
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(48) |        // Divide 48 MHz by 48
                     GCLK_GENDIV_ID(4);           // Apply to GCLK4 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable GCLK4 and connect it to GCLK_DPLL
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4
                      GCLK_CLKCTRL_ID(1);         // Feed GCLK4 to GCLK_DPLL
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set DPLL ratio to 1 MHz * (95 + 1) = 96 MHz
  SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(0) | // Ratio fractional
                           SYSCTRL_DPLLRATIO_LDR(95);     // Ratio

  // Configure DPLL to disregard phase lock and select GCLK as source
  SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_LBYPASS |    // Bypass lock
                           SYSCTRL_DPLLCTRLB_WUF |        // Wake up fast
    SYSCTRL_DPLLCTRLB_REFCLK(SYSCTRL_DPLLCTRLB_REFCLK_GCLK_Val); // Select GCLK
  
  // Enable DPLL
  SYSCTRL->DPLLCTRLA.reg |= SYSCTRL_DPLLCTRLA_ENABLE;
  
  // Configure generic clock generator 5
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DPLL96M |  // Select DPLL as source
                      GCLK_GENCTRL_ID(5);         // Apply to GCLK5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 1 to generic clock generator 5 (96 MHz)
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |        // Divide 96 MHz by 1
                     GCLK_GENDIV_ID(5);           // Apply to GCLK5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable GCLK5 and connect it to TCC2 and TC3
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK5 |    // Select GCLK5
                      GCLK_CLKCTRL_ID_TCC2_TC3;   // Feed GCLK5 to TCC2/TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  // Divide counter by 1 giving 96 MHz (10.42 ns) on each TCC2 tick
  TCC2->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);

  // Use "Normal PWM" (single-slope PWM): count up to PER, match on CC[n]
  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Select NPWM as waveform
  while (TCC2->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Set the period (the number to count to (TOP) before resetting timer)
  TCC2->PER.reg = period;
  while (TCC2->SYNCBUSY.bit.PER);

  // Set PWM signal to output 50% duty cycle
  // n for CC[n] is determined by n = x % 4 where x is from WO[x]
  TCC2->CC[0].reg = period / 2;
  while (TCC2->SYNCBUSY.bit.CC0);

  // Configure PA16 (D11 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA16;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA16;      // Set pin to low

  // Enable the port multiplexer for PA16
  PORT->Group[PORTA].PINCFG[16].reg |= PORT_PINCFG_PMUXEN;

  // Connect TCC2 timer to PA16. Function E is TCC2/WO[0] for PA16.
  // n for PMUX[n] is given by floor(pin_num / 2)
  // Odd pin num (2*n + 1): use PMUXO
  // Even pin num (2*n): use PMUXE
  PORT->Group[PORTA].PMUX[8].reg = PORT_PMUX_PMUXE_E;

  // Enable output (start PWM)
  TCC2->CTRLA.reg |= TCC_CTRLA_ENABLE;
  while (TCC2->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {
  // Do nothing
}

This is certainly a crazy, circuitous route to take for a 1 MHz signal, but it offers much greater control of the PWM frequency. If your application needs this kind of control (e.g. motor controls), firing up the PLL could be incredibly helpful.

In case you’re wondering where I’m going with this project: let’s just say that this fine control of clock speeds lets us create something like a digitally controlled oscillator (DCO).

1 Mhz square wave on Analog Discovery 2

Arduino Zero (SAMD21) Raw PWM Using CMSIS

Arduino has a habit of making pulse width modulation (PWM) pins with a set frequency (or limited frequency options). For the average user, this is not a bad thing, considering it hides all the ugliness (or beauty, depending on your perspective) of manually setting registers.

For those of us that want to dig deeper into the SAMD21, there are a few options. Almost all modern implementations of the ARM Cortex architectures can use the Cortex Microcontroller Software Interface Standard (CMSIS). This is a set of firmware libraries that provide an interface to the registers along with some other functions. Ideally, you should be able to use CMSIS regardless of the manufacturer of the chip (e.g. Microchip, STMicroelectronics, TI).

Some vendors have decided to include their own abstraction layer on top of CMSIS to help people configure and use their parts. In some cases, this can make setting up registers easy (especially if there is a slick GUI program that generates code for you, like STMicro’s STM32CubeMX). Microchip’s abstraction layer is known as the Advanced Software Framework (ASF).

Many professional developers swear by the vendor-provided abstraction layer (e.g. ASF). If you’re like me, you want to know what’s going on at the register level. Not that there is anything wrong with ASF, I just like to learn about the low-level stuff.

Here’s the problem: when it comes to manually configuring registers for 32-bit ARM microcontrollers, it’s vastly more difficult than it is for most other 8- or 16-bit architectures.

I’ve spent the last few days digging through the SAMD21 datasheet and online examples to get some insight into using CMSIS. On the surface, it’s not too bad: you just need to set some values in some registers to get a pin to output a square wave. In reality, it requires a good amount of code to perform this simple action.

Please note that I am just beginning to understand how the SAMD21 works, and this article is simply a chronicle of my learning journey. If you think I missed something or got something wrong, please let me know in the comments!

The best description I can find for how this works is from section 14.3 (page 95) in the datasheet:

To configure a pin for PWM requires a good bit of effort. The basic idea is to select a clock source from SYSCTRL (such as the 48 MHz system clock) and connect it to a Generic Clock Generator. The SAMD21 has 9 different generators we can use. We can configure each generator to have its own divider and mask (so that we can run various peripherals off different speeds thanks to having separate clock generators).

Then, we connect our generator to the peripheral we want. In the example below, we’ll be using the Timer/Counter for Control Applications (TCC). The SAMD21 also has a Timer/Counter peripheral (TC), but from what I could grasp, it’s “TCC lite” (i.e. TCC with less features).

Once we have our peripheral configured (TCC, in this case), we then want to connect that to a hardware pin so that we get an actual waveform to appear.

While this might seem extremely complicated, the upside is that the SAMD21 is an extremely powerful chip. We can connect and configure different peripherals in many, many different ways (thanks to all this multiplexing of clock signals!).

Let’s walk through an example of outputting a 1 MHz square wave on pin PA18. Note that PA18 is D10 on the Arduino Zero, Adafruit Feather M0, and the SparkFun SAMD21 Mini Breakout board. If you don’t have one of these boards, check your schematic to see where PA18 goes. Also, note that there are lots of ways to output a 1 MHz square wave on a pin. We’ll be doing this using a 50% duty cycle PWM and the 48 MHz system clock.

Step 1: Enable Clock Source in SYSCTRL

You have a few options for clock sources:

  • External oscillator (0.4 – 32 MHz)
  • External oscillator at 32 kHz
  • Internal oscillator at 32 kHz
  • Ultra low power internal oscillator at 32 kHz
  • Internal oscillator at 8 MHz
  • Digital frequency-locked loop at 48 MHz
  • Fractional digital phase-locked loop (48 – 96 MHz)

Some of these, like the phase-locked loop, can only be used to provide a clock signal for the peripherals. It cannot be used as a system clock. As such, the maximum processing speed of the SAMD21 is 48 MHz.

In most of the Arduino implementations of the SAMD21, the 48 MHz clock is already enabled for us (in the background, as part of the framework). That means we don’t need to initialize it our user code.

Step 2: Configure Clock Generator and Connect It to a Clock Source

Generic clock generators create a square wave (inside the chip) that we can frequency divide, mask, and connect to peripherals. The SAMD21 has 9 clock generators for us to use. We need to pick one, initialize it, and connect it to our clock source.

From what I could find, Generic Clock Generators 0 and 1 were used for other Arduino functions, so you probably want to avoid using those.

Step 3: Configure Peripheral and Connect It to a Clock Generator

The SAMD21 has several peripherals for us to use. We’ll be focusing on the TCC for this example. There are 3 TCCs for us to use, and each one has its own set of unique features. TCC0 and 1 are 24 bits wide whereas TCC2 is limited to 16 bits for its counter. From section 30.1 (page 651) in the datasheet:

SAMD21 TCC configuration chart

With TCC, we can set it to generate a PWM waveform on a pin. We will use a simple, single-slope PWM wave that counts up to a given value. During that counting process, if the value of the counter is equal to the Compare and Capture (CC) register, then the pin will toggle to low. Once the counter reaches the value in the Period (PER) register, then the pin will toggle back to high. The counter resets and starts over again.

From section 30.6.2.5 (page 660) in the datasheet, we get a diagram of how the PWM counter works. Note that TOP, for this mode, refers to the value in the PER register.

SAMD21 Normal PWM operation

WO[x] stands for “waveform output,” and the x is the pin we wish to use. In the datasheet, navigate to section 6.1 (page 21) to see a chart of the pin to function mapping (too long to copy here). Under functions E and F, you’ll see the TCC options. For our PA18 example, you can see that function F is TCC0/WO[2].

Here is the really confusing part: there are only 4 CC options (CC0 through CC3) for setting the duty cycle, but the pin/function mapping chart shows up to WO[7] channels. So, how do we connect a given CC register to a WO pin that’s greater than 3? This poorly labeled chart buried away in part 30.6.3.7 (page 678) holds the key:

SAMD21 output matrix

WO[7] is on the left and WO[0] is on the right. The OTMX configuration bits are found in the WEXCTRL register. By setting the OTMX bits to values 0-3, you can change which CC registers the various WO pins use.

By default, OTMX is set to 0x0, so the top row of the chart is used. Since we are using WO[2], we need to use CC2, according to the chart. If you want to use WO[5], you would use CC1.

Step 4: Set Data Direction and Peripheral for Pin

Because we’ve already configured TCC0/WO[2], that locks us in to pin PA18. Pins have a variety of functions that they can be set to (once again, refer to the chart in section 6.1).  For our project, we need to set PA18 to Function F (TCC0/WO[2]). We also need to set PA18 as an output in the data direction (DIR) register.

Example Code

Now for the moment you’ve all been waiting for (or, let’s be honest, the part you scrolled down for, skipping the above descriptions). Here is some example Arduino code to create a 1 MHz square wave on PA18 (D10 on the Zero, Adafruit Feather M0, or SparkFun SAMD21 Mini).

Note: Many thanks to MartinL on the Arduino forums! Their code snippet (found here) helped me tremendously. The following code is based on that snippet.

// Number to count to with PWM (TOP value). Frequency can be calculated by
// freq = GCLK4_freq / (TCC0_prescaler * (1 + TOP_value))
// With TOP of 47, we get a 1 MHz square wave in this example
uint32_t period = 48 - 1;
 
void setup() {

  // Because we are using TCC0, limit period to 24 bits
  period = ( period < 0x00ffffff ) ? period : 0x00ffffff;

  // Enable and configure generic clock generator 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DFLL48M |  // Select 48MHz as source
                      GCLK_GENCTRL_ID(4);         // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Set clock divider of 1 to generic clock generator 4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |         // Divide 48 MHz by 1
                     GCLK_GENDIV_ID(4);           // Apply to GCLK4 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  // Enable GCLK4 and connect it to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Feed GCLK4 to TCC0/1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Divide counter by 1 giving 48 MHz (20.83 ns) on each TCC0 tick
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);

  // Use "Normal PWM" (single-slope PWM): count up to PER, match on CC[n]
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Select NPWM as waveform
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Set the period (the number to count to (TOP) before resetting timer)
  TCC0->PER.reg = period;
  while (TCC0->SYNCBUSY.bit.PER);

  // Set PWM signal to output 50% duty cycle
  // n for CC[n] is determined by n = x % 4 where x is from WO[x]
  TCC0->CC[2].reg = period / 2;
  while (TCC0->SYNCBUSY.bit.CC2);

  // Configure PA18 (D10 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA18;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA18;      // Set pin to low

  // Enable the port multiplexer for PA18
  PORT->Group[PORTA].PINCFG[18].reg |= PORT_PINCFG_PMUXEN;

  // Connect TCC0 timer to PA18. Function F is TCC0/WO[2] for PA18.
  // Odd pin num (2*n + 1): use PMUXO
  // Even pin num (2*n): use PMUXE
  PORT->Group[PORTA].PMUX[9].reg = PORT_PMUX_PMUXE_F;

  // Enable output (start PWM)
  TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {
  // Do nothing
}

Run it, and connect an oscilloscope to pin PA18. You should get a nice 1 MHz signal (well, as nice as you can expect from a microcontroller). Here is the output as measured on my Analog Discovery 2:

1 Mhz square wave on Analog Discovery 2

That’s it for the setup of a PWM timer on the SAMD21. Feel free to use the code as a starting point for your own project!

Next SAMD21 article: Arduino Zero (SAMD21) FDPLL with CMSIS

Edit [12/23/18]: Added section about TCC counter sizes and updated code to reflect unsigned 24 bit maximum for TCC0.