Merge pull request 'Updates and fixes: SPI' (#29) from updates-and-fixes into main
All checks were successful
Rust/va416xx-rs/pipeline/head This commit looks good

Reviewed-on: #29
This commit is contained in:
Robin Müller 2024-09-20 11:43:44 +02:00
commit 051042ad1b
8 changed files with 562 additions and 352 deletions

View File

@ -41,4 +41,4 @@ debug-assertions = false # <-
lto = true lto = true
opt-level = 'z' # <- opt-level = 'z' # <-
overflow-checks = false # <- overflow-checks = false # <-
# strip = true # Automatically strip symbols from the binary. strip = true # Automatically strip symbols from the binary.

View File

@ -99,9 +99,9 @@ example.
### Using VS Code ### Using VS Code
Assuming a working debug connection to your VA108xx board, you can debug using VS Code with 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 the [`Cortex-Debug` plugin](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug).
[`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146) Please make sure that [`objdump-multiarch` and `nm-multiarch`](https://forums.raspberrypi.com/viewtopic.php?t=333146)
are installed as well. are installed as well.
Some sample configuration files for VS code were provided and can be used by running Some sample configuration files for VS code were provided and can be used by running

View File

@ -8,8 +8,13 @@ cortex-m = "0.7"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
crc = "3" crc = "3"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal" path = "../va416xx-hal"
[features]
default = []
rtt-panic = []

View File

@ -1,17 +1,5 @@
//! Vorago bootloader which can boot from two images. //! Vorago bootloader which can boot from two images.
//! //!
//! Bootloader memory map
//!
//! * <0x0> Bootloader start <code up to 0x3FFE bytes>
//! * <0x3FFE> Bootloader CRC <halfword>
//! * <0x4000> App image A start <code up to 0x1DFFC (~120K) bytes>
//! * <0x21FFC> App image A CRC check length <halfword>
//! * <0x21FFE> App image A CRC check value <halfword>
//! * <0x22000> App image B start <code up to 0x1DFFC (~120K) bytes>
//! * <0x3FFFC> App image B CRC check length <halfword>
//! * <0x3FFFE> App image B CRC check value <halfword>
//! * <0x40000> <end>
//!
//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock //! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock
//! but does not scale that clock up. //! but does not scale that clock up.
#![no_main] #![no_main]
@ -19,6 +7,9 @@
use cortex_m_rt::entry; use cortex_m_rt::entry;
use crc::{Crc, CRC_32_ISO_HDLC}; 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 panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{ 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 // 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. // loader to boot a bootloader without this feature.
const FLASH_SELF: bool = false; 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. // Important bootloader addresses and offsets, vector table information.
@ -88,8 +82,10 @@ impl WdtInterface for OptWdt {
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
if RTT_PRINTOUT {
rtt_init_print!(); rtt_init_print!();
rprintln!("-- VA416xx bootloader --"); rprintln!("-- VA416xx bootloader --");
}
let mut dp = pac::Peripherals::take().unwrap(); let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap();
// Disable ROM protection. // Disable ROM protection.
@ -133,20 +129,26 @@ fn main() -> ! {
nvm.write_data(0x0, &first_four_bytes); nvm.write_data(0x0, &first_four_bytes);
nvm.write_data(0x4, bootloader_data); nvm.write_data(0x4, bootloader_data);
if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) { if let Err(e) = nvm.verify_data(0x0, &first_four_bytes) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e); rprintln!("verification of self-flash to NVM failed: {:?}", e);
} }
}
if let Err(e) = nvm.verify_data(0x4, bootloader_data) { if let Err(e) = nvm.verify_data(0x4, bootloader_data) {
if RTT_PRINTOUT {
rprintln!("verification of self-flash to NVM failed: {:?}", e); rprintln!("verification of self-flash to NVM failed: {:?}", e);
} }
}
nvm.write_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()); 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()) { if let Err(e) = nvm.verify_data(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) {
if RTT_PRINTOUT {
rprintln!( rprintln!(
"error: CRC verification for bootloader self-flash failed: {:?}", "error: CRC verification for bootloader self-flash failed: {:?}",
e e
); );
} }
} }
}
// Check bootloader's CRC (and write it if blank) // Check bootloader's CRC (and write it if blank)
check_own_crc(&opt_wdt, &nvm, &cp); check_own_crc(&opt_wdt, &nvm, &cp);
@ -156,7 +158,7 @@ fn main() -> ! {
} else if check_app_crc(AppSel::B, &opt_wdt) { } else if check_app_crc(AppSel::B, &opt_wdt) {
boot_app(AppSel::B, &cp) boot_app(AppSel::B, &cp)
} else { } else {
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("both images corrupt! booting image A"); rprintln!("both images corrupt! booting image A");
} }
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. // 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(); let crc_calc = digest.finalize();
wdt.feed(); wdt.feed();
if crc_exp == 0x0000 || crc_exp == 0xffff { if crc_exp == 0x0000 || crc_exp == 0xffff {
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("BL CRC blank - prog new CRC"); rprintln!("BL CRC blank - prog new CRC");
} }
// Blank CRC, write it to NVM. // 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(); // cortex_m::peripheral::SCB::sys_reset();
} else if crc_exp != crc_calc { } else if crc_exp != crc_calc {
// Bootloader is corrupted. Try to run App A. // Bootloader is corrupted. Try to run App A.
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!( rprintln!(
"bootloader CRC corrupt, read {} and expected {}. booting image A immediately", "bootloader CRC corrupt, read {} and expected {}. booting image A immediately",
crc_calc, 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 { fn check_app_crc(app_sel: AppSel, wdt: &OptWdt) -> bool {
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("Checking image {:?}", app_sel); rprintln!("Checking image {:?}", app_sel);
} }
if app_sel == AppSel::A { 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() }; let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() };
// Sanity check. // Sanity check.
if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 {
if RTT_PRINTOUT {
rprintln!("detected invalid app size {}", image_size); rprintln!("detected invalid app size {}", image_size);
}
return false; return false;
} }
wdt.feed(); wdt.feed();
@ -252,7 +256,7 @@ fn check_app_given_addr(
} }
fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! { fn boot_app(app_sel: AppSel, cp: &cortex_m::Peripherals) -> ! {
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS && RTT_PRINTOUT {
rprintln!("booting app {:?}", app_sel); rprintln!("booting app {:?}", app_sel);
} }
let clkgen = unsafe { pac::Clkgen::steal() }; let clkgen = unsafe { pac::Clkgen::steal() };

View File

@ -2,8 +2,13 @@
#![no_main] #![no_main]
#![no_std] #![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])] #[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app { mod app {
use super::*;
use cortex_m::asm; use cortex_m::asm;
use embedded_hal::digital::StatefulOutputPin; use embedded_hal::digital::StatefulOutputPin;
use panic_rtt_target as _; use panic_rtt_target as _;
@ -13,6 +18,7 @@ mod app {
use va416xx_hal::{ use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5}, gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac, pac,
prelude::*,
}; };
#[local] #[local]
@ -23,14 +29,22 @@ mod app {
#[shared] #[shared]
struct Shared {} struct Shared {}
rtic_monotonics::systick_monotonic!(Mono, 10_000); rtic_monotonics::systick_monotonic!(Mono, 1_000);
#[init] #[init]
fn init(_ctx: init::Context) -> (Shared, Local) { fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_default!(); rtt_init_default!();
rprintln!("-- Vorago RTIC template --"); rprintln!("-- Vorago RTIC example application --");
let mut dp = pac::Peripherals::take().unwrap(); // Use the external clock connected to XTAL_N.
let portg = PinsG::new(&mut dp.sysconfig, dp.portg); 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(); let led = portg.pg5.into_readable_push_pull_output();
blinky::spawn().ok(); blinky::spawn().ok();
(Shared {}, Local { led }) (Shared {}, Local { led })

View File

@ -3,13 +3,12 @@
//! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board. //! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
#![no_main] #![no_main]
#![no_std] #![no_std]
use cortex_m_rt::entry; use cortex_m_rt::entry;
use embedded_hal::spi::{Mode, SpiBus, MODE_0}; use embedded_hal::spi::{Mode, SpiBus, MODE_0};
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::spi::{clk_div_for_target_clock, Spi, TransferConfig}; use va416xx_hal::spi::{Spi, SpiClkConfig};
use va416xx_hal::{ use va416xx_hal::{
gpio::{PinsB, PinsC}, gpio::{PinsB, PinsC},
pac, pac,
@ -22,9 +21,8 @@ use va416xx_hal::{
pub enum ExampleSelect { pub enum ExampleSelect {
// Enter loopback mode. It is not necessary to tie MOSI/MISO together for this // Enter loopback mode. It is not necessary to tie MOSI/MISO together for this
Loopback, Loopback,
// Send a test buffer and print everything received. You need to tie together MOSI/MISO in this // You need to tie together MOSI/MISO in this mode.
// mode. MosiMisoTiedTogether,
TestBuffer,
} }
const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback; const EXAMPLE_SEL: ExampleSelect = ExampleSelect::Loopback;
@ -50,21 +48,23 @@ fn main() -> ! {
let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb); let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc); let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
// Configure SPI1 pins. // Configure SPI0 pins.
let (sck, miso, mosi) = ( let (sck, miso, mosi) = (
pins_b.pb15.into_funsel_1(), pins_b.pb15.into_funsel_1(),
pins_c.pc0.into_funsel_1(), pins_c.pc0.into_funsel_1(),
pins_c.pc1.into_funsel_1(), pins_c.pc1.into_funsel_1(),
); );
let mut spi_cfg = SpiConfig::default().clk_div( let mut spi_cfg = SpiConfig::default()
clk_div_for_target_clock(Hertz::from_raw(SPI_SPEED_KHZ), &clocks) .clk_cfg(
SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
.expect("invalid target clock"), .expect("invalid target clock"),
); )
.mode(SPI_MODE)
.blockmode(BLOCKMODE);
if EXAMPLE_SEL == ExampleSelect::Loopback { if EXAMPLE_SEL == ExampleSelect::Loopback {
spi_cfg = spi_cfg.loopback(true) spi_cfg = spi_cfg.loopback(true)
} }
let transfer_cfg = TransferConfig::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
// Create SPI peripheral. // Create SPI peripheral.
let mut spi0 = Spi::new( let mut spi0 = Spi::new(
&mut dp.sysconfig, &mut dp.sysconfig,
@ -72,29 +72,27 @@ fn main() -> ! {
dp.spi0, dp.spi0,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
Some(&transfer_cfg.downgrade()), );
)
.expect("creating SPI peripheral failed");
spi0.set_fill_word(FILL_WORD); spi0.set_fill_word(FILL_WORD);
loop { loop {
let mut tx_buf: [u8; 3] = [1, 2, 3]; let tx_buf: [u8; 4] = [1, 2, 3, 0];
let mut rx_buf: [u8; 3] = [0; 3]; let mut rx_buf: [u8; 4] = [0; 4];
// Can't really verify correct reply here. // Can't really verify correct behaviour here. Just verify nothing crazy happens or it hangs up.
spi0.write(&[0x42]).expect("write failed"); spi0.write(&[0x42, 0x43]).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);
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"); .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) spi0.transfer(&mut rx_buf, &tx_buf)
.expect("SPI transfer failed"); .expect("SPI transfer failed");
assert_eq!(rx_buf, tx_buf); assert_eq!(rx_buf, [1, 2, 3, 0]);
delay_sysclk.delay_ms(500); delay_sysclk.delay_ms(500);
} }
} }

View File

@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [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 # [v0.2.0] 2024-09-18
- Documentation improvements - Documentation improvements

View File

@ -1,11 +1,15 @@
//! API for the SPI peripheral //! 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 //! ## Examples
//! //!
//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs) //! - [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 core::{convert::Infallible, marker::PhantomData, ops::Deref};
use embedded_hal::spi::Mode; use embedded_hal::spi::{Mode, MODE_0};
use crate::{ use crate::{
clock::{Clocks, PeripheralSelect, SyscfgExt}, clock::{Clocks, PeripheralSelect, SyscfgExt},
@ -228,100 +232,100 @@ pub trait TransferConfigProvider {
fn sod(&mut self, sod: bool); fn sod(&mut self, sod: bool);
fn blockmode(&mut self, blockmode: bool); fn blockmode(&mut self, blockmode: bool);
fn mode(&mut self, mode: Mode); 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; fn hw_cs_id(&self) -> u8;
} }
/// This struct contains all configuration parameter which are transfer specific /// This struct contains all configuration parameter which are transfer specific
/// and might change for transfers to different SPI slaves /// and might change for transfers to different SPI slaves
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
pub struct TransferConfig<HwCs> { pub struct TransferConfigWithHwcs<HwCs> {
pub clk_div: Option<u16>,
pub mode: Option<Mode>,
/// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to
/// false
pub hw_cs: Option<HwCs>, pub hw_cs: Option<HwCs>,
pub sod: bool, pub cfg: TransferConfig,
/// 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,
} }
/// Type erased variant of the transfer configuration. This is required to avoid generics in /// Type erased variant of the transfer configuration. This is required to avoid generics in
/// the SPI constructor. /// the SPI constructor.
pub struct ErasedTransferConfig { #[derive(Copy, Clone, Debug)]
pub clk_div: Option<u16>, pub struct TransferConfig {
pub clk_cfg: Option<SpiClkConfig>,
pub mode: Option<Mode>, pub mode: Option<Mode>,
pub sod: bool, pub sod: bool,
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless /// 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 /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words /// duration of multiple data words
pub blockmode: bool, pub blockmode: bool,
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
/// is set on a written word.
pub bmstall: bool,
pub hw_cs: HwChipSelectId, pub hw_cs: HwChipSelectId,
} }
impl TransferConfig<NoneT> { impl TransferConfigWithHwcs<NoneT> {
pub fn new_no_hw_cs( pub fn new_no_hw_cs(
clk_div: Option<u16>, clk_cfg: Option<SpiClkConfig>,
mode: Option<Mode>, mode: Option<Mode>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfig { TransferConfigWithHwcs {
clk_div,
mode,
hw_cs: None, hw_cs: None,
cfg: TransferConfig {
clk_cfg,
mode,
sod, sod,
blockmode, blockmode,
bmstall,
hw_cs: HwChipSelectId::Invalid,
},
} }
} }
} }
impl<HwCs: HwCsProvider> TransferConfig<HwCs> { impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
pub fn new( pub fn new(
clk_div: Option<u16>, clk_cfg: Option<SpiClkConfig>,
mode: Option<Mode>, mode: Option<Mode>,
hw_cs: Option<HwCs>, hw_cs: Option<HwCs>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfig { TransferConfigWithHwcs {
clk_div,
mode,
hw_cs, hw_cs,
cfg: TransferConfig {
clk_cfg,
mode,
sod, sod,
blockmode, blockmode,
} bmstall,
}
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, hw_cs: HwCs::CS_ID,
} },
} }
} }
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> { pub fn downgrade(self) -> TransferConfig {
self.cfg
}
}
impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> {
/// Slave Output Disable /// Slave Output Disable
fn sod(&mut self, sod: bool) { fn sod(&mut self, sod: bool) {
self.sod = sod; self.cfg.sod = sod;
} }
fn blockmode(&mut self, blockmode: bool) { fn blockmode(&mut self, blockmode: bool) {
self.blockmode = blockmode; self.cfg.blockmode = blockmode;
} }
fn mode(&mut self, mode: Mode) { fn mode(&mut self, mode: Mode) {
self.mode = Some(mode); self.cfg.mode = Some(mode);
} }
fn clk_div(&mut self, clk_div: u16) { fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) {
self.clk_div = Some(clk_div); self.cfg.clk_cfg = Some(clk_cfg);
} }
fn hw_cs_id(&self) -> u8 { fn hw_cs_id(&self) -> u8 {
@ -331,7 +335,16 @@ impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfig<HwCs> {
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details /// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
pub struct SpiConfig { 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) /// By default, configure SPI for master mode (ms == false)
ms: bool, ms: bool,
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
@ -345,7 +358,11 @@ pub struct SpiConfig {
impl Default for SpiConfig { impl Default for SpiConfig {
fn default() -> Self { fn default() -> Self {
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(), ms: Default::default(),
slave_output_disable: Default::default(), slave_output_disable: Default::default(),
loopback_mode: Default::default(), loopback_mode: Default::default(),
@ -360,8 +377,23 @@ impl SpiConfig {
self self
} }
pub fn clk_div(mut self, clk_div: u16) -> Self { pub fn blockmode(mut self, enable: bool) -> Self {
self.clk_div = clk_div; self.blockmode = enable;
self
}
pub fn bmstall(mut self, enable: bool) -> Self {
self.bmstall = enable;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.init_mode = mode;
self
}
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
self.clk = clk_cfg;
self self
} }
@ -455,6 +487,36 @@ impl Instance for pac::Spi3 {
// Spi // 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<u32, Infallible>;
/// 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<SpiInstance, Word = u8> { pub struct SpiBase<SpiInstance, Word = u8> {
spi: SpiInstance, spi: SpiInstance,
cfg: SpiConfig, cfg: SpiConfig,
@ -462,6 +524,7 @@ pub struct SpiBase<SpiInstance, Word = u8> {
/// Fill word for read-only SPI transactions. /// Fill word for read-only SPI transactions.
pub fill_word: Word, pub fill_word: Word,
blockmode: bool, blockmode: bool,
bmstall: bool,
word: PhantomData<Word>, word: PhantomData<Word>,
} }
@ -479,7 +542,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 { pub struct SpiClkConfig {
prescale_val: u16, prescale_val: u16,
scrdv: u8, scrdv: u8,
@ -494,6 +558,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<Self, SpiClkConfigError> {
spi_clk_config_from_div(div)
}
pub fn from_clk(spi_clk: Hertz, clocks: &Clocks) -> Option<Self> {
clk_div_for_target_clock(spi_clk, clocks).map(|div| spi_clk_config_from_div(div).unwrap())
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum SpiClkConfigError { pub enum SpiClkConfigError {
DivIsZero, DivIsZero,
@ -566,27 +647,21 @@ where
<Word as TryFrom<u32>>::Error: core::fmt::Debug, <Word as TryFrom<u32>>::Error: core::fmt::Debug,
{ {
#[inline] #[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
let val = spi_clk_config_from_div(div)?; self.spi
self.spi_instance()
.ctrl0() .ctrl0()
.modify(|_, w| unsafe { w.scrdv().bits(val.scrdv as u8) }); .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
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<Hertz>) {
let clk_prescale =
self.apb1_clk.raw() / (spi_clk.into().raw() * (self.cfg.ser_clock_rate_div as u32 + 1));
self.spi self.spi
.clkprescale() .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] #[inline]
pub fn cfg_mode(&mut self, mode: Mode) { pub fn cfg_mode(&mut self, mode: Mode) {
@ -598,7 +673,7 @@ where
} }
#[inline] #[inline]
pub fn spi_instance(&self) -> &SpiInstance { pub fn spi(&self) -> &SpiInstance {
&self.spi &self.spi
} }
@ -646,17 +721,17 @@ where
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>( pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
&mut self, &mut self,
transfer_cfg: &TransferConfig<HwCs>, transfer_cfg: &TransferConfigWithHwcs<HwCs>,
) -> Result<(), SpiClkConfigError> { ) {
if let Some(trans_clk_div) = transfer_cfg.clk_div { if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg {
self.cfg_clock_from_div(trans_clk_div)?; 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.cfg_mode(mode);
} }
self.blockmode = transfer_cfg.blockmode; self.blockmode = transfer_cfg.cfg.blockmode;
self.spi.ctrl1().modify(|_, w| { self.spi.ctrl1().modify(|_, w| {
if transfer_cfg.sod { if transfer_cfg.cfg.sod {
w.sod().set_bit(); w.sod().set_bit();
} else if transfer_cfg.hw_cs.is_some() { } else if transfer_cfg.hw_cs.is_some() {
w.sod().clear_bit(); w.sod().clear_bit();
@ -666,72 +741,93 @@ where
} else { } else {
w.sod().clear_bit(); w.sod().clear_bit();
} }
if transfer_cfg.blockmode { w.blockmode().bit(transfer_cfg.cfg.blockmode);
w.blockmode().set_bit(); w.bmstall().bit(transfer_cfg.cfg.bmstall)
} 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(()) 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)] #[inline(always)]
fn send_blocking(&self, word: Word) { pub fn write_fifo_unchecked(&self, data: u32) {
// TODO: Upper limit for wait cycles to avoid complete hangups? self.spi.data().write(|w| unsafe { w.bits(data) });
while self.spi.status().read().tnf().bit_is_clear() {}
self.send(word)
} }
/// 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)] #[inline(always)]
fn send(&self, word: Word) { pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
self.spi.data().write(|w| unsafe { w.bits(word.into()) }); 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)] #[inline(always)]
fn read_blocking(&self) -> Word { pub fn read_fifo_unchecked(&self) -> u32 {
// TODO: Upper limit for wait cycles to avoid complete hangups? self.spi.data().read().bits()
while self.spi.status().read().rne().bit_is_clear() {}
self.read_single_word()
} }
#[inline(always)] fn flush_internal(&self) {
fn read_single_word(&self) -> Word { let mut status_reg = self.spi.status().read();
(self.spi.data().read().bits() & Word::MASK) while status_reg.tfe().bit_is_clear()
.try_into() || status_reg.rne().bit_is_set()
.unwrap() || 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> { fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
if words.is_empty() { if words.is_empty() {
return Ok(()); return Ok(());
} }
let mut status_reg = self.spi.status().read(); self.flush_internal();
// 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();
}
Ok(()) 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 { if self.blockmode {
self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit())
} }
// Fill the first half of the write FIFO // Fill the first half of the write FIFO
let mut current_write_idx = 0; let mut current_write_idx = 0;
for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) { let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx])); 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; current_write_idx += 1;
} }
if self.blockmode { if self.blockmode {
@ -739,6 +835,171 @@ where
} }
current_write_idx 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<SpiInstance: Instance, Word: WordProvider> SpiLowLevel for SpiBase<SpiInstance, Word>
where
<Word as TryFrom<u32>>::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<u32, Infallible> {
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<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
type Error = Infallible;
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
where
<Word as TryFrom<u32>>::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< impl<
@ -772,55 +1033,44 @@ where
spi: SpiI, spi: SpiI,
pins: (Sck, Miso, Mosi), pins: (Sck, Miso, Mosi),
spi_cfg: SpiConfig, spi_cfg: SpiConfig,
transfer_cfg: Option<&ErasedTransferConfig>, ) -> Self {
) -> Result<Self, SpiClkConfigError> {
crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
// This is done in the C HAL. // This is done in the C HAL.
syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL); syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
let SpiConfig { let SpiConfig {
clk_div, clk,
init_mode,
blockmode,
bmstall,
ms, ms,
slave_output_disable, slave_output_disable,
loopback_mode, loopback_mode,
master_delayer_capture, master_delayer_capture,
} = spi_cfg; } = 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); let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
spi.ctrl0().write(|w| { spi.ctrl0().write(|w| {
unsafe { unsafe {
w.size().bits(Word::word_reg()); 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 // Clear clock phase and polarity. Will be set to correct value for each
// transfer // transfer
w.spo().bit(cpo_bit); w.spo().bit(cpo_bit);
w.sph().bit(cph_bit) w.sph().bit(cph_bit)
} }
}); });
spi.ctrl1().write(|w| { spi.ctrl1().write(|w| {
w.lbm().bit(loopback_mode); w.lbm().bit(loopback_mode);
w.sod().bit(slave_output_disable); w.sod().bit(slave_output_disable);
w.ms().bit(ms); w.ms().bit(ms);
w.mdlycap().bit(master_delayer_capture); w.mdlycap().bit(master_delayer_capture);
w.blockmode().bit(init_blockmode); w.blockmode().bit(blockmode);
unsafe { w.ss().bits(ss) } w.bmstall().bit(bmstall);
unsafe { w.ss().bits(0) }
}); });
spi.clkprescale() 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| { spi.fifo_clr().write(|w| {
w.rxfifo().set_bit(); w.rxfifo().set_bit();
@ -829,26 +1079,30 @@ where
// Enable the peripheral as the last step as recommended in the // Enable the peripheral as the last step as recommended in the
// programmers guide // programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit()); spi.ctrl1().modify(|_, w| w.enable().set_bit());
Ok(Spi { Spi {
inner: SpiBase { inner: SpiBase {
spi, spi,
cfg: spi_cfg, cfg: spi_cfg,
apb1_clk, apb1_clk: clocks.apb1(),
fill_word: Default::default(), fill_word: Default::default(),
blockmode: init_blockmode, bmstall,
blockmode,
word: PhantomData, word: PhantomData,
}, },
pins, pins,
}) }
} }
delegate::delegate! { delegate::delegate! {
to self.inner { to self.inner {
#[inline]
pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
#[inline] #[inline]
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
#[inline] #[inline]
pub fn spi_instance(&self) -> &SpiI; pub fn spi(&self) -> &SpiI;
#[inline] #[inline]
pub fn cfg_mode(&mut self, mode: Mode); pub fn cfg_mode(&mut self, mode: Mode);
@ -857,8 +1111,8 @@ where
pub fn perid(&self) -> u32; pub fn perid(&self) -> u32;
pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>( pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
&mut self, transfer_cfg: &TransferConfig<HwCs> &mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs>
) -> Result<(), SpiClkConfigError>; );
} }
} }
@ -882,140 +1136,23 @@ where
} }
} }
/// Changing the word size also requires a type conversion impl<
impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>> SpiI: Instance,
From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16> Sck: PinSck<SpiI>,
{ Miso: PinMiso<SpiI>,
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self { Mosi: PinMosi<SpiI>,
old_spi Word: WordProvider,
.inner > SpiLowLevel for Spi<SpiI, (Sck, Miso, Mosi), Word>
.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<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> 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<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
type Error = Infallible;
}
impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
where where
<Word as TryFrom<u32>>::Error: core::fmt::Debug, <Word as TryFrom<u32>>::Error: core::fmt::Debug,
{ {
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { delegate::delegate! {
self.transfer_preparation(words)?; to self.inner {
let mut current_read_idx = 0; fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
let mut current_write_idx = self.initial_send_fifo_pumping(None); fn write_fifo_unchecked(&self, data: u32);
loop { fn read_fifo(&self) -> nb::Result<u32, Infallible>;
if current_write_idx < words.len() { fn read_fifo_unchecked(&self) -> u32;
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;
}
}
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 +1177,63 @@ impl<
where where
<Word as TryFrom<u32>>::Error: core::fmt::Debug, <Word as TryFrom<u32>>::Error: core::fmt::Debug,
{ {
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { delegate::delegate! {
self.inner.read(words) 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>;
}
}
} }
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { /// Changing the word size also requires a type conversion
self.inner.write(words) impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
{
fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> 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,
}
}
} }
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { /// Changing the word size also requires a type conversion
self.inner.transfer(read, write) impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
} From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
{
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
self.inner.transfer_in_place(words) old_spi
} .inner
.spi
fn flush(&mut self) -> Result<(), Self::Error> { .ctrl0()
self.inner.flush() .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,
}
} }
} }