diff --git a/src/spi.rs b/src/spi.rs index 89a7f91..4e2747b 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -1,89 +1,220 @@ //! (TODO) Serial Peripheral Interface (SPI) -use crate::pac::SPI0; use crate::clock::Clocks; -use crate::sysctl::{self, APB0}; -pub use embedded_hal::spi::{Mode, Polarity, Phase}; +use crate::pac::spi0::ctrlr0::TMOD_A as transfer_mode; +use crate::pac::{SPI0, SPI1}; +use crate::sysctl::{self, APB2}; use core::convert::Infallible; +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 + 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 - ) -> 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)); - // no dma for now - spi.dmacr.write(|w| w.bits(0x00)); - spi.dmatdlr.write(|w| w.bits(0x10)); - spi.dmardlr.write(|w| w.bits(0x00)); - // no slave access for now - spi.ser.write(|w| w.bits(0x00)); - spi.ssienr.write(|w| w.bits(0x00)); - // set control registers - spi.ctrlr0.write(|w| { - w.work_mode() - .variant(work_mode) - .tmod() - .variant(tmod) - .frame_format() - .variant(frame_format) - .data_length() - .bits(data_bit_length - 1) - }); - spi.spi_ctrlr0.reset(); // standard - spi.endian.write(|w| w.bits(endian)); +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 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(); + } + + 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 + } + + pub fn release(self) -> $SPIX { + // power off + sysctl::clk_en_peri().modify(|_r, w| w.$spix_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, + } + } + + /// 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) + } } - // enable APB0 bus - apb0.enable(); - // enable peripheral via sysctl - sysctl::clk_en_peri().modify(|_r, w| - w.spi0_clk_en().set_bit()); - Spi { spi } - } - pub fn release(self) -> SPI0 { - // power off - sysctl::clk_en_peri().modify(|_r, w| - w.spi0_clk_en().clear_bit()); - self.spi - } -} + // 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; -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 + } - /// 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 { - todo!() - } + /// 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 + } + } - /// Sends a word to the slave - fn try_send(&mut self, word: u8) -> nb::Result<(), Self::Error> { - todo!("{}", word) - } + 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, @@ -101,7 +232,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, diff --git a/src/sysctl.rs b/src/sysctl.rs index 2440436..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 } @@ -135,9 +144,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. @@ -236,7 +273,7 @@ pub struct ACLK { /// ACLK clock frequency control impl ACLK { - pub fn steal() -> Self { + pub(crate) fn steal() -> Self { ACLK { _ownership: () } } @@ -302,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)) + } +}