diff --git a/esp-hal-common/src/rtc_cntl.rs b/esp-hal-common/src/rtc_cntl.rs index 840fd712767..1f9c0515fe3 100644 --- a/esp-hal-common/src/rtc_cntl.rs +++ b/esp-hal-common/src/rtc_cntl.rs @@ -101,6 +101,10 @@ impl Rtc { swd: Swd::new(), } } + + pub fn estimate_xtal_frequency(&mut self) -> u32 { + RtcClock::estimate_xtal_frequency() + } } /// RTC Watchdog Timer @@ -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. @@ -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() { @@ -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 @@ -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 diff --git a/esp32-hal/examples/clock_monitor.rs b/esp32-hal/examples/clock_monitor.rs new file mode 100644 index 00000000000..15a51dcd783 --- /dev/null +++ b/esp32-hal/examples/clock_monitor.rs @@ -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>> = + 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(); + }); + } +} diff --git a/esp32c3-hal/examples/clock_monitor.rs b/esp32c3-hal/examples/clock_monitor.rs new file mode 100644 index 00000000000..e4d3dc18786 --- /dev/null +++ b/esp32c3-hal/examples/clock_monitor.rs @@ -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>> = 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(); + }); +} diff --git a/esp32s2-hal/examples/clock_monitor.rs b/esp32s2-hal/examples/clock_monitor.rs new file mode 100644 index 00000000000..d03603882bd --- /dev/null +++ b/esp32s2-hal/examples/clock_monitor.rs @@ -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>> = + 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(); + }); + } +} diff --git a/esp32s3-hal/examples/clock_monitor.rs b/esp32s3-hal/examples/clock_monitor.rs new file mode 100644 index 00000000000..50b2d7ae937 --- /dev/null +++ b/esp32s3-hal/examples/clock_monitor.rs @@ -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>> = + 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(); + }); + } +}