From b0de7c9be6fba15be20aba092436464c26a61f96 Mon Sep 17 00:00:00 2001 From: Nic0w Date: Sun, 25 Apr 2021 09:46:53 +0200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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) }