diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1e1b275..3622df0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -27,3 +27,7 @@ jobs: with: command: clippy args: -- -Dwarnings + - uses: actions-rs/cargo@v1 + with: + command: test + args: --target x86_64-unknown-linux-gnu diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index e567ef1..192cdbf 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -12,10 +12,11 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7.1" embedded-hal = { version = "0.2.4", features = ["unproven"] } +embedded-time = "0.10.1" nb = "1.0.0" rp2040-pac = { git = "https://github.com/rp-rs/rp2040-pac", branch="main" } [dev-dependencies] cortex-m-rt = "0.6.13" panic-halt = "0.2.0" -rp2040-boot2 = { git = "https://github.com/rp-rs/rp2040-boot2-rs", branch="main" } +rp2040-boot2 = { git = "https://github.com/rp-rs/rp2040-boot2-rs", branch="main" } \ No newline at end of file diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 719afcc..58dbbaa 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -10,6 +10,7 @@ extern crate cortex_m; extern crate embedded_hal as hal; extern crate nb; + pub extern crate rp2040_pac as pac; pub mod adc; @@ -25,3 +26,4 @@ pub mod timer; pub mod uart; pub mod usb; pub mod watchdog; +pub mod xosc; diff --git a/rp2040-hal/src/rom_data.rs b/rp2040-hal/src/rom_data.rs index e55ccb3..8b80148 100644 --- a/rp2040-hal/src/rom_data.rs +++ b/rp2040-hal/src/rom_data.rs @@ -266,7 +266,7 @@ float_funcs! { /// Convert a float to a signed 64-bit integer, rounding towards -Infinity, and clamping /// the result to lie within the range -0x8000000000000000 to 0x7FFFFFFFFFFFFFFF 0x6c float_to_int64(v: f32) -> i64; - /// Convert a float to a signed fixed point 64-bit integer representation where n + /// Convert a float to a signed fixed point 64-bit integer representation where n /// specifies the position of the binary point in the resulting fixed point representation - /// e.g. _float2fix(0.5f, 16) == 0x8000. This method rounds towards -Infinity, and /// clamps the resulting integer to lie within the range -0x8000000000000000 to diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs new file mode 100644 index 0000000..a499488 --- /dev/null +++ b/rp2040-hal/src/xosc.rs @@ -0,0 +1,193 @@ +//! Crystal Oscillator (XOSC) +// See [Chapter 2 Section 16](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details + +use core::convert::TryInto; +use core::{convert::Infallible, ops::RangeInclusive}; + +use embedded_time::{ + duration::{Duration, Milliseconds}, + fixed_point::FixedPoint, + fraction::Fraction, + rate::{Hertz, Megahertz, Rate}, +}; + +use nb::Error::WouldBlock; + +/// State of the Crystal Oscillator (typestate trait) +pub trait State {} + +/// XOSC is disabled (typestate) +pub struct Disabled; + +/// XOSC is initialized, ie we've given parameters (typestate) +pub struct Initialized { + freq_hz: Hertz, +} + +/// Stable state (typestate) +pub struct Stable { + freq_hz: Hertz, +} + +/// XOSC is in dormant mode (see Chapter 2, Section 16, §5) +pub struct Dormant; + +impl State for Disabled {} +impl State for Initialized {} +impl State for Stable {} +impl State for Dormant {} + +/// Possible errors when initializing the CrystalOscillator +pub enum Error { + /// Frequency is out of the 1-15MHz range (see datasheet) + FrequencyOutOfRange, + + /// Argument is bad : overflows, ... + BadArgument, +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +pub fn setup_xosc_blocking( + xosc_dev: rp2040_pac::XOSC, + frequency: Hertz, +) -> Result, Error> { + let initialized_xosc = CrystalOscillator::new(xosc_dev).initialize(frequency)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// A Crystal Oscillator. +pub struct CrystalOscillator { + device: rp2040_pac::XOSC, + state: S, +} + +impl CrystalOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> CrystalOscillator { + CrystalOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> rp2040_pac::XOSC { + self.device + } +} + +impl CrystalOscillator { + /// Creates a new CrystalOscillator from the underlying device. + pub fn new(dev: rp2040_pac::XOSC) -> Self { + CrystalOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the XOSC : frequency range is set, startup delay is calculated and set. + pub fn initialize(self, frequency: Hertz) -> Result, Error> { + const ALLOWED_FREQUENCY_RANGE: RangeInclusive> = + Megahertz(1)..=Megahertz(15); + const STABLE_DELAY: Milliseconds = Milliseconds(1_u32); + const DIVIDER: Fraction = Fraction::new(256, 1); + + let freq_mhz: Megahertz = frequency.into(); + + if !ALLOWED_FREQUENCY_RANGE.contains(&freq_mhz) { + return Err(Error::FrequencyOutOfRange); + } + + self.device.ctrl.write(|w| { + w.freq_range()._1_15mhz(); + w + }); + + //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz + let delay_to_hz: Hertz = STABLE_DELAY.to_rate().map_err(|_| Error::BadArgument)?; + + //startup_delay = ((freq_hz * 10e-3) / 256) = ((freq_hz / 1000) / 256) + //See Chapter 2, Section 16, §3) + //We do the calculation first. + let startup_delay = frequency + .checked_div(delay_to_hz.integer()) + .and_then(|r| r.to_generic::(DIVIDER).ok()) + .ok_or(Error::BadArgument)?; + + //Then we check if it fits into an u16. + let startup_delay: u16 = (*startup_delay.integer()) + .try_into() + .map_err(|_| Error::BadArgument)?; + + self.device.startup.write(|w| unsafe { + w.delay().bits(startup_delay); + w + }); + + self.device.ctrl.write(|w| { + w.enable().enable(); + w + }); + + Ok(self.transition(Initialized { freq_hz: frequency })) + } +} + +/// A token that's given when the oscillator is stablilzed, and can be exchanged to proceed to the next stage. +pub struct StableOscillatorToken { + _private: (), +} + +impl CrystalOscillator { + /// One has to wait for the startup delay before using the oscillator, ie awaiting stablilzation of the XOSC + pub fn await_stabilization(&self) -> nb::Result { + if self.device.status.read().stable().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(StableOscillatorToken { _private: () }) + } + + /// Returns the stablilzed oscillator + pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Stable { freq_hz }) + } +} + +impl CrystalOscillator { + /// Operating frequency of the XOSC in hertz + pub fn operating_frequency(&self) -> Hertz { + self.state.freq_hz + } + + /// Disables the XOSC + pub fn disable(self) -> CrystalOscillator { + self.device.ctrl.modify(|_r, w| { + w.enable().disable(); + w + }); + + self.transition(Disabled) + } + + /// Put the XOSC in DORMANT state. + /// This method is marked unsafe because prior to switch the XOSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the XOSC to DORMANT state. + /// See Chapter 2, Section 16, §5) for details. + pub unsafe fn dormant(self) -> CrystalOscillator { + //taken from the C SDK + const XOSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant.write(|w| { + w.bits(XOSC_DORMANT_VALUE); + w + }); + + self.transition(Dormant) + } +}