From e2a4260c7b35d7e6d5aae408edd9f9b067e0073e Mon Sep 17 00:00:00 2001 From: longfangsong Date: Sun, 21 Feb 2021 13:47:20 +0800 Subject: [PATCH 1/3] impl embedded_hal::spi::FullDuplex for Spi --- src/spi.rs | 103 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/src/spi.rs b/src/spi.rs index 89a7f91..3cab426 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -1,47 +1,50 @@ //! (TODO) Serial Peripheral Interface (SPI) -use crate::pac::SPI0; use crate::clock::Clocks; +use crate::pac::spi0::ctrlr0::TMOD_A as transfer_mode; +use crate::pac::SPI0; use crate::sysctl::{self, APB0}; -pub use embedded_hal::spi::{Mode, Polarity, Phase}; use core::convert::Infallible; +pub use embedded_hal::spi::{Mode, Phase, Polarity}; -/// pub struct Spi { - spi: SPI + spi: SPI, + // Different with other MCUs like STM32 and Atmel + // k210 use fpioa to map a pin directly to SPI SS 0-3 instead of using an ordinary GPIO + // when transferring data, we'll set `pac::SPIX::ser` to (1 << cs_id) to select this device + cs_id: u8, } impl Spi { pub fn spi0( - spi: SPI0, - mode: Mode, - frame_format: FrameFormat, - endian: Endian, - clock: &Clocks, - apb0: &mut APB0 + spi: SPI0, + cs_id: u8, // todo: currently we presume SPI0_SS is already configured correctly, maybe we can do this for the user? + mode: Mode, + frame_format: FrameFormat, + endian: Endian, + clock: &Clocks, + apb0: &mut APB0, ) -> Self { let work_mode = hal_mode_to_pac(mode); let frame_format = frame_format_to_pac(frame_format); - let tmod = crate::pac::spi0::ctrlr0::TMOD_A::TRANS_RECV; // todo other modes let endian = endian as u32; let data_bit_length = 8; // todo more length let _ = clock; // todo unsafe { // no interrupts for now - spi.imr.write(|w| w.bits(0x00)); + spi.imr.write(|w| w.bits(0x0)); // no dma for now - spi.dmacr.write(|w| w.bits(0x00)); + spi.dmacr.write(|w| w.bits(0x0)); spi.dmatdlr.write(|w| w.bits(0x10)); - spi.dmardlr.write(|w| w.bits(0x00)); + spi.dmardlr.write(|w| w.bits(0x0)); // no slave access for now - spi.ser.write(|w| w.bits(0x00)); - spi.ssienr.write(|w| w.bits(0x00)); + spi.ser.write(|w| w.bits(0x0)); + spi.ssienr.write(|w| w.bits(0x0)); // set control registers spi.ctrlr0.write(|w| { + // no need to set tmod here, which will (and needs to) be set on each send/recv w.work_mode() .variant(work_mode) - .tmod() - .variant(tmod) .frame_format() .variant(frame_format) .data_length() @@ -53,19 +56,27 @@ impl Spi { // enable APB0 bus apb0.enable(); // enable peripheral via sysctl - sysctl::clk_en_peri().modify(|_r, w| - w.spi0_clk_en().set_bit()); - Spi { spi } + sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().set_bit()); + Spi { spi, cs_id } } pub fn release(self) -> SPI0 { // power off - sysctl::clk_en_peri().modify(|_r, w| - w.spi0_clk_en().clear_bit()); + sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().clear_bit()); self.spi } + + /// for making our life easier to use the same SPI interface but with different chip selected + pub fn take_for_cs(self, cs_id: u8) -> Self { + Self { + spi: self.spi, + cs_id, + } + } } +// todo: Shall we make FrameFormat a type parameter instead? +// so FullDuplex can be implemented for Spi only impl embedded_hal::spi::FullDuplex for Spi { /// An enumeration of SPI errors type Error = Infallible; @@ -75,12 +86,52 @@ impl embedded_hal::spi::FullDuplex for Spi { /// **NOTE** A word must be sent to the slave before attempting to call this /// method. fn try_read(&mut self) -> nb::Result { - todo!() + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::RECV)); + unsafe { + // C sdk said ctrlr1 = rx_len(1) / frame_width(1) - 1; + self.spi.ctrlr1.write(|w| w.bits(0x0)); + // enable spi + self.spi.ssienr.write(|w| w.bits(0x1)); + // select that chip + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + // clear dr + self.spi.dr[0].write(|w| w.bits(0xffffffff)); + } + let bytes_in_buffer = self.spi.rxflr.read().bits(); + let result = if bytes_in_buffer == 0 { + Err(nb::Error::WouldBlock) + } else { + Ok(self.spi.dr[0].read().bits() as u8) + }; + self.spi.ser.reset(); + self.spi.ssienr.reset(); + result } /// Sends a word to the slave fn try_send(&mut self, word: u8) -> nb::Result<(), Self::Error> { - todo!("{}", word) + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::TRANS)); + unsafe { + self.spi.ssienr.write(|w| w.bits(0x0)); + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + } + const MAX_FIFO_SIZE: u32 = 32; + let empty_in_buffer = MAX_FIFO_SIZE - self.spi.txflr.read().bits(); + let result = if empty_in_buffer == 0 { + Err(nb::Error::WouldBlock) + } else { + unsafe { + self.spi.dr[0].write(|w| w.bits(word as u32)); + } + Ok(()) + }; + self.spi.ser.reset(); + self.spi.ssienr.reset(); + result } } @@ -101,7 +152,7 @@ pub enum Endian { #[inline] fn hal_mode_to_pac(mode: Mode) -> crate::pac::spi0::ctrlr0::WORK_MODE_A { use crate::pac::spi0::ctrlr0::WORK_MODE_A; - use {Polarity::*, Phase::*}; + use {Phase::*, Polarity::*}; match (mode.polarity, mode.phase) { (IdleLow, CaptureOnFirstTransition) => WORK_MODE_A::MODE0, (IdleLow, CaptureOnSecondTransition) => WORK_MODE_A::MODE1, From 8383633f51b6b3b51fff4047c2122f6eb0d9df89 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Mon, 22 Feb 2021 07:25:12 +0800 Subject: [PATCH 2/3] use the right APB --- src/spi.rs | 6 +++--- src/sysctl.rs | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/spi.rs b/src/spi.rs index 3cab426..1acb955 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -3,7 +3,7 @@ use crate::clock::Clocks; use crate::pac::spi0::ctrlr0::TMOD_A as transfer_mode; use crate::pac::SPI0; -use crate::sysctl::{self, APB0}; +use crate::sysctl::{self, APB2}; use core::convert::Infallible; pub use embedded_hal::spi::{Mode, Phase, Polarity}; @@ -23,7 +23,7 @@ impl Spi { frame_format: FrameFormat, endian: Endian, clock: &Clocks, - apb0: &mut APB0, + apb2: &mut APB2, ) -> Self { let work_mode = hal_mode_to_pac(mode); let frame_format = frame_format_to_pac(frame_format); @@ -54,7 +54,7 @@ impl Spi { spi.endian.write(|w| w.bits(endian)); } // enable APB0 bus - apb0.enable(); + apb2.enable(); // enable peripheral via sysctl sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().set_bit()); Spi { spi, cs_id } diff --git a/src/sysctl.rs b/src/sysctl.rs index 2440436..8c36b95 100644 --- a/src/sysctl.rs +++ b/src/sysctl.rs @@ -135,9 +135,37 @@ impl APB0 { // _ownership: () // } -// pub struct APB2 { -// _ownership: () -// } +pub struct APB2 { + _ownership: (), +} + +impl APB2 { + pub(crate) fn enable(&mut self) { + clk_en_cent().modify(|_r, w| w.apb2_clk_en().set_bit()); + } + + pub fn set_frequency(&mut self, expected_freq: impl Into) -> Hertz { + let aclk = ACLK::steal(); + let aclk_frequency = aclk.get_frequency().0 as i64; + // apb2_frequency = aclk_frequency / (apb2_clk_sel + 1) + let apb2_clk_sel = (aclk_frequency / expected_freq.into().0 as i64 - 1) + .max(0) + .min(0b111) as u8; + unsafe { + sysctl() + .clk_sel0 + .modify(|_, w| w.apb2_clk_sel().bits(apb2_clk_sel)); + } + Hertz(aclk_frequency as u32 / (apb2_clk_sel as u32 + 1)) + } + + pub fn get_frequency(&self) -> Hertz { + let aclk = ACLK::steal(); + let aclk_frequency = aclk.get_frequency().0 as i64; + let apb2_clk_sel = sysctl().clk_sel0.read().apb2_clk_sel().bits(); + Hertz(aclk_frequency as u32 / (apb2_clk_sel as u32 + 1)) + } +} /// PLL0, which source is CLOCK_FREQ_IN0, /// and the output can be used on ACLK(CPU), SPIs, etc. From e4bf1e8e75841595de3c93f450e540d0ed051e33 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Sun, 28 Feb 2021 20:41:58 +0800 Subject: [PATCH 3/3] sysctl support for spi --- src/spi.rs | 308 +++++++++++++++++++++++++++++++------------------- src/sysctl.rs | 59 +++++++++- 2 files changed, 252 insertions(+), 115 deletions(-) diff --git a/src/spi.rs b/src/spi.rs index 1acb955..4e2747b 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -2,10 +2,16 @@ use crate::clock::Clocks; use crate::pac::spi0::ctrlr0::TMOD_A as transfer_mode; -use crate::pac::SPI0; +use crate::pac::{SPI0, SPI1}; use crate::sysctl::{self, APB2}; use core::convert::Infallible; -pub use embedded_hal::spi::{Mode, Phase, Polarity}; +pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +use crate::pac::UARTHS; +use crate::serial::Tx; +use crate::stdout::Stdout; +use crate::time::Hertz; +use core::fmt::Write; pub struct Spi { spi: SPI, @@ -15,126 +21,200 @@ pub struct Spi { cs_id: u8, } -impl Spi { - pub fn spi0( - spi: SPI0, - cs_id: u8, // todo: currently we presume SPI0_SS is already configured correctly, maybe we can do this for the user? - mode: Mode, - frame_format: FrameFormat, - endian: Endian, - clock: &Clocks, - apb2: &mut APB2, - ) -> Self { - let work_mode = hal_mode_to_pac(mode); - let frame_format = frame_format_to_pac(frame_format); - let endian = endian as u32; - let data_bit_length = 8; // todo more length - let _ = clock; // todo - unsafe { - // no interrupts for now - spi.imr.write(|w| w.bits(0x0)); - // no dma for now - spi.dmacr.write(|w| w.bits(0x0)); - spi.dmatdlr.write(|w| w.bits(0x10)); - spi.dmardlr.write(|w| w.bits(0x0)); - // no slave access for now - spi.ser.write(|w| w.bits(0x0)); - spi.ssienr.write(|w| w.bits(0x0)); - // set control registers - spi.ctrlr0.write(|w| { - // no need to set tmod here, which will (and needs to) be set on each send/recv - w.work_mode() - .variant(work_mode) - .frame_format() - .variant(frame_format) - .data_length() - .bits(data_bit_length - 1) - }); - spi.spi_ctrlr0.reset(); // standard - spi.endian.write(|w| w.bits(endian)); - } - // enable APB0 bus - apb2.enable(); - // enable peripheral via sysctl - sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().set_bit()); - Spi { spi, cs_id } - } +macro_rules! spi { + ($SPIX: ident, $spix: ident, $spix_clk_en: ident) => { + impl Spi<$SPIX> { + pub fn $spix( + spi: $SPIX, + cs_id: u8, // todo: currently we presume SPI0_SS is already configured correctly, maybe we can do this for the user? + mode: Mode, + frame_format: FrameFormat, + endian: Endian, + clock: &Clocks, + apb2: &mut APB2, + d8g: &mut Stdout>, + ) -> Self { + let work_mode = hal_mode_to_pac(mode); + let frame_format = frame_format_to_pac(frame_format); + let endian = endian as u32; + let data_bit_length = 8; // todo more length + let _ = clock; // todo + unsafe { + // no interrupts for now + spi.imr.write(|w| w.bits(0x0)); + // no dma for now + spi.dmacr.write(|w| w.bits(0x0)); + spi.dmatdlr.write(|w| w.bits(0x10)); + spi.dmardlr.write(|w| w.bits(0x0)); + // no slave access for now + spi.ser.write(|w| w.bits(0x0)); + spi.ssienr.write(|w| w.bits(0x0)); + // set control registers + spi.ctrlr0.write(|w| { + // no need to set tmod here, which will (and needs to) be set on each send/recv + w.work_mode() + .variant(work_mode) + .frame_format() + .variant(frame_format) + .data_length() + .bits(data_bit_length - 1) + }); + spi.spi_ctrlr0.reset(); // standard + spi.endian.write(|w| w.bits(endian)); + } + // enable APB0 bus + writeln!(d8g, "enable APB0 bus").unwrap(); + apb2.enable(); + // enable peripheral via sysctl + writeln!(d8g, "enable peripheral via sysctl").unwrap(); + sysctl::clk_en_peri().modify(|_r, w| w.$spix_clk_en().set_bit()); + Spi { spi, cs_id } + } - pub fn release(self) -> SPI0 { - // power off - sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().clear_bit()); - self.spi - } + pub fn try_send_debug(&mut self, word: u8, d8g: &mut Stdout>) { + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::TRANS)); + writeln!(d8g, "TRANS").unwrap(); + unsafe { + self.spi.ssienr.write(|w| w.bits(0x0)); + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + } + const MAX_FIFO_SIZE: u32 = 32; + let empty_in_buffer = MAX_FIFO_SIZE - self.spi.txflr.read().bits(); + writeln!(d8g, "{:?}", empty_in_buffer).unwrap(); + if empty_in_buffer != 0 { + unsafe { + self.spi.dr[0].write(|w| w.bits(word as u32)); + } + }; + writeln!(d8g, "written").unwrap(); + self.spi.ser.reset(); + self.spi.ssienr.reset(); + } - /// for making our life easier to use the same SPI interface but with different chip selected - pub fn take_for_cs(self, cs_id: u8) -> Self { - Self { - spi: self.spi, - cs_id, - } - } -} + pub fn try_read_debug(&mut self, d8g: &mut Stdout>) -> u8 { + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::RECV)); + writeln!(d8g, "RECV").unwrap(); + unsafe { + // C sdk said ctrlr1 = rx_len(1) / frame_width(1) - 1; + self.spi.ctrlr1.write(|w| w.bits(0x0)); + // enable spi + self.spi.ssienr.write(|w| w.bits(0x1)); + // select that chip + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + // clear dr + self.spi.dr[0].write(|w| w.bits(0xffffffff)); + } + let bytes_in_buffer = self.spi.rxflr.read().bits(); + writeln!(d8g, "{:?}", bytes_in_buffer).unwrap(); + let result = self.spi.dr[0].read().bits() as u8; + self.spi.ser.reset(); + self.spi.ssienr.reset(); + result + } -// todo: Shall we make FrameFormat a type parameter instead? -// so FullDuplex can be implemented for Spi only -impl embedded_hal::spi::FullDuplex for Spi { - /// An enumeration of SPI errors - type Error = Infallible; - - /// Reads the word stored in the shift register - /// - /// **NOTE** A word must be sent to the slave before attempting to call this - /// method. - fn try_read(&mut self) -> nb::Result { - self.spi - .ctrlr0 - .modify(|_, w| w.tmod().variant(transfer_mode::RECV)); - unsafe { - // C sdk said ctrlr1 = rx_len(1) / frame_width(1) - 1; - self.spi.ctrlr1.write(|w| w.bits(0x0)); - // enable spi - self.spi.ssienr.write(|w| w.bits(0x1)); - // select that chip - self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); - // clear dr - self.spi.dr[0].write(|w| w.bits(0xffffffff)); - } - let bytes_in_buffer = self.spi.rxflr.read().bits(); - let result = if bytes_in_buffer == 0 { - Err(nb::Error::WouldBlock) - } else { - Ok(self.spi.dr[0].read().bits() as u8) - }; - self.spi.ser.reset(); - self.spi.ssienr.reset(); - result - } + pub fn release(self) -> $SPIX { + // power off + sysctl::clk_en_peri().modify(|_r, w| w.$spix_clk_en().clear_bit()); + self.spi + } - /// Sends a word to the slave - fn try_send(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.spi - .ctrlr0 - .modify(|_, w| w.tmod().variant(transfer_mode::TRANS)); - unsafe { - self.spi.ssienr.write(|w| w.bits(0x0)); - self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + /// for making our life easier to use the same SPI interface but with different chip selected + pub fn take_for_cs(self, cs_id: u8) -> Self { + Self { + spi: self.spi, + cs_id, + } + } + + /// set clock rate + pub fn set_clock_rate( + &mut self, + expected_rate: impl Into, + clk_ctl: &sysctl::$SPIX, + ) -> Hertz { + let expected_rate = expected_rate.into().0; + let source = clk_ctl.get_frequency().0; + let spi_baudr = (source / expected_rate).max(2).min(65534) as u32; + unsafe { + self.spi.baudr.write(|w| w.bits(spi_baudr)); + } + Hertz(source / spi_baudr) + } } - const MAX_FIFO_SIZE: u32 = 32; - let empty_in_buffer = MAX_FIFO_SIZE - self.spi.txflr.read().bits(); - let result = if empty_in_buffer == 0 { - Err(nb::Error::WouldBlock) - } else { - unsafe { - self.spi.dr[0].write(|w| w.bits(word as u32)); + + // todo: Shall we make FrameFormat a type parameter instead? + // so FullDuplex can be implemented for Spi only + impl embedded_hal::spi::FullDuplex for Spi<$SPIX> { + /// An enumeration of SPI errors + type Error = Infallible; + + /// Reads the word stored in the shift register + /// + /// **NOTE** A word must be sent to the slave before attempting to call this + /// method. + fn try_read(&mut self) -> nb::Result { + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::RECV)); + unsafe { + // C sdk said ctrlr1 = rx_len(1) / frame_width(1) - 1; + self.spi.ctrlr1.write(|w| w.bits(0x0)); + // enable spi + self.spi.ssienr.write(|w| w.bits(0x1)); + // select that chip + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + // clear dr + self.spi.dr[0].write(|w| w.bits(0xffffffff)); + } + let bytes_in_buffer = self.spi.rxflr.read().bits(); + let result = if bytes_in_buffer == 0 { + Err(nb::Error::WouldBlock) + } else { + Ok(self.spi.dr[0].read().bits() as u8) + }; + self.spi.ser.reset(); + self.spi.ssienr.reset(); + result } - Ok(()) - }; - self.spi.ser.reset(); - self.spi.ssienr.reset(); - result - } + + /// Sends a word to the slave + fn try_send(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.spi + .ctrlr0 + .modify(|_, w| w.tmod().variant(transfer_mode::TRANS)); + unsafe { + self.spi.ssienr.write(|w| w.bits(0x0)); + self.spi.ser.write(|w| w.bits(0x1 << self.cs_id)); + } + const MAX_FIFO_SIZE: u32 = 32; + let empty_in_buffer = MAX_FIFO_SIZE - self.spi.txflr.read().bits(); + let result = if empty_in_buffer == 0 { + Err(nb::Error::WouldBlock) + } else { + unsafe { + self.spi.dr[0].write(|w| w.bits(word as u32)); + } + Ok(()) + }; + self.spi.ser.reset(); + self.spi.ssienr.reset(); + result + } + } + + impl embedded_hal::blocking::spi::transfer::Default for Spi<$SPIX> {} + + impl embedded_hal::blocking::spi::write::Default for Spi<$SPIX> {} + }; } +spi!(SPI0, spi0, spi0_clk_en); +spi!(SPI1, spi1, spi1_clk_en); + #[derive(Clone, Copy, PartialEq, Eq)] pub enum FrameFormat { Standard, diff --git a/src/sysctl.rs b/src/sysctl.rs index 8c36b95..c2ddd0d 100644 --- a/src/sysctl.rs +++ b/src/sysctl.rs @@ -74,7 +74,10 @@ impl SysctlExt for SYSCTL { Parts { aclk: ACLK { _ownership: () }, apb0: APB0 { _ownership: () }, + apb2: APB2 { _ownership: () }, pll0: PLL0 { _ownership: () }, + spi0: SPI0 { _ownership: () }, + spi1: SPI1 { _ownership: () }, } } } @@ -87,6 +90,12 @@ pub struct Parts { pub pll0: PLL0, /// entry for controlling the enable/disable/frequency of apb0 pub apb0: APB0, + /// entry for controlling the enable/disable/frequency of apb2 + pub apb2: APB2, + /// entry for controlling the enable/disable/frequency of spi0 + pub spi0: SPI0, + /// entry for controlling the enable/disable/frequency of spi1 + pub spi1: SPI1, // todo: SRAM, APB-bus, ROM, DMA, AI, PLL1, PLL2, APB1, APB2 } @@ -264,7 +273,7 @@ pub struct ACLK { /// ACLK clock frequency control impl ACLK { - pub fn steal() -> Self { + pub(crate) fn steal() -> Self { ACLK { _ownership: () } } @@ -330,3 +339,51 @@ impl ACLK { } } } + +pub struct SPI0 { + _ownership: (), +} + +impl SPI0 { + pub fn set_frequency(&mut self, expected_freq: impl Into) -> Hertz { + let expected_freq = expected_freq.into().0; + // spi0 = source(pll0) / ((spi0_clk_threshold + 1) * 2) + let source = PLL0::steal().get_frequency().0; + let spi0_clk_threshold = (source / expected_freq / 2 - 1).min(u8::max_value() as _) as u8; + unsafe { + sysctl() + .clk_th1 + .modify(|_, w| w.spi0_clk().bits(spi0_clk_threshold)); + } + Hertz(source / ((spi0_clk_threshold as u32 + 1) * 2)) + } + pub fn get_frequency(&self) -> Hertz { + let source = PLL0::steal().get_frequency().0; + let spi0_clk_threshold = sysctl().clk_th1.read().spi0_clk().bits() as u32; + Hertz(source / ((spi0_clk_threshold as u32 + 1) * 2)) + } +} + +pub struct SPI1 { + _ownership: (), +} + +impl SPI1 { + pub fn set_frequency(&mut self, expected_freq: impl Into) -> Hertz { + let expected_freq = expected_freq.into().0; + // spi1 = source(pll0) / ((spi1_clk_threshold + 1) * 2) + let source = PLL0::steal().get_frequency().0; + let spi1_clk_threshold = (source / expected_freq / 2 - 1).min(u8::max_value() as _) as u8; + unsafe { + sysctl() + .clk_th1 + .modify(|_, w| w.spi1_clk().bits(spi1_clk_threshold)); + } + Hertz(source / ((spi1_clk_threshold as u32 + 1) * 2)) + } + pub fn get_frequency(&self) -> Hertz { + let source = PLL0::steal().get_frequency().0; + let spi1_clk_threshold = sysctl().clk_th1.read().spi1_clk().bits() as u32; + Hertz(source / ((spi1_clk_threshold as u32 + 1) * 2)) + } +}