Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rtc: Add support for monitoring of XTAL clock #146

Merged
merged 2 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion esp-hal-common/src/rtc_cntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ impl Rtc {
swd: Swd::new(),
}
}

pub fn estimate_xtal_frequency(&mut self) -> u32 {
RtcClock::estimate_xtal_frequency()
}
}

/// RTC Watchdog Timer
Expand All @@ -109,6 +113,42 @@ pub struct RtcClock;
impl RtcClock {
const CAL_FRACT: u32 = 19;

/// Enable or disable 8 MHz internal oscillator
///
/// Output from 8 MHz internal oscillator is passed into a configurable
/// divider, which by default divides the input clock frequency by 256.
/// Output of the divider may be used as RTC_SLOW_CLK source.
/// Output of the divider is referred to in register descriptions and code as
/// 8md256 or simply d256. Divider values other than 256 may be configured, but
/// this facility is not currently needed, so is not exposed in the code.
///
/// When 8MHz/256 divided output is not needed, the divider should be disabled
/// to reduce power consumption.
fn enable_8m(clk_8m_en: bool, d256_en: bool) {
let rtc_cntl = unsafe { &*RTC_CNTL::ptr() };

if clk_8m_en {
rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m().clear_bit());
unsafe {
rtc_cntl.timer1.modify(|_, w| w.ck8m_wait().bits(5));
esp_rom_delay_us(50);
}
} else {
rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m().set_bit());
rtc_cntl
.timer1
.modify(|_, w| unsafe { w.ck8m_wait().bits(20) });
}

if d256_en {
rtc_cntl
.clk_conf
.modify(|_, w| w.enb_ck8m_div().clear_bit());
} else {
rtc_cntl.clk_conf.modify(|_, w| w.enb_ck8m_div().set_bit());
}
}

/// Get main XTAL frequency
/// This is the value stored in RTC register RTC_XTAL_FREQ_REG by the bootloader, as passed to
/// rtc_clk_init function.
Expand Down Expand Up @@ -195,12 +235,14 @@ impl RtcClock {
};
}

/// Calibration of RTC_SLOW_CLK is performed using a special feature of TIMG0.
/// This feature counts the number of XTAL clock cycles within a given number of
/// RTC_SLOW_CLK cycles.
fn calibrate_internal(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 {
// Except for ESP32, choosing RTC_CAL_RTC_MUX results in calibration of
// the 150k RTC clock (90k on ESP32-S2) regardless of the currently selected SLOW_CLK.
// On the ESP32, it uses the currently selected SLOW_CLK.
// The following code emulates ESP32 behavior for the other chips:

#[cfg(not(feature = "esp32"))]
let cal_clk = match cal_clk {
RtcCalSel::RtcCalRtcMux => match RtcClock::get_slow_freq() {
Expand Down Expand Up @@ -339,6 +381,14 @@ impl RtcClock {
cal_val
}

/// Measure ratio between XTAL frequency and RTC slow clock frequency
fn get_calibration_ratio(cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 {
let xtal_cycles = RtcClock::calibrate_internal(cal_clk, slowclk_cycles) as u64;
let ratio = (xtal_cycles << RtcClock::CAL_FRACT) / slowclk_cycles as u64;

(ratio & (u32::MAX as u64)) as u32
}

/// Measure RTC slow clock's period, based on main XTAL frequency
///
/// This function will time out and return 0 if the time for the given number
Expand Down Expand Up @@ -370,6 +420,28 @@ impl RtcClock {

(1000f32 / period) as u16
}

fn estimate_xtal_frequency() -> u32 {
// Number of 8M/256 clock cycles to use for XTAL frequency estimation.
const XTAL_FREQ_EST_CYCLES: u32 = 10;

let rtc_cntl = unsafe { &*RTC_CNTL::ptr() };
let clk_8m_enabled = rtc_cntl.clk_conf.read().enb_ck8m().bit_is_clear();
let clk_8md256_enabled = rtc_cntl.clk_conf.read().enb_ck8m_div().bit_is_clear();

if !clk_8md256_enabled {
RtcClock::enable_8m(true, true);
}

let ratio = RtcClock::get_calibration_ratio(RtcCalSel::RtcCal8mD256, XTAL_FREQ_EST_CYCLES);
let freq_mhz =
((ratio as u64 * RtcFastClock::RtcFastClock8m.hz() as u64 / 1_000_000u64 / 256u64)
>> RtcClock::CAL_FRACT) as u32;

RtcClock::enable_8m(clk_8m_enabled, clk_8md256_enabled);

freq_mhz
}
}

/// Behavior of the RWDT stage if it times out
Expand Down
69 changes: 69 additions & 0 deletions esp32-hal/examples/clock_monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! This demos a simple monitor for the XTAL frequency, by relying on a special
//! feature of the TIMG0 (Timer Group 0). This feature counts the number of XTAL
//! clock cycles within a given number of RTC_SLOW_CLK cycles.

#![no_std]
#![no_main]

use core::cell::RefCell;

use esp32_hal::{
clock::ClockControl,
interrupt,
pac::{self, Peripherals},
prelude::*,
Rtc,
};
use panic_halt as _;
use xtensa_lx::mutex::{CriticalSectionMutex, Mutex};
use xtensa_lx_rt::entry;

static mut RTC: CriticalSectionMutex<RefCell<Option<Rtc>>> =
CriticalSectionMutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.DPORT.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);

// Disable watchdog timer
rtc.rwdt.disable();

rtc.rwdt.start(2000u64.millis());
rtc.rwdt.listen();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Expected]",
clocks.xtal_clock.to_MHz()
);

interrupt::enable(pac::Interrupt::RTC_CORE, interrupt::Priority::Priority1).unwrap();

unsafe {
(&RTC).lock(|data| (*data).replace(Some(rtc)));
}

loop {}
}

#[interrupt]
fn RTC_CORE() {
unsafe {
(&RTC).lock(|data| {
let mut rtc = data.borrow_mut();
let rtc = rtc.as_mut().unwrap();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Monitor]",
rtc.estimate_xtal_frequency()
);

rtc.rwdt.clear_interrupt();
});
}
}
72 changes: 72 additions & 0 deletions esp32c3-hal/examples/clock_monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! This demos a simple monitor for the XTAL frequency, by relying on a special feature of the
//! TIMG0 (Timer Group 0). This feature counts the number of XTAL clock cycles within a given
//! number of RTC_SLOW_CLK cycles.

#![no_std]
#![no_main]

use core::cell::RefCell;

use bare_metal::Mutex;

use esp32c3_hal::{
clock::ClockControl,
interrupt,
pac::{self, Peripherals},
prelude::*,
Rtc,
};
use panic_halt as _;
use riscv_rt::entry;

static mut RTC: Mutex<RefCell<Option<Rtc>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);

// Disable watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();

rtc.rwdt.start(2000u64.millis());
rtc.rwdt.listen();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Expected]",
clocks.xtal_clock.to_MHz()
);

interrupt::enable(pac::Interrupt::RTC_CORE, interrupt::Priority::Priority1).unwrap();

riscv::interrupt::free(|_cs| unsafe {
RTC.get_mut().replace(Some(rtc));
});

unsafe {
riscv::interrupt::enable();
}

loop {}
}

#[interrupt]
fn RTC_CORE() {
riscv::interrupt::free(|cs| unsafe {
let mut rtc = RTC.borrow(*cs).borrow_mut();
let rtc = rtc.as_mut().unwrap();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Monitor]",
rtc.estimate_xtal_frequency()
);

rtc.rwdt.clear_interrupt();
});
}
69 changes: 69 additions & 0 deletions esp32s2-hal/examples/clock_monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! This demos a simple monitor for the XTAL frequency, by relying on a special
//! feature of the TIMG0 (Timer Group 0). This feature counts the number of XTAL
//! clock cycles within a given number of RTC_SLOW_CLK cycles.

#![no_std]
#![no_main]

use core::cell::RefCell;

use esp32s2_hal::{
clock::ClockControl,
interrupt,
pac::{self, Peripherals},
prelude::*,
Rtc,
};
use panic_halt as _;
use xtensa_lx::mutex::{CriticalSectionMutex, Mutex};
use xtensa_lx_rt::entry;

static mut RTC: CriticalSectionMutex<RefCell<Option<Rtc>>> =
CriticalSectionMutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);

// Disable watchdog timer
rtc.rwdt.disable();

rtc.rwdt.start(2000u64.millis());
rtc.rwdt.listen();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Expected]",
clocks.xtal_clock.to_MHz()
);

interrupt::enable(pac::Interrupt::RTC_CORE, interrupt::Priority::Priority1).unwrap();

unsafe {
(&RTC).lock(|data| (*data).replace(Some(rtc)));
}

loop {}
}

#[interrupt]
fn RTC_CORE() {
unsafe {
(&RTC).lock(|data| {
let mut rtc = data.borrow_mut();
let rtc = rtc.as_mut().unwrap();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Monitor]",
rtc.estimate_xtal_frequency()
);

rtc.rwdt.clear_interrupt();
});
}
}
70 changes: 70 additions & 0 deletions esp32s3-hal/examples/clock_monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! This demos a simple monitor for the XTAL frequency, by relying on a special
//! feature of the TIMG0 (Timer Group 0). This feature counts the number of XTAL
//! clock cycles within a given number of RTC_SLOW_CLK cycles.

#![no_std]
#![no_main]

use core::cell::RefCell;

use esp32s3_hal::{
clock::ClockControl,
interrupt,
pac::{self, Peripherals},
prelude::*,
Rtc,
};
use panic_halt as _;
use xtensa_lx::mutex::{CriticalSectionMutex, Mutex};
use xtensa_lx_rt::entry;

static mut RTC: CriticalSectionMutex<RefCell<Option<Rtc>>> =
CriticalSectionMutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);

// Disable watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();

rtc.rwdt.start(2000u64.millis());
rtc.rwdt.listen();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Expected]",
clocks.xtal_clock.to_MHz()
);

interrupt::enable(pac::Interrupt::RTC_CORE, interrupt::Priority::Priority1).unwrap();

unsafe {
(&RTC).lock(|data| (*data).replace(Some(rtc)));
}

loop {}
}

#[interrupt]
fn RTC_CORE() {
unsafe {
(&RTC).lock(|data| {
let mut rtc = data.borrow_mut();
let rtc = rtc.as_mut().unwrap();

esp_println::println!(
"{: <10} XTAL frequency: {} MHz",
"[Monitor]",
rtc.estimate_xtal_frequency()
);

rtc.rwdt.clear_interrupt();
});
}
}