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 Modes. 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 Pins are required. The XIn32 Pin is used in both Modes, 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 Pins. 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 Increments 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

Clock representing the 1 kHz output of the Xosc32kBase clock
Singleton token that can be exchanged for Xosc1k
Clock representing the 32 kHz output of the Xosc32kBase clock
XOSC32K base clock, which feeds the Xosc1k and Xosc32k clocks
Singleton token that can be exchanged for Xosc32kBase
Clock failure detection interface for the XOSC32K peripheral
Singleton token that can be exchanged for Xosc32kCfd
Singleton token that can be exchanged for Xosc32k
Set of tokens representing the disabled XOSC32K clocks power-on reset

Enums

Type-level variant of the XOSC32K operating Mode
Gain mode for the XOSC32K control loop
Type-level variant of the XOSC32K operating Mode
Value-level enum identifying one of two possible XOSC32K operating modes
Division factor for the safe clock prescaler
Start up delay before continuous monitoring takes effect
Type representing the identity of the Xosc1k clock
Type representing the identity of the Xosc32k clock

Traits

Type-level enum for the XOSC32K operation mode

Type Definitions

Type alias for the XOSC32K input Pin
Type alias for the XOSC32K output Pin