From b0de7c9be6fba15be20aba092436464c26a61f96 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 09:46:53 +0200 Subject: [PATCH 01/14] Ignores Cargo.lock and target/*. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9f11b75..79f5db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .idea/ +target/ +Cargo.lock From ff418b04531799dc36ad943c5700b1eaacc59050 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 10:12:38 +0200 Subject: [PATCH 02/14] Working implementation of a PLL HAL. --- rp2040-hal/Cargo.toml | 1 + rp2040-hal/src/lib.rs | 1 + rp2040-hal/src/pll.rs | 227 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 rp2040-hal/src/pll.rs diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 782adb1..b0c0238 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -14,3 +14,4 @@ cortex-m = "0.7.1" embedded-hal = "0.2.4" nb = "1.0.0" rp2040-pac = "0.1.1" +embedded-time = "0.10.1" diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index e8d96e2..b868bcb 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -14,6 +14,7 @@ pub extern crate rp2040_pac as pac; pub mod adc; pub mod i2c; +pub mod pll; pub mod prelude; pub mod pwm; pub mod rom_data; diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs new file mode 100644 index 0000000..07544c2 --- /dev/null +++ b/rp2040-hal/src/pll.rs @@ -0,0 +1,227 @@ +//! Phase-Locked Loops (PLL) +// See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details + +use core::{ + convert::{ + Infallible, + TryFrom + }, + marker::PhantomData, + ops::{ + RangeInclusive, + Range, + Deref + } +}; + +use embedded_time::{ + fixed_point::FixedPoint, + rate::{ + Hertz, + Generic + } +}; + +use nb::Error::WouldBlock; + +/// State of the PLL +pub trait State {} + + +/// PLL is disabled but is configured. +pub struct Disabled { + refdiv: u8, + vco_freq: Hertz, + post_div1: u8, + post_div2: u8 +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked; + +/// PLL is locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8 +} + +impl State for Disabled {} +impl State for Locked {} +impl State for Locking {} + + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: Deref {} + +impl PhaseLockedLoopDevice for rp2040_pac::PLL_SYS {} +impl PhaseLockedLoopDevice for rp2040_pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state: state + } + } + + /// Releases the underlying device. + pub fn free(self) -> D{ + self.device + } +} + + +/// Error type for the PLL module. +/// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. +pub enum Error { + + /// Proposed VCO frequency is out of range. + VCOFreqOutOfRange, + + /// Feedback Divider value is out of range. + FBDIVOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument +} + +impl PhaseLockedLoop { + + + /// Instantiates and configures a new Phase-Locked-Loop device. + pub fn new(dev: D, refdiv: u8, vco_freq: Generic, post_div1: u8, post_div2: u8) -> Result, Error> { + + const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); + const POSTDIV_RANGE: Range = 1..7; + + let vco_freq = Hertz::::try_from(vco_freq).map_err(|_| Error::BadArgument)?; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VCOFreqOutOfRange) + } + + if !POSTDIV_RANGE.contains(&post_div1) || !POSTDIV_RANGE.contains(&post_div2) { + return Err(Error::PostDivOutOfRage) + } + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, vco_freq, post_div1, post_div2 + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, xosc_frequency: Generic) -> Result, Error>{ + + const FBDIV_RANGE: Range = 16..320; + + let ref_freq_range: Range> = Hertz(5_000_000)..self.state.vco_freq.div(16); + + // Turn off PLL in case it is already running + self.device.pwr.reset(); + self.device.fbdiv_int.reset(); + + let refdiv = self.state.refdiv; + + let ref_freq_hz = Hertz::::try_from(xosc_frequency). + map_err(|_| Error::BadArgument)?. + checked_div(&(refdiv as u32)). + ok_or(Error::BadArgument)?; + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange) + } + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(refdiv as u8); + w + }); + + let fbdiv = *self.state.vco_freq.checked_div(ref_freq_hz.integer()). + ok_or(Error::BadArgument)?.integer() as u16; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FBDIVOutOfRange) + } + + self.device.fbdiv_int.write(|w| unsafe { + w.fbdiv_int().bits(fbdiv); + w + }); + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(refdiv); + w + }); + + // Turn on self.device + self.device.pwr.write(|w| unsafe { + //w.pd().clear_bit(); + //w.vcopd().clear_bit(); + w.bits(0); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + + Ok(self.transition(Locking { + post_div1, post_div2 + })) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData +} + +impl PhaseLockedLoop { + + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + + if self.device.cs.read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + + // Set up post dividers + self.device.prim.write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr.write(|w| unsafe { + //w.postdivpd().clear_bit(); + w.bits(0); + w + }); + + self.transition(Locked) + } + +} From 6157ce552fbfdfa1403920fadb345448299c451d Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 17:51:03 +0200 Subject: [PATCH 03/14] Move PLL parameters into a struct to help testability and reconfiguration of the PLL. --- rp2040-hal/src/pll.rs | 115 ++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 44 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 07544c2..5071658 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -4,7 +4,8 @@ use core::{ convert::{ Infallible, - TryFrom + TryFrom, + TryInto }, marker::PhantomData, ops::{ @@ -18,7 +19,8 @@ use embedded_time::{ fixed_point::FixedPoint, rate::{ Hertz, - Generic + Generic, + Rate } }; @@ -28,10 +30,11 @@ use nb::Error::WouldBlock; pub trait State {} -/// PLL is disabled but is configured. -pub struct Disabled { - refdiv: u8, - vco_freq: Hertz, +/// PLL is disabled. +pub struct Disabled; + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { post_div1: u8, post_div2: u8 } @@ -39,17 +42,10 @@ pub struct Disabled { /// PLL is locked : it delivers a steady frequency. pub struct Locked; -/// PLL is locking into its designated frequency. -pub struct Locking { - post_div1: u8, - post_div2: u8 -} - impl State for Disabled {} impl State for Locked {} impl State for Locking {} - /// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) pub trait PhaseLockedLoopDevice: Deref {} @@ -97,49 +93,83 @@ pub enum Error { BadArgument } + +/// Parameters for a PLL. +pub struct PLLConfig { + + /// Voltage Controlled Oscillator frequency. + pub vco_freq: R, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8 +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// See Chapter 2, Section 18, §2 +pub mod common_configs { + use super::PLLConfig; + use embedded_time::rate::Megahertz; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_125MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(1500), + refdiv: 1, + post_div1: 6, + post_div2: 2 + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(480), + refdiv: 1, + post_div1: 5, + post_div2: 2 + }; + +} + impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new(dev: D) -> PhaseLockedLoop { + PhaseLockedLoop { + state: Disabled, + device: dev, + } + } - /// Instantiates and configures a new Phase-Locked-Loop device. - pub fn new(dev: D, refdiv: u8, vco_freq: Generic, post_div1: u8, post_div2: u8) -> Result, Error> { + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into { const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; - let vco_freq = Hertz::::try_from(vco_freq).map_err(|_| Error::BadArgument)?; + let vco_freq: Hertz = config.vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { return Err(Error::VCOFreqOutOfRange) } - if !POSTDIV_RANGE.contains(&post_div1) || !POSTDIV_RANGE.contains(&post_div2) { + if !POSTDIV_RANGE.contains(&config.post_div2) || !POSTDIV_RANGE.contains(&config.post_div2) { return Err(Error::PostDivOutOfRage) } - Ok(PhaseLockedLoop { - state: Disabled { - refdiv, vco_freq, post_div1, post_div2 - }, - device: dev, - }) - } - - /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize(self, xosc_frequency: Generic) -> Result, Error>{ - - const FBDIV_RANGE: Range = 16..320; - - let ref_freq_range: Range> = Hertz(5_000_000)..self.state.vco_freq.div(16); + let ref_freq_range: Range> = Hertz(5_000_000)..vco_freq.div(16); // Turn off PLL in case it is already running self.device.pwr.reset(); self.device.fbdiv_int.reset(); - let refdiv = self.state.refdiv; - let ref_freq_hz = Hertz::::try_from(xosc_frequency). map_err(|_| Error::BadArgument)?. - checked_div(&(refdiv as u32)). + checked_div(&(config.refdiv as u32)). ok_or(Error::BadArgument)?; if !ref_freq_range.contains(&ref_freq_hz) { @@ -147,12 +177,14 @@ impl PhaseLockedLoop { } self.device.cs.write(|w| unsafe { - w.refdiv().bits(refdiv as u8); + w.refdiv().bits(config.refdiv); w }); - let fbdiv = *self.state.vco_freq.checked_div(ref_freq_hz.integer()). - ok_or(Error::BadArgument)?.integer() as u16; + let fbdiv = vco_freq.checked_div(ref_freq_hz.integer()). + ok_or(Error::BadArgument)?; + + let fbdiv: u16 = (*fbdiv.integer()).try_into().map_err(|_| Error::BadArgument)?; if !FBDIV_RANGE.contains(&fbdiv) { return Err(Error::FBDIVOutOfRange) @@ -163,11 +195,6 @@ impl PhaseLockedLoop { w }); - self.device.cs.write(|w| unsafe { - w.refdiv().bits(refdiv); - w - }); - // Turn on self.device self.device.pwr.write(|w| unsafe { //w.pd().clear_bit(); @@ -176,8 +203,8 @@ impl PhaseLockedLoop { w }); - let post_div1 = self.state.post_div1; - let post_div2 = self.state.post_div2; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; Ok(self.transition(Locking { post_div1, post_div2 From c3bc1bbaf87791e7910a296e28e92eae24900267 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 19:45:45 +0200 Subject: [PATCH 04/14] Fix type conversion issue --- rp2040-hal/src/pll.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 5071658..05940c3 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -131,7 +131,6 @@ pub mod common_configs { post_div1: 5, post_div2: 2 }; - } impl PhaseLockedLoop { @@ -145,13 +144,17 @@ impl PhaseLockedLoop { } /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into { + pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into>{ const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); const POSTDIV_RANGE: Range = 1..7; const FBDIV_RANGE: Range = 16..320; - let vco_freq: Hertz = config.vco_freq.try_into().map_err(|_| Error::BadArgument)?; + //First we convert our rate to Hertz as all other rates can be converted to that. + let vco_freq: Hertz = config.vco_freq.into(); + + //Then we try to downscale to u32. + let vco_freq: Hertz = vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { return Err(Error::VCOFreqOutOfRange) From 2462c430b98fb77d33bb93b7031db77629300a6e Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 19:48:09 +0200 Subject: [PATCH 05/14] Fix typo on post_div check Co-authored-by: tdittr --- 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 05940c3..5626868 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -160,7 +160,7 @@ impl PhaseLockedLoop { return Err(Error::VCOFreqOutOfRange) } - if !POSTDIV_RANGE.contains(&config.post_div2) || !POSTDIV_RANGE.contains(&config.post_div2) { + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) { return Err(Error::PostDivOutOfRage) } From 9be7c41400eb71a1335d9a4b814fbb6f339ef36c Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 10:12:38 +0200 Subject: [PATCH 06/14] Working implementation of a PLL HAL. --- rp2040-hal/src/lib.rs | 1 + rp2040-hal/src/pll.rs | 227 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 rp2040-hal/src/pll.rs diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 477db61..b3a3bca 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -15,6 +15,7 @@ pub extern crate rp2040_pac as pac; pub mod adc; pub mod i2c; +pub mod pll; pub mod prelude; pub mod pwm; pub mod rom_data; diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs new file mode 100644 index 0000000..07544c2 --- /dev/null +++ b/rp2040-hal/src/pll.rs @@ -0,0 +1,227 @@ +//! Phase-Locked Loops (PLL) +// See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details + +use core::{ + convert::{ + Infallible, + TryFrom + }, + marker::PhantomData, + ops::{ + RangeInclusive, + Range, + Deref + } +}; + +use embedded_time::{ + fixed_point::FixedPoint, + rate::{ + Hertz, + Generic + } +}; + +use nb::Error::WouldBlock; + +/// State of the PLL +pub trait State {} + + +/// PLL is disabled but is configured. +pub struct Disabled { + refdiv: u8, + vco_freq: Hertz, + post_div1: u8, + post_div2: u8 +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked; + +/// PLL is locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8 +} + +impl State for Disabled {} +impl State for Locked {} +impl State for Locking {} + + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: Deref {} + +impl PhaseLockedLoopDevice for rp2040_pac::PLL_SYS {} +impl PhaseLockedLoopDevice for rp2040_pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state: state + } + } + + /// Releases the underlying device. + pub fn free(self) -> D{ + self.device + } +} + + +/// Error type for the PLL module. +/// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. +pub enum Error { + + /// Proposed VCO frequency is out of range. + VCOFreqOutOfRange, + + /// Feedback Divider value is out of range. + FBDIVOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument +} + +impl PhaseLockedLoop { + + + /// Instantiates and configures a new Phase-Locked-Loop device. + pub fn new(dev: D, refdiv: u8, vco_freq: Generic, post_div1: u8, post_div2: u8) -> Result, Error> { + + const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); + const POSTDIV_RANGE: Range = 1..7; + + let vco_freq = Hertz::::try_from(vco_freq).map_err(|_| Error::BadArgument)?; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VCOFreqOutOfRange) + } + + if !POSTDIV_RANGE.contains(&post_div1) || !POSTDIV_RANGE.contains(&post_div2) { + return Err(Error::PostDivOutOfRage) + } + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, vco_freq, post_div1, post_div2 + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, xosc_frequency: Generic) -> Result, Error>{ + + const FBDIV_RANGE: Range = 16..320; + + let ref_freq_range: Range> = Hertz(5_000_000)..self.state.vco_freq.div(16); + + // Turn off PLL in case it is already running + self.device.pwr.reset(); + self.device.fbdiv_int.reset(); + + let refdiv = self.state.refdiv; + + let ref_freq_hz = Hertz::::try_from(xosc_frequency). + map_err(|_| Error::BadArgument)?. + checked_div(&(refdiv as u32)). + ok_or(Error::BadArgument)?; + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange) + } + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(refdiv as u8); + w + }); + + let fbdiv = *self.state.vco_freq.checked_div(ref_freq_hz.integer()). + ok_or(Error::BadArgument)?.integer() as u16; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FBDIVOutOfRange) + } + + self.device.fbdiv_int.write(|w| unsafe { + w.fbdiv_int().bits(fbdiv); + w + }); + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(refdiv); + w + }); + + // Turn on self.device + self.device.pwr.write(|w| unsafe { + //w.pd().clear_bit(); + //w.vcopd().clear_bit(); + w.bits(0); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + + Ok(self.transition(Locking { + post_div1, post_div2 + })) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData +} + +impl PhaseLockedLoop { + + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + + if self.device.cs.read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + + // Set up post dividers + self.device.prim.write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr.write(|w| unsafe { + //w.postdivpd().clear_bit(); + w.bits(0); + w + }); + + self.transition(Locked) + } + +} From 649998189f5e3de4fa9f27cdda723c72b4081998 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 17:51:03 +0200 Subject: [PATCH 07/14] Move PLL parameters into a struct to help testability and reconfiguration of the PLL. --- rp2040-hal/src/pll.rs | 115 ++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 44 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 07544c2..5071658 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -4,7 +4,8 @@ use core::{ convert::{ Infallible, - TryFrom + TryFrom, + TryInto }, marker::PhantomData, ops::{ @@ -18,7 +19,8 @@ use embedded_time::{ fixed_point::FixedPoint, rate::{ Hertz, - Generic + Generic, + Rate } }; @@ -28,10 +30,11 @@ use nb::Error::WouldBlock; pub trait State {} -/// PLL is disabled but is configured. -pub struct Disabled { - refdiv: u8, - vco_freq: Hertz, +/// PLL is disabled. +pub struct Disabled; + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { post_div1: u8, post_div2: u8 } @@ -39,17 +42,10 @@ pub struct Disabled { /// PLL is locked : it delivers a steady frequency. pub struct Locked; -/// PLL is locking into its designated frequency. -pub struct Locking { - post_div1: u8, - post_div2: u8 -} - impl State for Disabled {} impl State for Locked {} impl State for Locking {} - /// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) pub trait PhaseLockedLoopDevice: Deref {} @@ -97,49 +93,83 @@ pub enum Error { BadArgument } + +/// Parameters for a PLL. +pub struct PLLConfig { + + /// Voltage Controlled Oscillator frequency. + pub vco_freq: R, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8 +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// See Chapter 2, Section 18, §2 +pub mod common_configs { + use super::PLLConfig; + use embedded_time::rate::Megahertz; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_125MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(1500), + refdiv: 1, + post_div1: 6, + post_div2: 2 + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(480), + refdiv: 1, + post_div1: 5, + post_div2: 2 + }; + +} + impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new(dev: D) -> PhaseLockedLoop { + PhaseLockedLoop { + state: Disabled, + device: dev, + } + } - /// Instantiates and configures a new Phase-Locked-Loop device. - pub fn new(dev: D, refdiv: u8, vco_freq: Generic, post_div1: u8, post_div2: u8) -> Result, Error> { + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into { const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; - let vco_freq = Hertz::::try_from(vco_freq).map_err(|_| Error::BadArgument)?; + let vco_freq: Hertz = config.vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { return Err(Error::VCOFreqOutOfRange) } - if !POSTDIV_RANGE.contains(&post_div1) || !POSTDIV_RANGE.contains(&post_div2) { + if !POSTDIV_RANGE.contains(&config.post_div2) || !POSTDIV_RANGE.contains(&config.post_div2) { return Err(Error::PostDivOutOfRage) } - Ok(PhaseLockedLoop { - state: Disabled { - refdiv, vco_freq, post_div1, post_div2 - }, - device: dev, - }) - } - - /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize(self, xosc_frequency: Generic) -> Result, Error>{ - - const FBDIV_RANGE: Range = 16..320; - - let ref_freq_range: Range> = Hertz(5_000_000)..self.state.vco_freq.div(16); + let ref_freq_range: Range> = Hertz(5_000_000)..vco_freq.div(16); // Turn off PLL in case it is already running self.device.pwr.reset(); self.device.fbdiv_int.reset(); - let refdiv = self.state.refdiv; - let ref_freq_hz = Hertz::::try_from(xosc_frequency). map_err(|_| Error::BadArgument)?. - checked_div(&(refdiv as u32)). + checked_div(&(config.refdiv as u32)). ok_or(Error::BadArgument)?; if !ref_freq_range.contains(&ref_freq_hz) { @@ -147,12 +177,14 @@ impl PhaseLockedLoop { } self.device.cs.write(|w| unsafe { - w.refdiv().bits(refdiv as u8); + w.refdiv().bits(config.refdiv); w }); - let fbdiv = *self.state.vco_freq.checked_div(ref_freq_hz.integer()). - ok_or(Error::BadArgument)?.integer() as u16; + let fbdiv = vco_freq.checked_div(ref_freq_hz.integer()). + ok_or(Error::BadArgument)?; + + let fbdiv: u16 = (*fbdiv.integer()).try_into().map_err(|_| Error::BadArgument)?; if !FBDIV_RANGE.contains(&fbdiv) { return Err(Error::FBDIVOutOfRange) @@ -163,11 +195,6 @@ impl PhaseLockedLoop { w }); - self.device.cs.write(|w| unsafe { - w.refdiv().bits(refdiv); - w - }); - // Turn on self.device self.device.pwr.write(|w| unsafe { //w.pd().clear_bit(); @@ -176,8 +203,8 @@ impl PhaseLockedLoop { w }); - let post_div1 = self.state.post_div1; - let post_div2 = self.state.post_div2; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; Ok(self.transition(Locking { post_div1, post_div2 From 20c35d5e14a6a14bcb763d5bf5c373729b4ea2bc Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 19:45:45 +0200 Subject: [PATCH 08/14] Fix type conversion issue --- rp2040-hal/src/pll.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 5071658..05940c3 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -131,7 +131,6 @@ pub mod common_configs { post_div1: 5, post_div2: 2 }; - } impl PhaseLockedLoop { @@ -145,13 +144,17 @@ impl PhaseLockedLoop { } /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into { + pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into>{ const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); const POSTDIV_RANGE: Range = 1..7; const FBDIV_RANGE: Range = 16..320; - let vco_freq: Hertz = config.vco_freq.try_into().map_err(|_| Error::BadArgument)?; + //First we convert our rate to Hertz as all other rates can be converted to that. + let vco_freq: Hertz = config.vco_freq.into(); + + //Then we try to downscale to u32. + let vco_freq: Hertz = vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { return Err(Error::VCOFreqOutOfRange) From 5726bef879d776e84c7bf070a2e602215e22c8fb Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 19:48:09 +0200 Subject: [PATCH 09/14] Fix typo on post_div check Co-authored-by: tdittr --- 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 05940c3..5626868 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -160,7 +160,7 @@ impl PhaseLockedLoop { return Err(Error::VCOFreqOutOfRange) } - if !POSTDIV_RANGE.contains(&config.post_div2) || !POSTDIV_RANGE.contains(&config.post_div2) { + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) { return Err(Error::PostDivOutOfRage) } From eb376cf47b4ad485394f433dd2452e2b93db11ee Mon Sep 17 00:00:00 2001 From: Nic0w Date: Thu, 29 Apr 2021 20:02:36 +0200 Subject: [PATCH 10/14] Using modify() to clear specific bits instead of a blanket 0 on all bits. --- rp2040-hal/src/pll.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 5626868..cccfb6e 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -199,10 +199,9 @@ impl PhaseLockedLoop { }); // Turn on self.device - self.device.pwr.write(|w| unsafe { - //w.pd().clear_bit(); - //w.vcopd().clear_bit(); - w.bits(0); + self.device.pwr.modify(|_,w| { + w.pd().clear_bit(); + w.vcopd().clear_bit(); w }); @@ -245,9 +244,8 @@ impl PhaseLockedLoop { }); // Turn on post divider - self.device.pwr.write(|w| unsafe { - //w.postdivpd().clear_bit(); - w.bits(0); + self.device.pwr.modify(|_,w| { + w.postdivpd().clear_bit(); w }); From eb4ebc782a9d9d70b4e7f5f2e7c582ff2466dc5f Mon Sep 17 00:00:00 2001 From: Nic0w Date: Thu, 29 Apr 2021 20:35:47 +0200 Subject: [PATCH 11/14] Cargo fmt pass. --- rp2040-hal/src/pll.rs | 93 +++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index d57b322..598c554 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -2,26 +2,14 @@ // See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details use core::{ - convert::{ - Infallible, - TryFrom, - TryInto - }, + convert::{Infallible, TryFrom, TryInto}, marker::PhantomData, - ops::{ - RangeInclusive, - Range, - Deref - } + ops::{Deref, Range, RangeInclusive}, }; use embedded_time::{ fixed_point::FixedPoint, - rate::{ - Hertz, - Generic, - Rate - } + rate::{Generic, Hertz, Rate}, }; use nb::Error::WouldBlock; @@ -29,14 +17,13 @@ use nb::Error::WouldBlock; /// State of the PLL pub trait State {} - /// PLL is disabled. pub struct Disabled; /// PLL is configured, started and locking into its designated frequency. pub struct Locking { post_div1: u8, - post_div2: u8 + post_div2: u8, } /// PLL is locked : it delivers a steady frequency. @@ -55,28 +42,26 @@ impl PhaseLockedLoopDevice for rp2040_pac::PLL_USB {} /// A PLL. pub struct PhaseLockedLoop { device: D, - state: S + state: S, } impl PhaseLockedLoop { fn transition(self, state: To) -> PhaseLockedLoop { PhaseLockedLoop { device: self.device, - state: state + state: state, } } /// Releases the underlying device. - pub fn free(self) -> D{ + pub fn free(self) -> D { self.device } } - /// Error type for the PLL module. /// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. pub enum Error { - /// Proposed VCO frequency is out of range. VCOFreqOutOfRange, @@ -90,13 +75,11 @@ pub enum Error { RefFreqOutOfRange, /// Bad argument : overflows, bad conversion, ... - BadArgument + BadArgument, } - /// Parameters for a PLL. pub struct PLLConfig { - /// Voltage Controlled Oscillator frequency. pub vco_freq: R, @@ -107,7 +90,7 @@ pub struct PLLConfig { pub post_div1: u8, /// Post Divider 2 - pub post_div2: u8 + pub post_div2: u8, } /// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! @@ -121,7 +104,7 @@ pub mod common_configs { vco_freq: Megahertz(1500), refdiv: 1, post_div1: 6, - post_div2: 2 + post_div2: 2, }; /// Default, nominal configuration for PLL_USB. @@ -129,12 +112,11 @@ pub mod common_configs { vco_freq: Megahertz(480), refdiv: 1, post_div1: 5, - post_div2: 2 + post_div2: 2, }; } impl PhaseLockedLoop { - /// Instantiates a new Phase-Locked-Loop device. pub fn new(dev: D) -> PhaseLockedLoop { PhaseLockedLoop { @@ -144,8 +126,14 @@ impl PhaseLockedLoop { } /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize(self, xosc_frequency: Generic, config: PLLConfig) -> Result, Error> where R: Into>{ - + pub fn initialize( + self, + xosc_frequency: Generic, + config: PLLConfig, + ) -> Result, Error> + where + R: Into>, + { const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); const POSTDIV_RANGE: Range = 1..7; const FBDIV_RANGE: Range = 16..320; @@ -157,11 +145,12 @@ impl PhaseLockedLoop { let vco_freq: Hertz = vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { - return Err(Error::VCOFreqOutOfRange) + return Err(Error::VCOFreqOutOfRange); } - if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) { - return Err(Error::PostDivOutOfRage) + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) + { + return Err(Error::PostDivOutOfRage); } let ref_freq_range: Range> = Hertz(5_000_000)..vco_freq.div(16); @@ -170,13 +159,13 @@ impl PhaseLockedLoop { self.device.pwr.reset(); self.device.fbdiv_int.reset(); - let ref_freq_hz = Hertz::::try_from(xosc_frequency). - map_err(|_| Error::BadArgument)?. - checked_div(&(config.refdiv as u32)). - ok_or(Error::BadArgument)?; + let ref_freq_hz = Hertz::::try_from(xosc_frequency) + .map_err(|_| Error::BadArgument)? + .checked_div(&(config.refdiv as u32)) + .ok_or(Error::BadArgument)?; if !ref_freq_range.contains(&ref_freq_hz) { - return Err(Error::RefFreqOutOfRange) + return Err(Error::RefFreqOutOfRange); } self.device.cs.write(|w| unsafe { @@ -184,13 +173,16 @@ impl PhaseLockedLoop { w }); - let fbdiv = vco_freq.checked_div(ref_freq_hz.integer()). - ok_or(Error::BadArgument)?; + let fbdiv = vco_freq + .checked_div(ref_freq_hz.integer()) + .ok_or(Error::BadArgument)?; - let fbdiv: u16 = (*fbdiv.integer()).try_into().map_err(|_| Error::BadArgument)?; + let fbdiv: u16 = (*fbdiv.integer()) + .try_into() + .map_err(|_| Error::BadArgument)?; if !FBDIV_RANGE.contains(&fbdiv) { - return Err(Error::FBDIVOutOfRange) + return Err(Error::FBDIVOutOfRange); } self.device.fbdiv_int.write(|w| unsafe { @@ -199,7 +191,7 @@ impl PhaseLockedLoop { }); // Turn on PLL - self.device.pwr.modify(|_,w| { + self.device.pwr.modify(|_, w| { w.pd().clear_bit(); w.vcopd().clear_bit(); w @@ -209,33 +201,31 @@ impl PhaseLockedLoop { let post_div2 = config.post_div2; Ok(self.transition(Locking { - post_div1, post_div2 + post_div1, + post_div2, })) } } /// A token that's given when the PLL is properly locked, so we can safely transition to the next state. pub struct LockedPLLToken { - _private: PhantomData + _private: PhantomData, } impl PhaseLockedLoop { - /// Awaits locking of the PLL. pub fn await_lock(&self) -> nb::Result, Infallible> { - if self.device.cs.read().lock().bit_is_clear() { return Err(WouldBlock); } Ok(LockedPLLToken { - _private: PhantomData + _private: PhantomData, }) } /// Exchanges a token for a Locked PLL. pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { - // Set up post dividers self.device.prim.write(|w| unsafe { w.postdiv1().bits(self.state.post_div1); @@ -244,12 +234,11 @@ impl PhaseLockedLoop { }); // Turn on post divider - self.device.pwr.modify(|_,w| { + self.device.pwr.modify(|_, w| { w.postdivpd().clear_bit(); w }); self.transition(Locked) } - } From 5620bdbd07e4e7fdb343700ead6573afc77c73b5 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Thu, 29 Apr 2021 21:06:11 +0200 Subject: [PATCH 12/14] Move checks in new() so initialize() cannot fail. --- rp2040-hal/src/pll.rs | 66 ++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 598c554..02e8537 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -18,7 +18,12 @@ use nb::Error::WouldBlock; pub trait State {} /// PLL is disabled. -pub struct Disabled; +pub struct Disabled { + refdiv: u8, + fbdiv: u16, + post_div1: u8, + post_div2: u8, +} /// PLL is configured, started and locking into its designated frequency. pub struct Locking { @@ -118,19 +123,11 @@ pub mod common_configs { impl PhaseLockedLoop { /// Instantiates a new Phase-Locked-Loop device. - pub fn new(dev: D) -> PhaseLockedLoop { - PhaseLockedLoop { - state: Disabled, - device: dev, - } - } - - /// Configures and starts the PLL : it switches to Locking state. - pub fn initialize( - self, + pub fn new( + dev: D, xosc_frequency: Generic, config: PLLConfig, - ) -> Result, Error> + ) -> Result, Error> where R: Into>, { @@ -155,10 +152,6 @@ impl PhaseLockedLoop { let ref_freq_range: Range> = Hertz(5_000_000)..vco_freq.div(16); - // Turn off PLL in case it is already running - self.device.pwr.reset(); - self.device.fbdiv_int.reset(); - let ref_freq_hz = Hertz::::try_from(xosc_frequency) .map_err(|_| Error::BadArgument)? .checked_div(&(config.refdiv as u32)) @@ -168,11 +161,6 @@ impl PhaseLockedLoop { return Err(Error::RefFreqOutOfRange); } - self.device.cs.write(|w| unsafe { - w.refdiv().bits(config.refdiv); - w - }); - let fbdiv = vco_freq .checked_div(ref_freq_hz.integer()) .ok_or(Error::BadArgument)?; @@ -185,8 +173,34 @@ impl PhaseLockedLoop { return Err(Error::FBDIVOutOfRange); } + let refdiv = config.refdiv; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, + fbdiv, + post_div1, + post_div2, + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self) -> PhaseLockedLoop { + // Turn off PLL in case it is already running + self.device.pwr.reset(); + self.device.fbdiv_int.reset(); + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(self.state.refdiv); + w + }); + self.device.fbdiv_int.write(|w| unsafe { - w.fbdiv_int().bits(fbdiv); + w.fbdiv_int().bits(self.state.fbdiv); w }); @@ -197,13 +211,13 @@ impl PhaseLockedLoop { w }); - let post_div1 = config.post_div1; - let post_div2 = config.post_div2; + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; - Ok(self.transition(Locking { + self.transition(Locking { post_div1, post_div2, - })) + }) } } From 64dee52dd57cfe4f206b50d4160547c58360d881 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Wed, 5 May 2021 07:55:51 +0200 Subject: [PATCH 13/14] Satisfies clippy --- rp2040-hal/src/pll.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 02e8537..849736e 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -54,7 +54,7 @@ impl PhaseLockedLoop { fn transition(self, state: To) -> PhaseLockedLoop { PhaseLockedLoop { device: self.device, - state: state, + state, } } @@ -131,7 +131,8 @@ impl PhaseLockedLoop { where R: Into>, { - const VCO_FREQ_RANGE: RangeInclusive> = Hertz(400_000_000)..=Hertz(1600_000_000); + const VCO_FREQ_RANGE: RangeInclusive> = + Hertz(400_000_000)..=Hertz(1_600_000_000); const POSTDIV_RANGE: Range = 1..7; const FBDIV_RANGE: Range = 16..320; From a663b1f55295d215fd27344402f31ef809104824 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 9 May 2021 09:42:31 +0200 Subject: [PATCH 14/14] Clippy, second pass for errors in CI. --- rp2040-hal/src/pll.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 849736e..6e1a384 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -68,10 +68,10 @@ impl PhaseLockedLoop { /// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. pub enum Error { /// Proposed VCO frequency is out of range. - VCOFreqOutOfRange, + VcoFreqOutOfRange, /// Feedback Divider value is out of range. - FBDIVOutOfRange, + FeedbackDivOutOfRange, /// Post Divider value is out of range. PostDivOutOfRage, @@ -143,7 +143,7 @@ impl PhaseLockedLoop { let vco_freq: Hertz = vco_freq.try_into().map_err(|_| Error::BadArgument)?; if !VCO_FREQ_RANGE.contains(&vco_freq) { - return Err(Error::VCOFreqOutOfRange); + return Err(Error::VcoFreqOutOfRange); } if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) @@ -171,7 +171,7 @@ impl PhaseLockedLoop { .map_err(|_| Error::BadArgument)?; if !FBDIV_RANGE.contains(&fbdiv) { - return Err(Error::FBDIVOutOfRange); + return Err(Error::FeedbackDivOutOfRange); } let refdiv = config.refdiv;