diff --git a/Cargo.toml b/Cargo.toml index 1947a61..286778a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,4 +41,4 @@ debug-assertions = false # <- lto = true opt-level = 'z' # <- overflow-checks = false # <- -# strip = true # Automatically strip symbols from the binary. +strip = true # Automatically strip symbols from the binary. diff --git a/README.md b/README.md index e0b27c9..a3db5da 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ example. ### Using VS Code -Assuming a working debug connection to your VA108xx board, you can debug using VS Code with -the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). Please make sure that -[`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146) +Assuming a working debug connection to your VA416xx board, you can debug using VS Code with +the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). +Please make sure that [`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146) are installed as well. Some sample configuration files for VS code were provided and can be used by running diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index e9406e1..692eaa2 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -8,8 +8,13 @@ cortex-m = "0.7" cortex-m-rt = "0.7" embedded-hal = "1" panic-rtt-target = { version = "0.1.3" } +panic-halt = { version = "0.2" } rtt-target = { version = "0.5" } crc = "3" [dependencies.va416xx-hal] path = "../va416xx-hal" + +[features] +default = [] +rtt-panic = [] diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index 3a35405..af0ba40 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -1,17 +1,5 @@ //! Vorago bootloader which can boot from two images. //! -//! Bootloader memory map -//! -//! * <0x0> Bootloader start -//! * <0x3FFE> Bootloader CRC -//! * <0x4000> App image A start -//! * <0x21FFC> App image A CRC check length -//! * <0x21FFE> App image A CRC check value -//! * <0x22000> App image B start -//! * <0x3FFFC> App image B CRC check length -//! * <0x3FFFE> App image B CRC check value -//! * <0x40000> -//! //! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock //! but does not scale that clock up. #![no_main] @@ -19,6 +7,9 @@ use cortex_m_rt::entry; use crc::{Crc, CRC_32_ISO_HDLC}; +#[cfg(not(feature = "rtt-panic"))] +use panic_halt as _; +#[cfg(feature = "rtt-panic")] use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va416xx_hal::{ @@ -42,6 +33,9 @@ const DEBUG_PRINTOUTS: bool = true; // self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash // loader to boot a bootloader without this feature. const FLASH_SELF: bool = false; +// Useful for debugging and see what the bootloader is doing. Enabled currently, because +// the binary stays small enough. +const RTT_PRINTOUT: bool = true; // Important bootloader addresses and offsets, vector table information. @@ -88,8 +82,10 @@ impl WdtInterface for OptWdt { #[entry] fn main() -> ! { - rtt_init_print!(); - rprintln!("-- VA416xx bootloader --"); + if RTT_PRINTOUT { + rtt_init_print!(); + rprintln!("-- VA416xx bootloader --"); + } let mut dp = pac::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap(); // Disable ROM protection. @@ -133,18 +129,24 @@ fn main() -> ! { nvm.write_data(0x0, &first_four_bytes); nvm.write_data(0x4, bootloader_data); if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) { - rprintln!("verification of self-flash to NVM failed: {:?}", e); + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } } if let Err(e) = nvm.verify_data(0x4, bootloader_data) { - rprintln!("verification of self-flash to NVM failed: {:?}", e); + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } } nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()); if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) { - rprintln!( - "error: CRC verification for bootloader self-flash failed: {:?}", - e - ); + if RTT_PRINTOUT { + rprintln!( + "error: CRC verification for bootloader self-flash failed: {:?}", + e + ); + } } } @@ -156,7 +158,7 @@ fn main() -> ! { } else if check_app_crc(AppSel::B, &opt_wdt) { boot_app(AppSel::B, &cp) } else { - if DEBUG_PRINTOUTS { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!("both images corrupt! booting image A"); } // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. @@ -184,7 +186,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) { let crc_calc = digest.finalize(); wdt.feed(); if crc_exp == 0x0000 || crc_exp == 0xffff { - if DEBUG_PRINTOUTS { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!("BL CRC blank - prog new CRC"); } // Blank CRC, write it to NVM. @@ -194,7 +196,7 @@ fn check_own_crc(wdt: &OptWdt, nvm: &Nvm, cp: &cortex_m::Peripherals) { // cortex_m::peripheral::SCB::sys_reset(); } else if crc_exp != crc_calc { // Bootloader is corrupted. Try to run App A. - if DEBUG_PRINTOUTS { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!( "bootloader CRC corrupt, read {} and expected {}. booting image A immediately", crc_calc, @@ -217,7 +219,7 @@ fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) { } } fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool { - if DEBUG_PRINTOUTS { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!("Checking image {:?}", app_sel); } if app_sel == AppSel::A { @@ -237,7 +239,9 @@ fn check_app_given_addr( let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() }; // Sanity check. if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { - rprintln!("detected invalid app size {}", image_size); + if RTT_PRINTOUT { + rprintln!("detected invalid app size {}", image_size); + } return false; } wdt.feed(); @@ -252,7 +256,7 @@ fn check_app_given_addr( } fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! { - if DEBUG_PRINTOUTS { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { rprintln!("booting app {:?}", app_sel); } let clkgen = unsafe { pac::Clkgen::steal() }; diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs index f412cc3..eb72cb9 100644 --- a/examples/rtic/src/main.rs +++ b/examples/rtic/src/main.rs @@ -2,8 +2,13 @@ #![no_main] #![no_std] +use va416xx_hal::time::Hertz; + +const EXTCLK_FREQ: Hertz = Hertz::from_raw(40_000_000); + #[rtic::app(device = pac, dispatchers = [U1, U2, U3])] mod app { + use super::*; use cortex_m::asm; use embedded_hal::digital::StatefulOutputPin; use panic_rtt_target as _; @@ -13,6 +18,7 @@ mod app { use va416xx_hal::{ gpio::{OutputReadablePushPull, Pin, PinsG, PG5}, pac, + prelude::*, }; #[local] @@ -23,14 +29,22 @@ mod app { #[shared] struct Shared {} - rtic_monotonics::systick_monotonic!(Mono, 10_000); + rtic_monotonics::systick_monotonic!(Mono, 1_000); #[init] - fn init(_ctx: init::Context) -> (Shared, Local) { + fn init(mut cx: init::Context) -> (Shared, Local) { rtt_init_default!(); - rprintln!("-- Vorago RTIC template --"); - let mut dp = pac::Peripherals::take().unwrap(); - let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + rprintln!("-- Vorago RTIC example application --"); + // Use the external clock connected to XTAL_N. + let clocks = cx + .device + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(EXTCLK_FREQ) + .freeze(&mut cx.device.sysconfig) + .unwrap(); + Mono::start(cx.core.SYST, clocks.sysclk().raw()); + let portg = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); let led = portg.pg5.into_readable_push_pull_output(); blinky::spawn().ok(); (Shared {}, Local { led }) diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs index e918236..f1b4832 100644 --- a/examples/simple/examples/spi.rs +++ b/examples/simple/examples/spi.rs @@ -3,13 +3,12 @@ //! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board. #![no_main] #![no_std] - use cortex_m_rt::entry; use embedded_hal::spi::{Mode, SpiBus, MODE_0}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use simple_examples::peb1; -use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig}; +use va416xx_hal::spi::{Spi, SpiClkConfig, TransferConfigWithHwcs}; use va416xx_hal::{ gpio::{PinsB, PinsC}, pac, @@ -22,9 +21,8 @@ use va416xx_hal::{ pub enum ExampleSelect { // Enter loopback mode. It is not necessary to tie MOSI/MISO together for this Loopback, - // Send a test buffer and print everything received. You need to tie together MOSI/MISO in this - // mode. - TestBuffer, + // You need to tie together MOSI/MISO in this mode. + MosiMisoTiedTogether, } const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback; @@ -50,21 +48,21 @@ fn main() -> ! { let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb); let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc); - // Configure SPI1 pins. + // Configure SPI0 pins. let (sck, miso, mosi) = ( pins_b.pb15.into_funsel_1(), pins_c.pc0.into_funsel_1(), pins_c.pc1.into_funsel_1(), ); - let mut spi_cfg = SpiConfig::default().clk_div( - clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks) + let mut spi_cfg = SpiConfig::default().clk_cfg( + SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks) .expect("invalid target clock"), ); if EXAMPLE_SEL == ExampleSelect::Loopback { spi_cfg = spi_cfg.loopback(true) } - let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false); + let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false); // Create SPI peripheral. let mut spi0 = Spi::new( &mut dp.sysconfig, @@ -77,24 +75,24 @@ fn main() -> ! { .expect("creating SPI peripheral failed"); spi0.set_fill_word(FILL_WORD); loop { - let mut tx_buf: [u8; 3] = [1, 2, 3]; - let mut rx_buf: [u8; 3] = [0; 3]; - // Can't really verify correct reply here. - spi0.write(&[0x42]).expect("write failed"); - // Need small delay.. otherwise we will read back the sent byte (which we don't want here). - // The write function will return as soon as all bytes were shifted out, ignoring the - // reply bytes. - delay_sysclk.delay_us(50); - // Because of the loopback mode, we should get back the fill word here. - spi0.read(&mut rx_buf[0..1]).unwrap(); - assert_eq!(rx_buf[0], FILL_WORD); + let tx_buf: [u8; 4] = [1, 2, 3, 0]; + let mut rx_buf: [u8; 4] = [0; 4]; + // Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up. + spi0.write(&[0x42, 0x43]).expect("write failed"); - spi0.transfer_in_place(&mut tx_buf) + // Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up. + spi0.read(&mut rx_buf[0..2]).unwrap(); + + // If the pins are tied together, we should received exactly what we send. + + let mut inplace_buf = tx_buf; + spi0.transfer_in_place(&mut inplace_buf) .expect("SPI transfer_in_place failed"); - assert_eq!([1, 2, 3], tx_buf); + assert_eq!([1, 2, 3, 0], inplace_buf); + spi0.transfer(&mut rx_buf, &tx_buf) .expect("SPI transfer failed"); - assert_eq!(rx_buf, tx_buf); + assert_eq!(rx_buf, [1, 2, 3, 0]); delay_sysclk.delay_ms(500); } } diff --git a/va416xx-hal/CHANGELOG.md b/va416xx-hal/CHANGELOG.md index d80e080..fdcdbb0 100644 --- a/va416xx-hal/CHANGELOG.md +++ b/va416xx-hal/CHANGELOG.md @@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Changed + +- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now + only expects a configuration structure and the transfer configuration needs to be applied in a + separate step. + +## Fixed + +- Fixes for SPI peripheral: Flush implementation was incorrect and should now flush properly. +- Fixes for SPI example +- Fixes for RTIC example + # [v0.2.0] 2024-09-18 - Documentation improvements diff --git a/va416xx-hal/src/spi.rs b/va416xx-hal/src/spi.rs index b956bd6..3fcf8b3 100644 --- a/va416xx-hal/src/spi.rs +++ b/va416xx-hal/src/spi.rs @@ -1,11 +1,15 @@ //! API for the SPI peripheral //! +//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure. +//! These provide the [embedded_hal::spi] traits, but also offer a low level interface +//! via the [SpiLowLevel] trait. +//! //! ## Examples //! //! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs) use core::{convert::Infallible, marker::PhantomData, ops::Deref}; -use embedded_hal::spi::Mode; +use embedded_hal::spi::{Mode, MODE_0}; use crate::{ clock::{Clocks, PeripheralSelect, SyscfgExt}, @@ -228,30 +232,23 @@ pub trait TransferConfigProvider { fn sod(&mut self, sod: bool); fn blockmode(&mut self, blockmode: bool); fn mode(&mut self, mode: Mode); - fn clk_div(&mut self, clk_div: u16); + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); fn hw_cs_id(&self) -> u8; } /// This struct contains all configuration parameter which are transfer specific /// and might change for transfers to different SPI slaves -#[derive(Copy, Clone)] -pub struct TransferConfig { - pub clk_div: Option, - pub mode: Option, - /// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to - /// false +#[derive(Copy, Clone, Debug)] +pub struct TransferConfigWithHwcs { pub hw_cs: Option, - pub sod: bool, - /// If this is enabled, all data in the FIFO is transmitted in a single frame unless - /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the - /// duration of multiple data words - pub blockmode: bool, + pub cfg: TransferConfig, } /// Type erased variant of the transfer configuration. This is required to avoid generics in /// the SPI constructor. -pub struct ErasedTransferConfig { - pub clk_div: Option, +#[derive(Copy, Clone, Debug)] +pub struct TransferConfig { + pub clk_cfg: Option, pub mode: Option, pub sod: bool, /// If this is enabled, all data in the FIFO is transmitted in a single frame unless @@ -261,67 +258,67 @@ pub struct ErasedTransferConfig { pub hw_cs: HwChipSelectId, } -impl TransferConfig { +impl TransferConfigWithHwcs { pub fn new_no_hw_cs( - clk_div: Option, + clk_cfg: Option, mode: Option, blockmode: bool, sod: bool, ) -> Self { - TransferConfig { - clk_div, - mode, + TransferConfigWithHwcs { hw_cs: None, - sod, - blockmode, + cfg: TransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwChipSelectId::Invalid, + }, } } } -impl TransferConfig { +impl TransferConfigWithHwcs { pub fn new( - clk_div: Option, + clk_cfg: Option, mode: Option, hw_cs: Option, blockmode: bool, sod: bool, ) -> Self { - TransferConfig { - clk_div, - mode, + TransferConfigWithHwcs { hw_cs, - sod, - blockmode, + cfg: TransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwCs::CS_ID, + }, } } - pub fn downgrade(self) -> ErasedTransferConfig { - ErasedTransferConfig { - clk_div: self.clk_div, - mode: self.mode, - sod: self.sod, - blockmode: self.blockmode, - hw_cs: HwCs::CS_ID, - } + pub fn downgrade(self) -> TransferConfig { + self.cfg } } -impl TransferConfigProvider for TransferConfig { +impl TransferConfigProvider for TransferConfigWithHwcs { /// Slave Output Disable fn sod(&mut self, sod: bool) { - self.sod = sod; + self.cfg.sod = sod; } fn blockmode(&mut self, blockmode: bool) { - self.blockmode = blockmode; + self.cfg.blockmode = blockmode; } fn mode(&mut self, mode: Mode) { - self.mode = Some(mode); + self.cfg.mode = Some(mode); } - fn clk_div(&mut self, clk_div: u16) { - self.clk_div = Some(clk_div); + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) { + self.cfg.clk_cfg = Some(clk_cfg); } fn hw_cs_id(&self) -> u8 { @@ -331,7 +328,16 @@ impl TransferConfigProvider for TransferConfig { /// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details pub struct SpiConfig { - clk_div: u16, + clk: SpiClkConfig, + // SPI mode configuration + pub init_mode: Mode, + /// If this is enabled, all data in the FIFO is transmitted in a single frame unless + /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the + /// duration of multiple data words. Defaults to true. + pub blockmode: bool, + /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty. + /// Currently enabled by default. + pub bmstall: bool, /// By default, configure SPI for master mode (ms == false) ms: bool, /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control @@ -345,7 +351,11 @@ pub struct SpiConfig { impl Default for SpiConfig { fn default() -> Self { Self { - clk_div: DEFAULT_CLK_DIV, + init_mode: MODE_0, + blockmode: true, + bmstall: true, + // Default value is definitely valid. + clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(), ms: Default::default(), slave_output_disable: Default::default(), loopback_mode: Default::default(), @@ -360,8 +370,8 @@ impl SpiConfig { self } - pub fn clk_div(mut self, clk_div: u16) -> Self { - self.clk_div = clk_div; + pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { + self.clk = clk_cfg; self } @@ -455,6 +465,36 @@ impl Instance for pac::Spi3 { // Spi //================================================================================================== +/// Low level access trait for the SPI peripheral. +pub trait SpiLowLevel { + /// Low level function to write a word to the SPI FIFO but also checks whether + /// there is actually data in the FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; + + /// Low level function to write a word to the SPI FIFO without checking whether + /// there FIFO is full. + /// + /// This does not necesarily mean there is a space in the FIFO available. + /// Use [Self::write_fifo] function to write a word into the FIFO reliably. + fn write_fifo_unchecked(&self, data: u32); + + /// Low level function to read a word from the SPI FIFO. Must be preceeded by a + /// [Self::write_fifo] call. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + fn read_fifo(&self) -> nb::Result; + + /// Low level function to read a word from from the SPI FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. + fn read_fifo_unchecked(&self) -> u32; +} + pub struct SpiBase { spi: SpiInstance, cfg: SpiConfig, @@ -462,6 +502,7 @@ pub struct SpiBase { /// Fill word for read-only SPI transactions. pub fill_word: Word, blockmode: bool, + bmstall: bool, word: PhantomData, } @@ -479,7 +520,8 @@ pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { } } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SpiClkConfig { prescale_val: u16, scrdv: u8, @@ -494,6 +536,23 @@ impl SpiClkConfig { } } +impl SpiClkConfig { + pub fn new(prescale_val: u16, scrdv: u8) -> Self { + Self { + prescale_val, + scrdv, + } + } + + pub fn from_div(div: u16) -> Result { + spi_clk_config_from_div(div) + } + + pub fn from_clk(spi_clk: Hertz, clocks: &Clocks) -> Option { + clk_div_for_target_clock(spi_clk, clocks).map(|div| spi_clk_config_from_div(div).unwrap()) + } +} + #[derive(Debug)] pub enum SpiClkConfigError { DivIsZero, @@ -566,27 +625,21 @@ where >::Error: core::fmt::Debug, { #[inline] - pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { - let val = spi_clk_config_from_div(div)?; - self.spi_instance() + pub fn cfg_clock(&mut self, cfg: SpiClkConfig) { + self.spi .ctrl0() - .modify(|_, w| unsafe { w.scrdv().bits(val.scrdv as u8) }); - self.spi_instance() - .clkprescale() - .write(|w| unsafe { w.bits(val.prescale_val as u32) }); - Ok(()) - } - - /* - #[inline] - pub fn cfg_clock(&mut self, spi_clk: impl Into) { - let clk_prescale = - self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1)); + .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) }); self.spi .clkprescale() - .write(|w| unsafe { w.bits(clk_prescale) }); + .write(|w| unsafe { w.bits(cfg.prescale_val as u32) }); + } + + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { + let val = spi_clk_config_from_div(div)?; + self.cfg_clock(val); + Ok(()) } - */ #[inline] pub fn cfg_mode(&mut self, mode: Mode) { @@ -598,7 +651,7 @@ where } #[inline] - pub fn spi_instance(&self) -> &SpiInstance { + pub fn spi(&self) -> &SpiInstance { &self.spi } @@ -646,17 +699,17 @@ where pub fn cfg_transfer>( &mut self, - transfer_cfg: &TransferConfig, - ) -> Result<(), SpiClkConfigError> { - if let Some(trans_clk_div) = transfer_cfg.clk_div { - self.cfg_clock_from_div(trans_clk_div)?; + transfer_cfg: &TransferConfigWithHwcs, + ) { + if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg { + self.cfg_clock(trans_clk_div); } - if let Some(mode) = transfer_cfg.mode { + if let Some(mode) = transfer_cfg.cfg.mode { self.cfg_mode(mode); } - self.blockmode = transfer_cfg.blockmode; + self.blockmode = transfer_cfg.cfg.blockmode; self.spi.ctrl1().modify(|_, w| { - if transfer_cfg.sod { + if transfer_cfg.cfg.sod { w.sod().set_bit(); } else if transfer_cfg.hw_cs.is_some() { w.sod().clear_bit(); @@ -666,72 +719,97 @@ where } else { w.sod().clear_bit(); } - if transfer_cfg.blockmode { + if transfer_cfg.cfg.blockmode { w.blockmode().set_bit(); } else { w.blockmode().clear_bit(); } w }); + } + + /// Low level function to write a word to the SPI FIFO but also checks whether + /// there is actually data in the FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + #[inline(always)] + pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> { + if self.spi.status().read().tnf().bit_is_clear() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); Ok(()) } - /// Sends a word to the slave + /// Low level function to write a word to the SPI FIFO without checking whether + /// there FIFO is full. + /// + /// This does not necesarily mean there is a space in the FIFO available. + /// Use [Self::write_fifo] function to write a word into the FIFO reliably. #[inline(always)] - fn send_blocking(&self, word: Word) { - // TODO: Upper limit for wait cycles to avoid complete hangups? - while self.spi.status().read().tnf().bit_is_clear() {} - self.send(word) + pub fn write_fifo_unchecked(&self, data: u32) { + self.spi.data().write(|w| unsafe { w.bits(data) }); } + /// Low level function to read a word from the SPI FIFO. Must be preceeded by a + /// [Self::write_fifo] call. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. #[inline(always)] - fn send(&self, word: Word) { - self.spi.data().write(|w| unsafe { w.bits(word.into()) }); + pub fn read_fifo(&self) -> nb::Result { + if self.spi.status().read().rne().bit_is_clear() { + return Err(nb::Error::WouldBlock); + } + Ok(self.read_fifo_unchecked()) } - /// Read a word from the slave. Must be preceeded by a [`send`](Self::send) call + /// Low level function to read a word from from the SPI FIFO. + /// + /// This does not necesarily mean there is a word in the FIFO available. + /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb] + /// API. + /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit. #[inline(always)] - fn read_blocking(&self) -> Word { - // TODO: Upper limit for wait cycles to avoid complete hangups? - while self.spi.status().read().rne().bit_is_clear() {} - self.read_single_word() + pub fn read_fifo_unchecked(&self) -> u32 { + self.spi.data().read().bits() } - #[inline(always)] - fn read_single_word(&self) -> Word { - (self.spi.data().read().bits() & Word::MASK) - .try_into() - .unwrap() + fn flush_internal(&self) { + let mut status_reg = self.spi.status().read(); + while status_reg.tfe().bit_is_clear() + || status_reg.rne().bit_is_set() + || status_reg.busy().bit_is_set() + { + if status_reg.rne().bit_is_set() { + self.read_fifo_unchecked(); + } + status_reg = self.spi.status().read(); + } } fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { if words.is_empty() { return Ok(()); } - let mut status_reg = self.spi.status().read(); - // Wait until all bytes have been transferred. - while status_reg.tfe().bit_is_clear() { - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } - status_reg = self.spi.status().read(); - } - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } + self.flush_internal(); Ok(()) } - fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize { + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. Returns the amount of written bytes. + fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize { if self.blockmode { self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) } // Fill the first half of the write FIFO let mut current_write_idx = 0; - for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) { - self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx])); + let smaller_idx = core::cmp::min(FILL_DEPTH, words.len()); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(words[current_write_idx].into()); + } current_write_idx += 1; } if self.blockmode { @@ -739,6 +817,171 @@ where } current_write_idx } + + // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer + // initialization. + fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize { + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + let smaller_idx = core::cmp::min(FILL_DEPTH, send_len); + for _ in 0..smaller_idx { + if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall { + self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK); + } else { + self.write_fifo_unchecked(self.fill_word.into()); + } + current_write_idx += 1; + } + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()) + } + current_write_idx + } +} + +impl SpiLowLevel for SpiBase +where + >::Error: core::fmt::Debug, +{ + #[inline(always)] + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> { + if self.spi.status().read().tnf().bit_is_clear() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); + Ok(()) + } + + #[inline(always)] + fn write_fifo_unchecked(&self, data: u32) { + self.spi.data().write(|w| unsafe { w.bits(data) }); + } + + #[inline(always)] + fn read_fifo(&self) -> nb::Result { + if self.spi.status().read().rne().bit_is_clear() { + return Err(nb::Error::WouldBlock); + } + Ok(self.read_fifo_unchecked()) + } + + #[inline(always)] + fn read_fifo_unchecked(&self) -> u32 { + self.spi.data().read().bits() + } +} + +impl embedded_hal::spi::ErrorType for SpiBase { + type Error = Infallible; +} + +impl embedded_hal::spi::SpiBus for SpiBase +where + >::Error: core::fmt::Debug, +{ + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len()); + loop { + if current_read_idx < words.len() { + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + if current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(self.fill_word.into()))?; + } + current_write_idx += 1; + } + if current_read_idx >= words.len() && current_write_idx >= words.len() { + break; + } + } + Ok(()) + } + + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); + while current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } + current_write_idx += 1; + // Ignore received words. + if self.spi.status().read().rne().bit_is_set() { + self.clear_rx_fifo(); + } + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transfer_preparation(write)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write); + while current_read_idx < read.len() || current_write_idx < write.len() { + if current_write_idx < write.len() { + if current_write_idx == write.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(write[current_write_idx].into()))?; + } + current_write_idx += 1; + } + if current_read_idx < read.len() { + read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.transfer_preparation(words)?; + let mut current_read_idx = 0; + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); + + while current_read_idx < words.len() || current_write_idx < words.len() { + if current_write_idx < words.len() { + if current_write_idx == words.len() - 1 && self.bmstall { + nb::block!( + self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK) + )?; + } else { + nb::block!(self.write_fifo(words[current_write_idx].into()))?; + } + current_write_idx += 1; + } + if current_read_idx < words.len() && current_read_idx < current_write_idx { + words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK) + .try_into() + .unwrap(); + current_read_idx += 1; + } + } + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_internal(); + Ok(()) + } } impl< @@ -772,55 +1015,44 @@ where spi: SpiI, pins: (Sck, Miso, Mosi), spi_cfg: SpiConfig, - transfer_cfg: Option<&ErasedTransferConfig>, - ) -> Result { + ) -> Self { crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); // This is done in the C HAL. syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL); let SpiConfig { - clk_div, + clk, + init_mode, + blockmode, + bmstall, ms, slave_output_disable, loopback_mode, master_delayer_capture, } = spi_cfg; - let mut init_mode = embedded_hal::spi::MODE_0; - let mut ss = 0; - let mut init_blockmode = false; - let apb1_clk = clocks.apb1(); - if let Some(transfer_cfg) = transfer_cfg { - if let Some(mode) = transfer_cfg.mode { - init_mode = mode; - } - //self.cfg_clock_from_div(transfer_cfg.clk_div); - if transfer_cfg.hw_cs != HwChipSelectId::Invalid { - ss = transfer_cfg.hw_cs as u8; - } - init_blockmode = transfer_cfg.blockmode; - } - let spi_clk_cfg = spi_clk_config_from_div(clk_div)?; let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode); spi.ctrl0().write(|w| { unsafe { w.size().bits(Word::word_reg()); - w.scrdv().bits(spi_clk_cfg.scrdv); + w.scrdv().bits(clk.scrdv); // Clear clock phase and polarity. Will be set to correct value for each // transfer w.spo().bit(cpo_bit); w.sph().bit(cph_bit) } }); + spi.ctrl1().write(|w| { w.lbm().bit(loopback_mode); w.sod().bit(slave_output_disable); w.ms().bit(ms); w.mdlycap().bit(master_delayer_capture); - w.blockmode().bit(init_blockmode); - unsafe { w.ss().bits(ss) } + w.blockmode().bit(blockmode); + w.bmstall().bit(bmstall); + unsafe { w.ss().bits(0) } }); spi.clkprescale() - .write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val as u32) }); + .write(|w| unsafe { w.bits(clk.prescale_val as u32) }); spi.fifo_clr().write(|w| { w.rxfifo().set_bit(); @@ -829,26 +1061,30 @@ where // Enable the peripheral as the last step as recommended in the // programmers guide spi.ctrl1().modify(|_, w| w.enable().set_bit()); - Ok(Spi { + Spi { inner: SpiBase { spi, cfg: spi_cfg, - apb1_clk, + apb1_clk: clocks.apb1(), fill_word: Default::default(), - blockmode: init_blockmode, + bmstall, + blockmode, word: PhantomData, }, pins, - }) + } } delegate::delegate! { to self.inner { + #[inline] + pub fn cfg_clock(&mut self, cfg: SpiClkConfig); + #[inline] pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; #[inline] - pub fn spi_instance(&self) -> &SpiI; + pub fn spi(&self) -> &SpiI; #[inline] pub fn cfg_mode(&mut self, mode: Mode); @@ -857,8 +1093,8 @@ where pub fn perid(&self) -> u32; pub fn cfg_transfer>( - &mut self, transfer_cfg: &TransferConfig - ) -> Result<(), SpiClkConfigError>; + &mut self, transfer_cfg: &TransferConfigWithHwcs + ); } } @@ -882,140 +1118,23 @@ where } } -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi -{ - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - fill_word: Default::default(), - apb1_clk: old_spi.inner.apb1_clk, - word: PhantomData, - }, - pins: old_spi.pins, - } - } -} - -/// Changing the word size also requires a type conversion -impl, Miso: PinMiso, Mosi: PinMosi> - From> for Spi -{ - fn from(old_spi: Spi) -> Self { - old_spi - .inner - .spi - .ctrl0() - .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); - Spi { - inner: SpiBase { - spi: old_spi.inner.spi, - cfg: old_spi.inner.cfg, - blockmode: old_spi.inner.blockmode, - apb1_clk: old_spi.inner.apb1_clk, - fill_word: Default::default(), - word: PhantomData, - }, - pins: old_spi.pins, - } - } -} - -impl embedded_hal::spi::ErrorType for SpiBase { - type Error = Infallible; -} - -impl embedded_hal::spi::SpiBus for SpiBase +impl< + SpiI: Instance, + Sck: PinSck, + Miso: PinMiso, + Mosi: PinMosi, + Word: WordProvider, + > SpiLowLevel for Spi where >::Error: core::fmt::Debug, { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(None); - loop { - if current_write_idx < words.len() { - self.send_blocking(self.fill_word); - current_write_idx += 1; - } - if current_read_idx < words.len() { - words[current_read_idx] = self.read_blocking(); - current_read_idx += 1; - } - if current_read_idx >= words.len() && current_write_idx >= words.len() { - break; - } + delegate::delegate! { + to self.inner { + fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>; + fn write_fifo_unchecked(&self, data: u32); + fn read_fifo(&self) -> nb::Result; + fn read_fifo_unchecked(&self) -> u32; } - Ok(()) - } - - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); - while current_write_idx < words.len() { - self.send_blocking(words[current_write_idx]); - current_write_idx += 1; - // Ignore received words. - if self.spi.status().read().rne().bit_is_set() { - self.clear_rx_fifo(); - } - } - Ok(()) - } - - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { - self.transfer_preparation(write)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(write)); - while current_read_idx < read.len() || current_write_idx < write.len() { - if current_write_idx < write.len() { - self.send_blocking(write[current_write_idx]); - current_write_idx += 1; - } - if current_read_idx < read.len() { - read[current_read_idx] = self.read_blocking(); - current_read_idx += 1; - } - } - - Ok(()) - } - - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.transfer_preparation(words)?; - let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); - - while current_read_idx < words.len() || current_write_idx < words.len() { - if current_write_idx < words.len() { - self.send_blocking(words[current_write_idx]); - current_write_idx += 1; - } - if current_read_idx < words.len() && current_read_idx < current_write_idx { - words[current_read_idx] = self.read_blocking(); - current_read_idx += 1; - } - } - Ok(()) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - let status_reg = self.spi.status().read(); - while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() { - if status_reg.rne().bit_is_set() { - self.read_single_word(); - } - } - Ok(()) } } @@ -1040,23 +1159,63 @@ impl< where >::Error: core::fmt::Debug, { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.read(words) - } - - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - self.inner.write(words) - } - - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { - self.inner.transfer(read, write) - } - - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.transfer_in_place(words) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.inner.flush() + delegate::delegate! { + to self.inner { + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn flush(&mut self) -> Result<(), Self::Error>; + } + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + bmstall: old_spi.inner.bmstall, + fill_word: Default::default(), + apb1_clk: old_spi.inner.apb1_clk, + word: PhantomData, + }, + pins: old_spi.pins, + } + } +} + +/// Changing the word size also requires a type conversion +impl, Miso: PinMiso, Mosi: PinMosi> + From> for Spi +{ + fn from(old_spi: Spi) -> Self { + old_spi + .inner + .spi + .ctrl0() + .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) }); + Spi { + inner: SpiBase { + spi: old_spi.inner.spi, + cfg: old_spi.inner.cfg, + blockmode: old_spi.inner.blockmode, + bmstall: old_spi.inner.bmstall, + apb1_clk: old_spi.inner.apb1_clk, + fill_word: Default::default(), + word: PhantomData, + }, + pins: old_spi.pins, + } } }