diff --git a/rp2040-hal/CHANGELOG.md b/rp2040-hal/CHANGELOG.md index 77a1da894..7aa51cc1a 100644 --- a/rp2040-hal/CHANGELOG.md +++ b/rp2040-hal/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +## Fixed + +- Fixed minimum PLL's VCO frequency according to updated datasheet - @ithinuel + +### Added + +- Support for GPin0 and GPin1 clock sources - @ithinuel +- Clock frequency approximation function using the frequency counter - @ithinuel + ## [0.9.0] ### MSRV diff --git a/rp2040-hal/src/clocks/clock_sources.rs b/rp2040-hal/src/clocks/clock_sources.rs index c399f5766..bba4657f1 100644 --- a/rp2040-hal/src/clocks/clock_sources.rs +++ b/rp2040-hal/src/clocks/clock_sources.rs @@ -16,6 +16,8 @@ use pac::{PLL_SYS, PLL_USB}; pub(crate) type PllSys = PhaseLockedLoop; impl Sealed for PllSys {} impl ClockSource for PllSys { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::PLL_SYS_CLKSRC_PRIMARY; + fn get_freq(&self) -> HertzU32 { self.operating_frequency() } @@ -24,36 +26,48 @@ impl ClockSource for PllSys { pub(crate) type PllUsb = PhaseLockedLoop; impl Sealed for PllUsb {} impl ClockSource for PllUsb { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::PLL_USB_CLKSRC_PRIMARY; + fn get_freq(&self) -> HertzU32 { self.operating_frequency() } } impl ClockSource for UsbClock { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLK_USB; + fn get_freq(&self) -> HertzU32 { self.frequency } } impl ClockSource for AdcClock { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLK_ADC; + fn get_freq(&self) -> HertzU32 { self.frequency } } impl ClockSource for RtcClock { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLK_RTC; + fn get_freq(&self) -> HertzU32 { self.frequency } } impl ClockSource for SystemClock { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLK_SYS; + fn get_freq(&self) -> HertzU32 { self.frequency } } impl ClockSource for ReferenceClock { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLK_REF; + fn get_freq(&self) -> HertzU32 { self.frequency } @@ -62,6 +76,8 @@ impl ClockSource for ReferenceClock { pub(crate) type Xosc = CrystalOscillator; impl Sealed for Xosc {} impl ClockSource for Xosc { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::XOSC_CLKSRC; + fn get_freq(&self) -> HertzU32 { self.operating_frequency() } @@ -71,23 +87,43 @@ pub(crate) type Rosc = RingOscillator; impl Sealed for Rosc {} // We are assuming the second output is never phase shifted (see 2.17.4) impl ClockSource for RingOscillator { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::ROSC_CLKSRC; + fn get_freq(&self) -> HertzU32 { self.operating_frequency() } } -// GPIN0 -pub(crate) type GPin0 = Pin; +/// Gpio20 in clock function associated with a frequency. +pub struct GPin0(Pin, HertzU32); +impl GPin0 { + /// Assemble Gpio20 and a frequency into a clock source. + pub fn new(p: Pin, freq: HertzU32) -> Self { + GPin0(p, freq) + } +} +impl Sealed for GPin0 {} impl ClockSource for GPin0 { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLKSRC_GPIN0; + fn get_freq(&self) -> HertzU32 { - todo!() + self.1 + } +} + +/// Gpio22 in clock function associated with a frequency. +pub struct GPin1(Pin, HertzU32); +impl GPin1 { + /// Assemble Gpio22 and a frequency into a clock source. + pub fn new(p: Pin, freq: HertzU32) -> Self { + GPin1(p, freq) } } +impl Sealed for GPin1 {} +impl ClockSource for GPin1 { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLKSRC_GPIN1; -// GPIN1 -pub(crate) type GPin1 = Pin; -impl ClockSource for Pin { fn get_freq(&self) -> HertzU32 { - todo!() + self.1 } } diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 5f280a5c5..a157182e6 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -63,6 +63,7 @@ //! See [Chapter 2 Section 15](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details use core::{convert::Infallible, marker::PhantomData}; use fugit::{HertzU32, RateExtU32}; +use pac::clocks::fc0_src::FC0_SRC_A; use crate::{ pac::{self, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, @@ -79,9 +80,34 @@ use crate::{ mod macros; mod clock_sources; -use clock_sources::PllSys; - -use self::clock_sources::{GPin0, GPin1, PllUsb, Rosc, Xosc}; +pub use clock_sources::{GPin0, GPin1}; + +use clock_sources::{PllSys, PllUsb, Rosc, Xosc}; + +/// Frequency counter accuracy +/// +/// See: https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#table-fc-test-interval +#[repr(u8)] +#[allow(missing_docs)] +#[allow(clippy::enum_variant_names)] +pub enum FCAccuracy { + _2048kHz = 0, + _1024kHz, + _512kHz, + _256kHz, + _128kHz, + _64kHz, + _32kHz, + _16kHz, + _8kHz, + _4kHz, + _2kHz, + _1kHz, + _500Hz, + _250Hz, + _125Hz, + _62_5Hz, +} #[derive(Copy, Clone)] /// Provides refs to the CLOCKS block. @@ -111,6 +137,11 @@ pub enum ClockError { FrequencyTooLow, } +/// The clock stopped while its frequency was being measured. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ClockDiedError; + /// For clocks pub trait Clock: Sealed + Sized { /// Enum with valid source clocks register values for `Clock` @@ -167,6 +198,9 @@ pub trait StoppableClock: Sealed { /// Trait for things that can be used as clock source pub trait ClockSource: Sealed { + /// Associated Frequency counter source. + const FCOUNTER_SRC: FC0_SRC_A; + /// Get the operating frequency for this source /// /// Used to determine the divisor @@ -301,6 +335,63 @@ impl ClocksManager { .configure_clock(&self.system_clock, self.system_clock.freq()) } + /// Measure the frequency of the given clock source by approximation. + pub fn measure_frequency( + &mut self, + _trg_clk: C, + accuracy: FCAccuracy, + ) -> Result { + // Wait for the frequency counter to be ready + while self.clocks.fc0_status.read().running().bit_is_set() { + core::hint::spin_loop() + } + + // Set the speed of the reference clock in kHz. + self.clocks.fc0_ref_khz.write(|w| unsafe { + w.fc0_ref_khz() + .bits(self.reference_clock.get_freq().to_kHz()) + }); + + // > The test interval is 0.98us * 2**interval, but let's call it 1us * 2**interval. + // > The default gives a test interval of 250us + // + // https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-clocks-FC0_INTERVAL + // https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#table-fc-test-interval + self.clocks + .fc0_interval + .write(|w| unsafe { w.fc0_interval().bits(accuracy as u8) }); + + // We don't really care about the min/max, so these are just set to min/max values. + self.clocks + .fc0_min_khz + .write(|w| unsafe { w.fc0_min_khz().bits(0) }); + self.clocks + .fc0_max_khz + .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); + + // Select which clock to measure. + self.clocks + .fc0_src + .write(|w| w.fc0_src().variant(C::FCOUNTER_SRC)); + + // Wait until the measurement is ready + let mut status; + loop { + status = self.clocks.fc0_status.read(); + if status.done().bit_is_set() { + break; + } + } + + if status.fail().bit_is_set() { + Err(ClockDiedError) + } else { + let result = self.clocks.fc0_result.read(); + let speed_hz = result.khz().bits() * 1000 + u32::from(result.frac().bits()); + Ok(speed_hz.Hz()) + } + } + /// Releases the CLOCKS block pub fn free(self) -> CLOCKS { self.clocks diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index aae025d4d..f79e1f507 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -127,10 +127,10 @@ pub mod common_configs { /// Default, nominal configuration for PLL_USB. pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { - vco_freq: HertzU32::MHz(480), + vco_freq: HertzU32::MHz(960), refdiv: 1, post_div1: 5, - post_div2: 2, + post_div2: 4, }; } @@ -141,18 +141,22 @@ impl PhaseLockedLoop { xosc_frequency: HertzU32, config: PLLConfig, ) -> Result, Error> { - const VCO_FREQ_RANGE: RangeInclusive = HertzU32::MHz(400)..=HertzU32::MHz(1_600); + const VCO_FREQ_RANGE: RangeInclusive = HertzU32::MHz(750)..=HertzU32::MHz(1_600); const POSTDIV_RANGE: Range = 1..7; const FBDIV_RANGE: Range = 16..320; - let vco_freq = config.vco_freq; + let PLLConfig { + vco_freq, + refdiv, + post_div1, + post_div2, + } = config; if !VCO_FREQ_RANGE.contains(&vco_freq) { return Err(Error::VcoFreqOutOfRange); } - if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) - { + if !POSTDIV_RANGE.contains(&post_div1) || !POSTDIV_RANGE.contains(&post_div2) { return Err(Error::PostDivOutOfRage); } @@ -161,7 +165,7 @@ impl PhaseLockedLoop { let ref_freq_hz: HertzU32 = xosc_frequency .to_Hz() - .checked_div(u32::from(config.refdiv)) + .checked_div(u32::from(refdiv)) .ok_or(Error::BadArgument)? .Hz(); @@ -180,9 +184,6 @@ impl PhaseLockedLoop { return Err(Error::FeedbackDivOutOfRange); } - let refdiv = config.refdiv; - let post_div1 = config.post_div1; - let post_div2 = config.post_div2; let frequency: HertzU32 = ((ref_freq_hz / u32::from(refdiv)) * u32::from(fbdiv)) / (u32::from(post_div1) * u32::from(post_div2));