Expand description

Digital Phase-Locked Loop

Overview

The dpll module provides access to the two digital phase-locked loops (DPLLs) within the OSCCTRL peripheral.

A DPLL is used to multiply clock frequencies. It takes a lower-frequency input clock and produces a higher-frequency output clock. It works by taking the output clock, dividing it down to the same frequency as the input clock, comparing phase between the two signals, and locking that phase difference to zero. Consequently, the clock divider within the feedback loop sets the frequency multiplication factor.

The DPLLs operate over a large range of frequencies, but their operating region is not infinite. Specifically, they can only accept input frequencies between 32 kHz and 3.2 MHz, and they can only output frequencies in the range of 96 MHz to 200 MHz.

Creating and configuring a Dpll proceeds according to the principles outlined in the clock module documentation. It is best shown with an example.

Example

Suppose we start with the default clock tree after power-on reset.

DFLL (48 MHz)
└── GCLK0 (48 MHz)
    └── Master clock (48 MHz)

We would like to transform it to a clock tree like this:

DFLL (48 MHz)
└── GCLK1 (2 MHz)
    └── DPLL (200 MHz)
        └── GCLK0 (200 MHz)
            └── Master clock (200 MHz)

Let’s start by using clock_system_at_reset to access the HAL clocking structs.

use atsamd_hal::{
    clock::v2::{
        clock_system_at_reset,
        dpll::Dpll,
        gclk::{Gclk, GclkDiv16},
        pclk::Pclk,
    },
    pac::Peripherals,
};
let mut pac = Peripherals::take().unwrap();
let (buses, clocks, tokens) = clock_system_at_reset(
    pac.OSCCTRL,
    pac.OSC32KCTRL,
    pac.GCLK,
    pac.MCLK,
    &mut pac.NVMCTRL,
);

First, we would like to divide down the 48 MHz output of the Dfll to produce a valid input frequency for the Dpll. We start by feeding the already-Enabled Dfll to Gclk1 with a GclkDivider of 24, producing a 2 MHz output frequency. This has the side effect of Incrementing the counter for EnabledDfll.

let (gclk1, dfll) = Gclk::from_source(tokens.gclks.gclk1, clocks.dfll);
let gclk1 = gclk1.div(GclkDiv16::Div(24)).enable();

Next, we use the output of Gclk1 to enable the peripheral channel clock (Pclk) for Dpll0. This Increments the counter for EnabledGclk1.

let (pclk_dpll0, gclk1) = Pclk::enable(tokens.pclks.dpll0, gclk1);

Now we use Dpll::from_pclk, which consumes the Pclk and returns an instance of Dpll0. We use builder API functions to set the loop divider to 100 and enable the Dpll. This will multiply the 2 MHz input clock to produce a 200 MHz output clock.

let dpll0 = Dpll::from_pclk(tokens.dpll0, pclk_dpll0)
    .loop_div(100, 0)
    .enable();

There are two things to note at this point.

First, the loop divider has both an integer part and a fractional part. However, users should generally avoid using fractional division, if possible, because it increases the jitter of the output clock. See the Dpll::loop_div documentation for more details.

Second, because the input clock frequency and loop division factors are run-time values, the Dpll cannot verify at compile time that the input and output frequencies satisfy the requirements specified in the overview. Instead, these values are checked at run-time. If either frequency violates its requirement, the call to Dpll::enable will panic.

Finally, we wait until the EnabledDpll0 output is ready, and then we swap the EnabledGclk0, which feeds the processor master clock, from the 48 MHz EnabledDfll to the 200 MHz EnabledDpll0.

while !dpll0.is_ready() {}
let (gclk0, dfll, dpll0) = clocks.gclk0.swap_sources(dfll, dpll0);

We have now achieved the disired clock tree. The complete example is provided below.

use atsamd_hal::{
    clock::v2::{
        clock_system_at_reset,
        dpll::Dpll,
        gclk::{Gclk, GclkDiv16},
        pclk::Pclk,
    },
    pac::Peripherals,
};
let mut pac = Peripherals::take().unwrap();
let (buses, clocks, tokens) = clock_system_at_reset(
    pac.OSCCTRL,
    pac.OSC32KCTRL,
    pac.GCLK,
    pac.MCLK,
    &mut pac.NVMCTRL,
);
let (gclk1, dfll) = Gclk::from_source(tokens.gclks.gclk1, clocks.dfll);
let gclk1 = gclk1.div(GclkDiv16::Div(24)).enable();
let (pclk_dpll0, gclk1) = Pclk::enable(tokens.pclks.dpll0, gclk1);
let dpll0 = Dpll::from_pclk(tokens.dpll0, pclk_dpll0)
    .loop_div(100, 0)
    .enable();
while !dpll0.is_ready() {}
let (gclk0, dfll, dpll0) = clocks.gclk0.swap_sources(dfll, dpll0);

Structs

Digital phase-locked loop used to multiply clock frequencies
Singleton token that can be exchanged for a Dpll

Enums

Type-level variant of DpllId representing the identity of DPLL0
Type-level variant of DpllId representing the identity of DPLL1
Value-level enum identifying one of two possible Dplls
Value-level enum of possible clock sources for a Dpll

Traits

Type-level enum identifying one of two possible Dplls
Type-level enum of possible clock Sources for a Dpll

Type Definitions

Type alias for the corresponding Dpll
Type alias for the corresponding Dpll
Type alias for the corresponding EnabledDpll
Type alias for the corresponding EnabledDpll