From 08504b6d2d4e2d6f73099a1fcf8a8526598eb68a Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 05:17:23 +0100 Subject: [PATCH 1/8] Update PLL's minimum VCO frequency (Fixes #682) --- rp2040-hal/src/pll.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index aae025d4d..12934e971 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -141,7 +141,7 @@ 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; From 4dbadef2893faab7771ee503f4c827ffa4586faf Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 05:19:31 +0100 Subject: [PATCH 2/8] Add GPin0 and GPin1 clock initial source support --- rp2040-hal/src/clocks/clock_sources.rs | 28 +++++++++++++++++++------- rp2040-hal/src/clocks/mod.rs | 4 ++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/rp2040-hal/src/clocks/clock_sources.rs b/rp2040-hal/src/clocks/clock_sources.rs index c399f5766..b95299f1b 100644 --- a/rp2040-hal/src/clocks/clock_sources.rs +++ b/rp2040-hal/src/clocks/clock_sources.rs @@ -76,18 +76,32 @@ impl ClockSource for RingOscillator { } } -// 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 { fn get_freq(&self) -> HertzU32 { - todo!() + self.1 } } -// GPIN1 -pub(crate) type GPin1 = Pin; -impl ClockSource for Pin { +/// 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 { 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..5c9650446 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -79,9 +79,9 @@ use crate::{ mod macros; mod clock_sources; -use clock_sources::PllSys; +pub use clock_sources::{GPin0, GPin1}; -use self::clock_sources::{GPin0, GPin1, PllUsb, Rosc, Xosc}; +use clock_sources::{PllSys, PllUsb, Rosc, Xosc}; #[derive(Copy, Clone)] /// Provides refs to the CLOCKS block. From 8445d1fdbe547a04874ded40fad156979a9130e7 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 05:22:18 +0100 Subject: [PATCH 3/8] Add clock measurement support --- rp2040-hal/src/clocks/clock_sources.rs | 22 ++++++++++++++ rp2040-hal/src/clocks/mod.rs | 41 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/rp2040-hal/src/clocks/clock_sources.rs b/rp2040-hal/src/clocks/clock_sources.rs index b95299f1b..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,6 +87,8 @@ 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() } @@ -86,6 +104,8 @@ impl GPin0 { } impl Sealed for GPin0 {} impl ClockSource for GPin0 { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLKSRC_GPIN0; + fn get_freq(&self) -> HertzU32 { self.1 } @@ -101,6 +121,8 @@ impl GPin1 { } impl Sealed for GPin1 {} impl ClockSource for GPin1 { + const FCOUNTER_SRC: FC0_SRC_A = FC0_SRC_A::CLKSRC_GPIN1; + fn get_freq(&self) -> HertzU32 { self.1 } diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 5c9650446..87f3b6126 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}, @@ -167,6 +168,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 +305,43 @@ impl ClocksManager { .configure_clock(&self.system_clock, self.system_clock.freq()) } + /// Approximates the frequency of the given clock source. + pub fn approximate_frequency(&mut self, _trg_clk: C) -> HertzU32 { + // 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()) }); + + // Corresponds to a 1ms test time, which seems to give good enough accuracy + self.clocks + .fc0_interval + .write(|w| unsafe { w.fc0_interval().bits(10) }); + + // 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) }); + + // To measure rosc directly we use the value 0x03. + self.clocks.fc0_src.write(|w| { w.fc0_src().variant(C::FCOUNTER_SRC) }); + + // Wait until the measurement is ready + while self.clocks.fc0_status.read().done().bit_is_clear() { + core::hint::spin_loop() + } + + let speed_hz = self.clocks.fc0_result.read().khz().bits() * 1000; + speed_hz.Hz() + } + /// Releases the CLOCKS block pub fn free(self) -> CLOCKS { self.clocks From 442b6622528aa6fcebfb15dc6a1fe74516517757 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 16:00:05 +0100 Subject: [PATCH 4/8] update changelog --- rp2040-hal/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 From 6cbfae25de754ce66b23c46f787e842d1ba0474c Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 16:04:59 +0100 Subject: [PATCH 5/8] fix formatting --- rp2040-hal/src/clocks/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 87f3b6126..088583f84 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -313,9 +313,10 @@ impl ClocksManager { } // 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()) }); + self.clocks.fc0_ref_khz.write(|w| unsafe { + w.fc0_ref_khz() + .bits(self.reference_clock.get_freq().to_kHz()) + }); // Corresponds to a 1ms test time, which seems to give good enough accuracy self.clocks @@ -331,7 +332,9 @@ impl ClocksManager { .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); // To measure rosc directly we use the value 0x03. - self.clocks.fc0_src.write(|w| { w.fc0_src().variant(C::FCOUNTER_SRC) }); + self.clocks + .fc0_src + .write(|w| w.fc0_src().variant(C::FCOUNTER_SRC)); // Wait until the measurement is ready while self.clocks.fc0_status.read().done().bit_is_clear() { From fc6a3482a2c6e036cebcc2e89751957274a3aa74 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 18:57:06 +0100 Subject: [PATCH 6/8] clocks: Fix comments & rename approximate_frequency --- rp2040-hal/src/clocks/mod.rs | 47 +++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 088583f84..0c58d55c6 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -84,6 +84,30 @@ 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)] +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. struct ShareableClocks { @@ -305,8 +329,12 @@ impl ClocksManager { .configure_clock(&self.system_clock, self.system_clock.freq()) } - /// Approximates the frequency of the given clock source. - pub fn approximate_frequency(&mut self, _trg_clk: C) -> HertzU32 { + /// Measure the frequency of the given clock source by approximation. + pub fn measure_frequency( + &mut self, + _trg_clk: C, + accuracy: FCAccuracy, + ) -> HertzU32 { // Wait for the frequency counter to be ready while self.clocks.fc0_status.read().running().bit_is_set() { core::hint::spin_loop() @@ -318,10 +346,14 @@ impl ClocksManager { .bits(self.reference_clock.get_freq().to_kHz()) }); - // Corresponds to a 1ms test time, which seems to give good enough accuracy + // > 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(10) }); + .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 @@ -331,7 +363,7 @@ impl ClocksManager { .fc0_max_khz .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); - // To measure rosc directly we use the value 0x03. + // Select which clock to measure. self.clocks .fc0_src .write(|w| w.fc0_src().variant(C::FCOUNTER_SRC)); @@ -341,7 +373,10 @@ impl ClocksManager { core::hint::spin_loop() } - let speed_hz = self.clocks.fc0_result.read().khz().bits() * 1000; + // TODO: the result may not be valid (eg clock stopped during measurement). We may want to + // return a Result instead. Is it worth the hassle though? + let result = self.clocks.fc0_result.read(); + let speed_hz = result.khz().bits() * 1000 + result.frac().bits(); speed_hz.Hz() } From 8b48e81476c82ac83e55f082b5d82cd19469927b Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 21:04:01 +0100 Subject: [PATCH 7/8] clocks: add error support for measure_frequency --- rp2040-hal/src/clocks/mod.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 0c58d55c6..a157182e6 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -89,6 +89,7 @@ use clock_sources::{PllSys, PllUsb, Rosc, Xosc}; /// 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, @@ -136,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` @@ -334,7 +340,7 @@ impl ClocksManager { &mut self, _trg_clk: C, accuracy: FCAccuracy, - ) -> HertzU32 { + ) -> Result { // Wait for the frequency counter to be ready while self.clocks.fc0_status.read().running().bit_is_set() { core::hint::spin_loop() @@ -369,15 +375,21 @@ impl ClocksManager { .write(|w| w.fc0_src().variant(C::FCOUNTER_SRC)); // Wait until the measurement is ready - while self.clocks.fc0_status.read().done().bit_is_clear() { - core::hint::spin_loop() + let mut status; + loop { + status = self.clocks.fc0_status.read(); + if status.done().bit_is_set() { + break; + } } - // TODO: the result may not be valid (eg clock stopped during measurement). We may want to - // return a Result instead. Is it worth the hassle though? - let result = self.clocks.fc0_result.read(); - let speed_hz = result.khz().bits() * 1000 + result.frac().bits(); - speed_hz.Hz() + 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 From 3766cdebc03a62ac43a497639e568f9926104a62 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 4 Sep 2023 21:57:09 +0100 Subject: [PATCH 8/8] update the usb pll configuration according to the new vco min limit --- rp2040-hal/src/pll.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 12934e971..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, }; } @@ -145,14 +145,18 @@ impl PhaseLockedLoop { 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));