Expand description
Digital Frequency Locked Loop
The dfll
module provides access to the 48 MHz digital frequency locked
loop (DFLL) within the OSCCTRL
peripheral.
Operation modes
The DFLL can operate in both open-loop and closed-loop modes. In open-loop mode, it uses an internal oscillator to produce an unreferenced, 48 MHz output clock. While in closed-loop mode, the DFLL multiplies a low-frequency input clock to yield a 48 MHz output clock. The reference clock can be provided by a GCLK, through the DFLL peripheral channel clock, or it can be provided by the USB start-of-frame signal.
The DFLL is represented by the type Dfll<M>
, where M
is one of three
Mode
types. The default type is OpenLoop
, while the other two types,
FromPclk
and FromUsb
, represent closed-loop Mode
s with the
corresponding Reference
clock.
The DFLL at power-on reset
Because the DFLL can produce a 48 MHz clock from an internal oscillator, it
is used as the system’s default master clock at power-on reset. While most
clocks are disabled at reset and represented by items in the Tokens
struct, the Dfll
is Enabled
at reset, so it is found in the
Clocks
struct.
At reset, the EnabledDfll
is in OpenLoop
Mode
and has one
consumer clock, so its complete type is EnabledDfll<U1>
. The corresponding
consumer is Gclk0
, which is represented as EnabledGclk0<DfllId, U1>
.
Here, the EnabledGclk0
has its own consumer as well, which is the
system’s master clock.
Example
Configuring the Dfll
proceeds according to the principles outlined in
the clock
module documentation. Suppose we start with the default clock
tree after power-on reset.
DFLL (48 MHz; open-loop mode)
└── GCLK0 (48 MHz)
└── Master clock (48 MHz)
We would like to transform it to a clock tree like this:
XOSC0 (24 MHz; external clock)
└── GCLK0 (24 MHz)
├── Master clock (24 MHz)
└── DFLL (48 MHz; closed-loop mode)
Let’s start by using clock_system_at_reset
to access the HAL clocking
structs. We’ll also need access to the GPIO Pins
.
use atsamd_hal::{
clock::v2::{clock_system_at_reset, dfll::Dfll, xosc::Xosc},
gpio::Pins,
pac::Peripherals,
time::U32Ext,
};
let mut pac = Peripherals::take().unwrap();
let pins = Pins::new(pac.PORT);
let (buses, clocks, tokens) = clock_system_at_reset(
pac.OSCCTRL,
pac.OSC32KCTRL,
pac.GCLK,
pac.MCLK,
&mut pac.NVMCTRL,
);
Next, we create a 24 MHz Xosc
clock from one of the Pins
and enable
it.
let xosc0 = Xosc::from_clock(tokens.xosc0, pins.pa14, 24.mhz()).enable();
We can then swap Gclk0
from the EnabledDfll
to the EnabledXosc
.
This releases the EnabledDfll
and Decrement
s its consumer count,
which allows us to disable it and retrieve the underlying DfllToken
.
let (gclk0, dfll, xosc0) = clocks.gclk0.swap_sources(clocks.dfll, xosc0);
let token_dfll = dfll.disable().free();
Next, we can enable the peripheral channel clock, or Pclk
, for the
Dfll
, sourcing it from Gclk0
. With the Pclk
, we can then recreate
the Dfll
in closed-loop mode. Finally, we can adjust some of the
closed-loop parameters before we enable it. The returned EnabledDfll
can
be used as a clock Source
elsewhere in the clock tree.
let (pclk_dfll, gclk0) = Pclk::enable(tokens.pclks.dfll, gclk0);
let dfll = Dfll::from_pclk(token_dfll, pclk_dfll)
.coarse_max_step(1)
.fine_max_step(10)
.quick_lock(false)
.enable();
The entire example is provided below.
use atsamd_hal::{
clock::v2::{clock_system_at_reset, dfll::Dfll, pclk::Pclk, xosc::Xosc},
gpio::Pins,
pac::Peripherals,
time::U32Ext,
};
let mut pac = Peripherals::take().unwrap();
let pins = Pins::new(pac.PORT);
let (buses, clocks, tokens) = clock_system_at_reset(
pac.OSCCTRL,
pac.OSC32KCTRL,
pac.GCLK,
pac.MCLK,
&mut pac.NVMCTRL,
);
let xosc0 = Xosc::from_clock(tokens.xosc0, pins.pa14, 24.mhz()).enable();
let (gclk0, dfll, xosc0) = clocks.gclk0.swap_sources(clocks.dfll, xosc0);
let token_dfll = dfll.disable().free();
let (pclk_dfll, gclk0) = Pclk::enable(tokens.pclks.dfll, gclk0);
let dfll = Dfll::from_pclk(token_dfll, pclk_dfll)
.coarse_max_step(1)
.fine_max_step(10)
.quick_lock(false)
.enable();
Reconfiguring an EnabledDfll
In some cases, users may want to reconfigure the DFLL while it remains
enabled. For instance, a user may want to place the DFLL in its closed-loop,
USB recovery mode while in use by the system’s master clock. It would
normally be impossible to do so with other clocks in the clock
module,
because changing the clock source would break an invariant of the clock
tree. However, the DFLL is special, because its output frequency is always
48 MHz. Moreover, by design, consumers of the DFLL aren’t affected by its
configuration (see the discussion on Id
types).
For this reason, we define a special into_mode
function on
EnabledDfll
. It will consume the EnabledDfll
and transform it to use
a different Mode
.
While the Dfll
constructors (i.e. open_loop
, from_pclk
, and
from_usb
) handle the Mode
type for you, into_mode
is generic
over the initial and final Mode
, so it takes and returns the corresponding
Mode
types directly. Furthermore, into_mode
also accepts a closure,
allowing you to modify the Dfll
before the new Mode
is applied.
Consider the following example. As above, we start with the clocks in their
default configuration at power-on reset. Remember that the Dfll
is used
by the system’s master clock. At this point, we would like to reconfigure it
to use an external 32 kHz clock on pin PA10. First, we construct a Gclk
from the corresponding gpio
Pin
. Then we enable the Pclk
for the
DFLL and construct an instance of FromPclk
. Finally, we call
into_mode
, which takes an instance of FromPclk
and returns an instance
of OpenLoop
. Neither OpenLoop
nor FromUsb
need to store a
corresponding resource, so they are both effectively equivalent to the ()
type. We can also change some of the DFLL control loop settings prior to the
Mode
change using the closure argument to into_mode
.
use atsamd_hal::{
clock::v2::{
clock_system_at_reset,
dfll::{Dfll, FromPclk},
gclk::Gclk,
pclk::Pclk,
},
gpio::Pins,
pac::Peripherals,
time::U32Ext,
};
let mut pac = Peripherals::take().unwrap();
let pins = Pins::new(pac.PORT);
let (buses, mut clocks, tokens) = clock_system_at_reset(
pac.OSCCTRL,
pac.OSC32KCTRL,
pac.GCLK,
pac.MCLK,
&mut pac.NVMCTRL,
);
let gclk4 = Gclk::from_pin(tokens.gclks.gclk4, pins.pa10, 32_768.hz()).enable();
let (pclk, gclk4) = Pclk::enable(tokens.pclks.dfll, gclk4);
let from_pclk = FromPclk { pclk };
let (dfll, open_loop) = clocks.dfll.into_mode(from_pclk, |dfll| {
dfll.set_coarse_max_step(1);
dfll.set_fine_max_step(8);
dfll.set_chill_cycle(false);
dfll.set_run_standby(true);
});
Structs
Enums
Dfll