Expand description
External, 32 kHz crystal oscillator controller
Overview
The xosc32k
module provides access to the 32 kHz external crystal
oscillator controller (XOSC32K) within the OSC32KCTRL
peripheral.
The peripheral can operate in two Mode
s. It can accept an external
clock, or it can interface with an crystal oscillator. In both cases, the
clock must be 32,768 Hz.
When used with an external clock, only one GPIO Pin
is required, but
when used with a crystal oscillator, two GPIO Pin
s are required. The
XIn32
Pin
is used in both Mode
s, while the XOut32
Pin
is only
used in CrystalMode
.
Clock tree structure
The XOSC32K
clock is unlike most other clocks, because it has two separate
outputs, one at 32 kHz and another divided down to 1 kHz. Moreover, none,
either or both of these outputs can be enabled at any given time.
We can see, then, that the XOSC32K
peripheral forms its own, miniature
clock tree. There is a 1:N producer clock that must be enabled first; and
there are two possible consumer clocks that can be independently and
optionally enabled. In fact, this structure is illustrated by the XOSC32K
register, which has three different enable bits: ENABLE
, EN32K
and
EN1K
.
To represent this structure in the type system, we divide the XOSC32K
peripheral into these three clocks. Users start by enabling the
Xosc32kBase
clock, which corresponds to setting the XOSC32K
register
ENABLE
bit. The call to Xosc32kBase::enable
returns a 1:N Enabled
clock Source
, which can be consumed by both the Xosc32k
and
Xosc1k
clocks. Enabling either of these two clocks will Increment
the EnabledXosc32kBase
counter, preventing it from being disabled.
Note that Xosc32k
and Xosc1k
are themselves 1:N clocks as well.
Clock failure detection and write lock
Like the Xosc
clocks, the XOSC32K peripheral also has clock failure
detection. However, unlike the XOSCCTRL
registers, the XOSC32K
register
has a dedicated write lock bit that will freeze its configuration until the
next power-on reset.
While Xosc
clock failure detection is configured directly in the
XOSCCTRL
register, the XOSC32K peripheral has a separate, dedicated
clock failure detection register (CFDCTRL
). This difference likely exists
to provide control of clock failure detection after write lock has been
enabled.
In this module, write lock is implemented by simply dropping the
Xosc32kBase
clock, which prevents any further access to the XOSC32K
register. Thus, to allow control of clock failure detection in the presence
of write lock, we provide a dedicated Xosc32kCfd
interface, which has
exclusive control over the CFDCTRL
register.
Example
Creating and configuring the XOSC32K clocks proceeds according to the
principles outlined in the clock
module documentation. It is best shown
with an example.
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,
osculp32k::OscUlp32k,
xosc32k::{
ControlGainMode, SafeClockDiv, StartUpDelay, Xosc1k, Xosc32k, Xosc32kBase,
Xosc32kCfd,
},
},
gpio::Pins,
pac::Peripherals,
};
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 the Xosc32kBase
clock from a 32 kHz oscillator using its
corresponding Xosc32kBaseToken
and the XIn32
and XOut32
Pin
s.
We then set the delay before the clock is unmasked by providing a desired
StartUpDelay
. Finally, we select a ControlGainMode
for the crystal
before enabling it.
let xosc32k_base = Xosc32kBase::from_crystal(tokens.xosc32k.base, pins.pa00, pins.pa01)
.start_up_delay(StartUpDelay::Delay1s)
.control_gain_mode(ControlGainMode::HighSpeed)
.enable();
At this point, we opt to wait until the Xosc32kBase
oscillator is_ready
and stable.
while !xosc32k_base.is_ready() {}
With the EnabledXosc32kBase
clock in hand, we can enable the Xosc1k
and Xosc32k
, each of which Increment
s the Enabled
counter.
Once we are satisfied with the configuration, we can call write_lock
to
lock the XOSC32K configuration at the hardware level. Doing so also consumes
the EnabledXosc32kBase
clock, which eliminates any ability to change the
configuration at the API level.
let (xosc1k, xosc32k_base) = Xosc1k::enable(tokens.xosc32k.xosc1k, xosc32k_base);
let (xosc32k, xosc32k_base) = Xosc32k::enable(tokens.xosc32k.xosc32k, xosc32k_base);
xosc32k_base.write_lock();
However, while we have locked the XOSC32K configuration, we still want to enable clock failure detection, which will continuously monitor the clock and switch to a safe, backup clock if necessary.
To do so, we must first enable the backup clock, which, for the XOSC32K, is
the OscUlp32k
. The OSCULP32K peripheral has a nearly identical structure
to the XOSC32K; we create an EnabledOscUlp32k
from the
EnabledOscUlp32kBase
clock and the corresponding OscUlp32kToken
.
Upon creation of the Xosc32kCfd
struct, we register it as a consumer of
the EnabledOscUlp32k
, which will Increment
its Counter
as well. When
creating the safe clock, the OscUlp32k
can be optionally divided by two,
which is selected with SafeClockDiv
.
let (osculp32k, osculp32k_base) =
OscUlp32k::enable(tokens.osculp32k.osculp32k, clocks.osculp32k_base);
let (mut cfd, osculp32k) =
Xosc32kCfd::enable(tokens.xosc32k.cfd, osculp32k, SafeClockDiv::Div1);
Finally, with the clock failure detection interface in hand, we can do
things like check if the XOSC32K has_failed
or if it is_switched
to
the safe clock. If we are able to recover from a clock failure, we can even
switch_back
to the crystal oscillator.
if cfd.has_failed() && cfd.is_switched() {
cfd.switch_back();
}
The complete example is provided below.
use atsamd_hal::{
clock::v2::{
clock_system_at_reset,
osculp32k::OscUlp32k,
xosc32k::{
ControlGainMode, SafeClockDiv, StartUpDelay, Xosc1k, Xosc32k, Xosc32kBase,
Xosc32kCfd,
},
},
gpio::Pins,
pac::Peripherals,
};
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 xosc32k_base = Xosc32kBase::from_crystal(tokens.xosc32k.base, pins.pa00, pins.pa01)
.start_up_delay(StartUpDelay::Delay1s)
.control_gain_mode(ControlGainMode::HighSpeed)
.enable();
while !xosc32k_base.is_ready() {}
let (xosc1k, xosc32k_base) = Xosc1k::enable(tokens.xosc32k.xosc1k, xosc32k_base);
let (xosc32k, xosc32k_base) = Xosc32k::enable(tokens.xosc32k.xosc32k, xosc32k_base);
xosc32k_base.write_lock();
let (osculp32k, osculp32k_base) =
OscUlp32k::enable(tokens.osculp32k.osculp32k, clocks.osculp32k_base);
let (mut cfd, osculp32k) =
Xosc32kCfd::enable(tokens.xosc32k.cfd, osculp32k, SafeClockDiv::Div1);
if cfd.has_failed() && cfd.is_switched() {
cfd.switch_back();
}
Structs
Xosc32kBase
clockXosc1k
Xosc32kBase
clockXosc32kBase
Xosc32kCfd
Xosc32k
Enums
Mode