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
Increment
ing 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 Increment
s 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
Enums
Dpll
Traits
Type Definitions
EnabledDpll
EnabledDpll