// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! The serial peripheral interface of RM0090 rev 22 chapter 28, written against the `spi1` layout.
//!
//! One layout serves eight instances: SPI1 to SPI6 and the two I2S extension blocks.
//!
//! **This model is the SPI and not the I2S.** `I2SCFGR`'s `I2SMOD`, section 28.5.8 bit 11, chooses which
//! of two peripherals the register block is, and the manual gives them separate feature lists and separate
//! functional sections. With it set this model stores every write and does nothing else, so a firmware that
//! configures an I2S gets a peripheral that does nothing rather than one that pretends. RL-116 is the I2S.
//!
//! | Register or section | Behaviour | Source | Test |
//! |---|---|---|---|
//! | Reset state | `TXE` set, everything else clear, the CRC polynomial at 7, and the prescaler at 2 | 28.5.3, 28.5.5, 28.5.9 | `reset_leaves_the_transmit_buffer_empty` |
//! | `DR` | A write stores into the transmit buffer, a read answers the receive buffer and leaves the word there | 28.3.5, 28.3.10 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back`, `a_second_read_of_the_data_register_answers_the_same_word` |
//! | `SR` `TXE` | Set while the transmit buffer is free, cleared by writing `DR`, and held down while a second word waits behind the one on the wire | 28.3.5, 28.3.7 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | `SR` `RXNE` | Set when the shift register hands its word to the receive buffer, cleared by reading `DR` | 28.3.7 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | `SR` `BSY` | Set when a transfer starts and cleared when it finishes, when the SPI is disabled, or on a mode fault; and kept low throughout a master bidirectional reception, which is the exception the section names | 28.3.7 | `the_busy_flag_stands_for_exactly_the_transfer`, `a_master_receiving_bidirectionally_never_shows_busy` |
//! | Transfer time | A word takes its bits at the baud rate, `PCLK` over the `BR` encoding, eight or sixteen bits by `DFF`, and a run of them is timed from where the run began so the rounding cannot accumulate | 28.5.1 | `a_transfer_takes_the_time_its_bits_take`, `the_frame_is_eight_bits_or_sixteen_by_dff`, `a_run_of_words_does_not_drift` |
//! | Master full duplex | The sequence begins on the write to `DR`; the word goes out and the word on the wire comes in | 28.3.3, 28.3.5 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | Receive only | With `RXONLY` set the sequence begins as soon as `SPE` is set, and nothing is transmitted | 28.3.5 | `receive_only_starts_itself_and_sends_nothing` |
//! | Bidirectional | `BIDIMODE` with `BIDIOE` transmits and receives nothing; without it receives and transmits nothing | 28.3.4, 28.3.5 | `bidirectional_carries_one_direction_at_a_time` |
//! | `LSBFIRST` | The bit order on the wire, which the model applies to the word it hands out and takes in | 28.5.1 | `lsbfirst_reverses_the_word_on_the_wire` |
//! | `SR` `OVR` | A word arriving with `RXNE` still set leaves the buffer alone and sets `OVR`; a read of `DR` while it stands, then a read of `SR`, clears it | 28.3.10 | `an_unread_word_overruns_and_the_new_one_is_lost`, `an_overrun_needs_both_halves_of_its_sequence` |
//! | `SR` `MODF` | `NSS` low on a master in hardware mode, or `SSI` low on one in software mode, sets it, clears `SPE` and `MSTR`, and hardware refuses to set either again while it stands; a read or write of `SR` while it stands, then a write of `CR1`, clears it | 28.3.1, 28.3.10 | `a_mode_fault_drops_the_master_and_holds_it_down`, `software_slave_management_faults_on_its_own_bit`, `a_fault_clears_only_through_both_halves_of_its_sequence` |
//! | `NSS` | Software management through `SSM` and `SSI` leaves the pin free; hardware management is what watches it, and `SSOE` makes it an output rather than the input a fault comes in on | 28.3.1 | `software_slave_management_faults_on_its_own_bit`, `an_output_slave_select_takes_no_fault_from_its_pin` |
//! | `SR` `CRCERR` | Set when the word received does not match `RXCRCR`; cleared by writing zero, and a written one leaves it alone | 28.3.10, 28.5.3 | `a_crc_that_does_not_match_is_flagged`, `a_written_one_leaves_the_crc_flag_where_it_was` |
//! | CRC | `CRCPR` is the polynomial; enabling `CRCEN` resets both registers; they accumulate over the words that cross; `CRCNEXT` sends the transmit register once the buffered data has gone | 28.3.6 | `the_crc_accumulates_over_the_words_of_a_transfer`, `enabling_the_crc_resets_its_registers`, `the_crc_goes_out_after_the_data_already_buffered` |
//! | `SR` `FRE` | A frame error in TI slave mode, cleared by reading `SR` | 28.3.10 | `a_frame_error_clears_when_the_status_register_is_read` |
//! | `CR2` interrupts | `TXEIE`, `RXNEIE`, and `ERRIE` raise the instance's line; the line follows the flags | 28.3.11 | `each_enable_raises_the_line_for_its_own_flag` |
//! | 28.3.8 | All four disable procedures, run as a firmware runs them, including the one that clears `SPE` with a word still on the wire | 28.3.8 | `the_manuals_disable_procedures_all_terminate` |
//! | `I2SCFGR`, `I2SPR` | Stored, and with `I2SMOD` set the block does nothing at all; `I2SPR`'s reset value is one ST's file gets wrong | 28.5.8, 28.5.9 | `the_audio_mode_is_stored_and_does_nothing`, `the_prescaler_comes_out_of_reset_at_the_value_the_manual_gives` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behaviour | Source | Reason |
//! |---|---|---|
//! | The I2S: its audio clock, its four protocols, and the `CHSIDE` and `UDR` flags, which section 28.5.3 marks not used in SPI mode | 28.4 | A second peripheral sharing the block, RL-116. No board in release 0.1 has an audio codec, and the audio clock is the `PLLI2S` tap the clock controller answers and nothing yet consumes |
//! | A slave's transfer beginning when the master's clock arrives, rather than when `SPE` is set or `DR` is written | 28.3.5 | No wire delivers a clock: the peer is a method, and a method cannot be a clock edge. The model begins a slave's transfer as it begins a master's, and times it from `BR`, which section 28.5.1 gives the master's baud generator alone. A slave's frames are therefore the right words in the right order at a rate its master does not choose |
//! | Clock gating: the SPI answering the bus only once `RCC_APBxENR` enables it, and being held at reset by `RCC_APBxRSTR` | 28.5, 6.3.5 to 6.3.14 | The bus does not consult another model, RL-112 |
//! | The DMA request lines of `RXDMAEN` and `TXDMAEN` | 28.3.9 | The bits are stored, and the model raises no request because nothing could answer one: a DMA model cannot reach memory across the protocol boundary. A firmware driving its SPI by DMA waits on a transfer-complete flag in the DMA that this model cannot help with, so its wait moves rather than ends |
//! | The clock phase and polarity as the shape of a waveform | 28.3.1 | `CPOL` and `CPHA` decide which edge samples and which shifts, which is visible on a wire and not in a register. They are stored, and the word that crosses is the same whatever they say |
//! | The two APB cycles between a write to `DR` and `BSY` rising, and the one SPI clock cycle a slave's `BSY` dips between continuous transfers | 28.3.7, 28.5.3 | Both are gaps narrower than the access that would observe them. The manual's own advice for the first is to wait on `TXE` and then on `BSY`, which the model carries exactly |
//! | `BSY` also meaning that the transmit buffer is not empty | 28.5.3 | The model reads `BSY` off the word on the wire alone, so a write to `DR` with the SPI disabled leaves it low where the part would raise it |
//! | The CRC computed bit by bit in transmission order, so that `LSBFIRST` reverses what it sees | 28.3.6 | The model accumulates over the word as written. A firmware that checks a CRC against a peer sees the same answer at both ends, because both ends of this model agree; one that checks against a figure computed elsewhere with `LSBFIRST` set would not |
//! | A multi-master bus, and section 28.3.10's rule that `NSS` must be pulled high during the `MODF` clearing sequence | 28.3.10 | One master is all a machine describes; the rule guards against a second one. `Spi::set_nss` does carry the level, so a machine may raise it |
//! | Any bound on how long a receive-only transfer runs | 28.3.5 | Section 28.3.5 begins that sequence as soon as `SPE` is set and the part clocks on until it is cleared, so the model schedules a word for as long as the firmware leaves it enabled. That is faithful and it is expensive: a word at the reset baud is about 190 ns, so an enabled receive-only SPI is some millions of events a second, and a session with one never goes idle. A firmware that means to stop reading clears `SPE`, which is what section 28.3.8 has it do |
//! | The transmit underrun of `UDR` | 28.5.3 | Section 28.5.3 says in as many words that the bit is not used in SPI mode |
//! | ES0206 rev 24 2.12.1: `BSY` may stay high when the SPI is disabled, in three named configurations | ES0206 rev 24 | The model clears `BSY` on the disable, section 28.3.7's second cause, which is the behaviour the manual documents and the erratum departs from. Its workaround for the master receive-only case is to ignore `BSY`, so a firmware written against the erratum still runs here |
//! | ES0206 rev 24 2.12.2: the clock starts on `MSTR` even with the SPI disabled, coming from a receiving slave | ES0206 rev 24 | The model starts nothing while `SPE` is clear. The erratum's effect is an anticipated clock on a wire, and a wire is what this model does not have |
//! | ES0206 rev 24 2.12.3: the CRC is wrong when the polynomial is even | ES0206 rev 24 | The model computes the same answer for an even polynomial as for an odd one. Modelling the erratum would mean reproducing a wrong answer whose value the sheet does not give |
//! | ES0206 rev 24 2.12.4: the last bit of data or CRC corrupted in master mode with delayed `SCK` feedback | ES0206 rev 24 | A propagation delay on a board's tracks at a frequency the model does not carry |
//! | ES0206 rev 24 2.12.5: `BSY` may stay high at the end of a slave transfer | ES0206 rev 24 | The same shape as 2.12.1 and the same reason |
//! | ES0206 rev 24 2.12.6: in I2S slave mode the external master must set the `WS` level | ES0206 rev 24 | The I2S is RL-116 |
//!
//! **Where this model departs from the vendor's file.** ST names `SR` bit 8 `TIFRFE` where section 28.5.3
//! calls it `FRE`; the generated table stays the vendor's and the model uses the manual's name in prose. The
//! register file enforces field access from that table, and ST types every flag of `SR` read-only except
//! `CRCERR`, which it types read-write where section 28.5.3 types it `rc_w0` — a written one has no effect
//! — so the model puts that bit back itself. ST also gives `I2SPR` a reset value of `0x000A` where section
//! 28.5.9 gives `0x0002`, which `memory_map.rs` corrects for every instance of the layout.
//!
//! **One rule the model keeps that the page does not state.** `CRCNEXT` clears itself once the CRC word has
//! gone out: section 28.5.1 bit 12 types it read-write and says nothing about hardware, but a bit that stood
//! would make every word after the first a CRC word, which section 28.3.6's procedure does not intend.
use std::collections::VecDeque;
use std::num::NonZeroU64;
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::time::{EventId, VirtualNanos};
use crate::soc::stm32f427::generated::spi1::{Register, cr1, cr2, i2scfgr, sr};
/// The SPI's one interrupt, position zero of its instance's table.
const GLOBAL: usize = 0;
/// How many transmitted words the model keeps for a test to look at.
const REMEMBERED: usize = 64;
/// An integrated circuit on a serial interface's wires: what a board hangs off an MCU.
///
/// The interface is full duplex, RM0090 rev 22 section 28.3.3: a word crosses each way at the same
/// time, so a peer answers in the same call it is given. Both words are as they travel, after
/// `LSBFIRST` of section 28.5.1 bit 7 has had its say, because that is what is on the wire.
pub trait Peer: std::fmt::Debug + Send {
/// The word this peer puts on `MISO` while `mosi` crosses on the controller's line.
fn transfer(&mut self, mosi: u16) -> u16;
/// Every word this peer has been sent, oldest first, or nothing where it keeps no log.
///
/// This is the far end's own record, and it is the only thing that can say a stream arrived. The
/// controller's [`Spi::shifted_out`] is what it believes it sent, which a model that never reaches
/// its peer would fill just as happily.
fn received(&self) -> &[u16] {
&[]
}
}
/// A peer that answers a known sequence, repeats its last word once that drains, and records every
/// word it was sent.
///
/// It is what a bench hangs on an interface with no chip model for it yet. It answers something known
/// and **different** from what it was given, so a controller shifting its own transmission back is
/// caught rather than passed, which a loopback would not do. And what it records is what says the far end
/// of the bus got the stream, rather than the controller's own record of what it believes it sent.
#[derive(Debug, Default)]
pub struct Pattern {
answers: VecDeque<u16>,
held: u16,
received: Vec<u16>,
}
impl Pattern {
/// A peer answering `words`, oldest first.
#[must_use]
pub fn answering(words: impl IntoIterator<Item = u16>) -> Self {
Self {
answers: words.into_iter().collect(),
held: 0,
received: Vec::new(),
}
}
}
impl Peer for Pattern {
fn received(&self) -> &[u16] {
&self.received
}
fn transfer(&mut self, mosi: u16) -> u16 {
self.received.push(mosi);
if let Some(word) = self.answers.pop_front() {
self.held = word;
}
self.held
}
}
/// One serial peripheral interface of an STM32F42xxx, RM0090 rev 22 chapter 28.
#[derive(Debug)]
pub struct Spi {
clock_hz: NonZeroU64,
peer: Option<Box<dyn Peer>>,
outgoing: VecDeque<u16>,
transmit: Option<u16>,
receive: u16,
shifting: Option<Shift>,
run: Option<Run>,
nss: Level,
interrupt: Level,
fault_sequence: bool,
overrun_sequence: bool,
crc_error: bool,
control: u64,
}
/// A word on its way across the wire.
#[derive(Clone, Copy, Debug)]
struct Shift {
event: EventId,
word: u16,
crc_phase: bool,
}
/// A run of words: when the word in flight ends, and the fraction of a nanosecond carried from the ones
/// before it. Carrying the remainder rather than counting the bits keeps both numbers small, where a
/// running bit count would overflow the arithmetic after a few minutes of a busy bus.
#[derive(Clone, Copy, Debug)]
struct Run {
ends: VirtualNanos,
carry: u64,
}
impl Spi {
/// The SPI of an instance whose peripheral bus runs at `clock_hz`, which is what `BR` divides.
#[must_use]
pub fn with_clock(clock_hz: NonZeroU64) -> Self {
Self {
clock_hz,
peer: None,
outgoing: VecDeque::new(),
transmit: None,
receive: 0,
shifting: None,
run: None,
nss: Level::High,
interrupt: Level::Low,
fault_sequence: false,
overrun_sequence: false,
crc_error: false,
control: 0,
}
}
/// Hangs an integrated circuit on this interface's wires, which is what a board does to a part.
///
/// One peer, because a second needs a chip select to tell them apart and nothing carries pin
/// levels between models yet, RL-112. Attaching again replaces what was there.
pub fn attach(&mut self, peer: impl Peer + 'static) {
self.peer = Some(Box::new(peer));
}
/// Whether anything is on the other end of the wires.
#[must_use]
pub fn attached(&self) -> bool {
self.peer.is_some()
}
/// The chip on these wires, to ask what it saw.
#[must_use]
pub fn peer(&self) -> Option<&dyn Peer> {
self.peer.as_deref()
}
/// Hangs a [`Pattern`] peer answering `words`. Sugar for [`Spi::attach`], and what most tests want.
pub fn drive_miso(&mut self, words: impl IntoIterator<Item = u16>) {
self.attach(Pattern::answering(words));
}
/// The words the SPI has most recently shifted out, oldest first.
// ceiling: REMEMBERED words; a record of every word sent would grow for as long as the bus stayed busy.
#[must_use]
pub fn shifted_out(&self) -> Vec<u16> {
self.outgoing.iter().copied().collect()
}
/// The level on the `NSS` pin, which RM0090 rev 22 section 28.3.1 gives two jobs: the mode fault of
/// section 28.3.10 on a master managing it in hardware, and that section's frame error on a TI slave.
pub fn drive_nss(
&mut self,
level: Level,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let was = self.nss;
self.nss = level;
if was == level || level == Level::High {
return;
}
let cr1_value = registers.get(Register::Cr1);
if cr1::MSTR.is_set(cr1_value) {
// Section 28.3.1: the fault is entered "When configured in master mode with NSS configured as
// an input (MSTR=1 and SSOE=0)", and section 28.3.1 again: with software management "the
// external NSS pin remains free for other application uses", so the pin says nothing there.
if !cr1::SSM.is_set(cr1_value) && !cr2::SSOE.is_set(registers.get(Register::Cr2)) {
self.fault(registers, context);
}
return;
}
if cr2::FRF.is_set(registers.get(Register::Cr2)) && self.shifting.is_some() {
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::TIFRFE.set(sr::BSY.set(status, 0), 1));
self.shifting = None;
self.run = None;
self.refresh_interrupt(registers, context);
}
}
/// The master mode fault of RM0090 rev 22 section 28.3.10: `MODF` is set, `SPE` is cleared, "which
/// blocks all output from the device", and `MSTR` is cleared, "thus forcing the device into slave mode".
fn fault(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let cr1_value = registers.get(Register::Cr1);
registers.set(Register::Cr1, cr1::MSTR.set(cr1::SPE.set(cr1_value, 0), 0));
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::MODF.set(sr::BSY.set(status, 0), 1));
self.shifting = None;
self.run = None;
self.fault_sequence = false;
self.store_control(registers);
self.refresh_interrupt(registers, context);
}
}
/// A register of this layout read back as the word it is. RM0090 rev 22 section 28.5 gives every one of
/// them sixteen bits, so the mask cannot lose anything the part could have stored.
fn stored(registers: &RegisterFile, register: Register) -> u16 {
u16::try_from(registers.get(register) & u64::from(u16::MAX)).expect("masked to sixteen bits")
}
/// `BR`: `fPCLK` over two to the encoding plus one, RM0090 rev 22 section 28.5.1 bits 5:3.
fn baud_divider(encoding: u64) -> u64 {
2 << encoding
}
impl Spi {
/// The bit rate on the wire, RM0090 rev 22 section 28.5.1.
fn baud_hz(&self, registers: &RegisterFile) -> u64 {
self.clock_hz.get() / baud_divider(cr1::BR.get(registers.get(Register::Cr1)))
}
/// Eight bits, or sixteen with `DFF` set; RM0090 rev 22 section 28.5.1 bit 11.
fn frame_bits(registers: &RegisterFile) -> u64 {
if cr1::DFF.is_set(registers.get(Register::Cr1)) {
16
} else {
8
}
}
/// When the word starting now finishes. The nanoseconds a word takes rarely divide evenly, so the
/// remainder is carried rather than dropped, which would drift for as long as the bus stayed busy.
fn ends(&mut self, now: VirtualNanos, registers: &RegisterFile) -> VirtualNanos {
let baud = self.baud_hz(registers).max(1);
let bits = Self::frame_bits(registers);
let run = self.run.get_or_insert(Run {
ends: now,
carry: 0,
});
let numerator = bits * 1_000_000_000 + run.carry;
run.carry = numerator % baud;
run.ends = VirtualNanos(run.ends.0 + (numerator / baud).max(1));
run.ends
}
/// The word masked to the frame, so a firmware that writes sixteen bits into an eight-bit frame sends
/// what the wire can carry.
fn framed(registers: &RegisterFile, word: u16) -> u16 {
if Self::frame_bits(registers) == 16 {
word
} else {
word & 0xFF
}
}
/// The word as it travels, which `LSBFIRST` reverses; RM0090 rev 22 section 28.5.1 bit 7.
fn on_the_wire(registers: &RegisterFile, word: u16) -> u16 {
if !cr1::LSBFIRST.is_set(registers.get(Register::Cr1)) {
return word;
}
let bits = u32::try_from(Self::frame_bits(registers)).expect("eight or sixteen");
word.reverse_bits() >> (16 - bits)
}
/// Whether the block is an SPI at all, or the I2S that shares its registers; section 28.5.8 bit 11.
fn is_spi(registers: &RegisterFile) -> bool {
!i2scfgr::I2SMOD.is_set(registers.get(Register::I2scfgr))
}
/// Whether a transfer puts a word on the wire, RM0090 rev 22 section 28.3.5.
fn transmits(registers: &RegisterFile) -> bool {
let value = registers.get(Register::Cr1);
if cr1::BIDIMODE.is_set(value) {
cr1::BIDIOE.is_set(value)
} else {
!cr1::RXONLY.is_set(value)
}
}
/// Whether a transfer takes a word off the wire, RM0090 rev 22 section 28.3.5.
fn receives(registers: &RegisterFile) -> bool {
let value = registers.get(Register::Cr1);
if cr1::BIDIMODE.is_set(value) {
!cr1::BIDIOE.is_set(value)
} else {
true
}
}
/// Whether `BSY` shows this transfer at all: RM0090 rev 22 section 28.3.7 keeps it low through a
/// master's bidirectional reception, which is the one exception it names.
fn busy_shows(registers: &RegisterFile) -> bool {
let value = registers.get(Register::Cr1);
!(cr1::MSTR.is_set(value) && cr1::BIDIMODE.is_set(value) && !cr1::BIDIOE.is_set(value))
}
/// One word across the wire, both ways at once: what the peer is given is what went out, and what
/// it answers is what arrives. Nothing attached means nothing drives `MISO`, and the line reads low.
///
/// It is called for every word the clock carries, whether or not the controller is listening: a
/// chip is clocked by the wire and not by the controller's opinion of the direction, so a run in
/// the transmit-only bidirectional mode of section 28.3.4 leaves the peer as many words further on
/// as it was sent.
fn cross(&mut self, outgoing: u16) -> u16 {
match self.peer.as_mut() {
Some(peer) => peer.transfer(outgoing),
None => 0,
}
}
}
/// The CRC of RM0090 rev 22 section 28.3.6: the polynomial of `CRCPR` over the words of a transfer, eight
/// bits wide for an eight-bit frame and sixteen for a sixteen-bit one.
fn accumulate(crc: u16, word: u16, polynomial: u16, bits: u64) -> u16 {
let top = 1 << (bits - 1);
let mask = if bits == 16 { u16::MAX } else { 0xFF };
let mut value = crc;
for position in (0..bits).rev() {
let bit = (word >> position) & 1;
let carry = (value & top) != 0;
value = (value << 1) & mask;
if carry ^ (bit == 1) {
value ^= polynomial & mask;
}
}
value
}
impl Spi {
/// Starts a transfer when one is due, RM0090 rev 22 section 28.3.5: on the write to `DR`, or as soon
/// as `SPE` is set where nothing is transmitted.
fn start(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if self.shifting.is_some() || !Self::is_spi(registers) {
return;
}
let cr1_value = registers.get(Register::Cr1);
if !cr1::SPE.is_set(cr1_value) {
return;
}
let armed = cr1::CRCEN.is_set(cr1_value) && cr1::CRCNEXT.is_set(cr1_value);
let mut crc_phase = armed;
let word = if Self::transmits(registers) {
match self.transmit.take() {
// Section 28.3.6: "If data are present in the TX buffer, the CRC value is transmitted only
// after the transmission of the data byte." The procedure has a firmware set CRCNEXT as it
// writes the last word, so the bit waits for the buffer to drain rather than eating it.
Some(word) => {
crc_phase = false;
word
}
None if armed => stored(registers, Register::Txcrcr),
None => return,
}
} else {
0
};
let status = registers.get(Register::Sr);
let busy = u64::from(Self::busy_shows(registers));
registers.set(Register::Sr, sr::BSY.set(sr::TXE.set(status, 1), busy));
let at = self.ends(context.now(), registers);
self.shifting = Some(Shift {
event: context.schedule(at),
word,
crc_phase,
});
}
/// One word has crossed.
fn finish(
&mut self,
shift: Shift,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let bits = Self::frame_bits(registers);
let polynomial = stored(registers, Register::Crcpr);
let crc_on = cr1::CRCEN.is_set(registers.get(Register::Cr1));
let on_the_wire = Self::on_the_wire(registers, shift.word);
if Self::transmits(registers) {
if self.outgoing.len() == REMEMBERED {
self.outgoing.pop_front();
}
self.outgoing.push_back(on_the_wire);
if crc_on && !shift.crc_phase {
let crc = stored(registers, Register::Txcrcr);
let next = accumulate(crc, shift.word, polynomial, bits);
registers.set(Register::Txcrcr, u64::from(next));
}
}
let answered = self.cross(on_the_wire);
let mut status = registers.get(Register::Sr);
if Self::receives(registers) {
let word = Self::framed(registers, Self::on_the_wire(registers, answered));
if sr::RXNE.is_set(status) {
// Section 28.3.10: the buffer keeps what it had and the new word is lost.
status = sr::OVR.set(status, 1);
} else {
self.receive = word;
status = sr::RXNE.set(status, 1);
if shift.crc_phase {
// Section 28.3.6: the word against the CRC phase is the peer's CRC, checked here; the
// page still has it reach the buffer, since "the CRC must be read in the SPI_DR
// register to clear the RXNE flag".
self.crc_error = word != stored(registers, Register::Rxcrcr);
status = sr::CRCERR.set(status, u64::from(self.crc_error));
} else if crc_on {
let crc = stored(registers, Register::Rxcrcr);
let next = accumulate(crc, word, polynomial, bits);
registers.set(Register::Rxcrcr, u64::from(next));
}
}
}
if shift.crc_phase {
let cr1_value = registers.get(Register::Cr1);
registers.set(Register::Cr1, cr1::CRCNEXT.set(cr1_value, 0));
}
registers.set(Register::Sr, sr::BSY.set(status, 0));
self.shifting = None;
self.start(registers, context);
if self.shifting.is_none() {
self.run = None;
}
self.store_control(registers);
self.refresh_interrupt(registers, context);
}
/// Raises the instance's line while an enabled flag stands, RM0090 rev 22 section 28.3.11. The model
/// keeps the level it last drove, so a write that changes nothing reports nothing.
fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let status = registers.get(Register::Sr);
let control = registers.get(Register::Cr2);
let errors = sr::OVR.mask() | sr::MODF.mask() | sr::CRCERR.mask() | sr::TIFRFE.mask();
let wanted = if (cr2::TXEIE.is_set(control) && sr::TXE.is_set(status))
|| (cr2::RXNEIE.is_set(control) && sr::RXNE.is_set(status))
|| (cr2::ERRIE.is_set(control) && status & errors != 0)
{
Level::High
} else {
Level::Low
};
if wanted == self.interrupt {
return;
}
self.interrupt = wanted;
match wanted {
Level::High => context.raise(GLOBAL),
Level::Low => context.lower(GLOBAL),
}
}
fn store_control(&mut self, registers: &RegisterFile) {
self.control = registers.get(Register::Cr1);
}
}
impl Device for Spi {
type Register = Register;
/// `DR` answers the receive buffer and clears `RXNE`, RM0090 rev 22 section 28.3.5, leaving the word
/// there as section 28.3.10 requires. Reading `SR` clears `FRE` and is the second half of the
/// sequences that clear `OVR` and `MODF`.
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
let status = registers.get(Register::Sr);
match register {
Register::Dr => {
registers.set(Register::Sr, sr::RXNE.set(status, 0));
// Step one of section 28.3.10's overrun sequence counts only while the overrun stands.
self.overrun_sequence = sr::OVR.is_set(status);
self.refresh_interrupt(registers, context);
Some(u64::from(self.receive))
}
Register::Sr => {
let mut cleared = sr::TIFRFE.set(status, 0);
if self.overrun_sequence {
cleared = sr::OVR.set(cleared, 0);
self.overrun_sequence = false;
}
// "Make a read or write access to the SPI_SR register while the MODF bit is set."
self.fault_sequence = sr::MODF.is_set(status);
registers.set(Register::Sr, cleared);
self.refresh_interrupt(registers, context);
Some(status)
}
_ => None,
}
}
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
Register::Dr => {
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::TXE.set(status, 0));
let written =
u16::try_from(value & u64::from(u16::MAX)).expect("masked to sixteen bits");
self.transmit = Some(Self::framed(registers, written));
self.start(registers, context);
}
Register::Cr1 => self.write_cr1(registers, context),
Register::Sr => self.write_sr(value, registers, context),
_ => {}
}
self.refresh_interrupt(registers, context);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
// A model keeps the id it last scheduled and ignores any other; there is no cancel.
let Some(shift) = self.shifting.filter(|shift| shift.event == event) else {
return;
};
self.finish(shift, registers, context);
}
}
impl Spi {
/// `CR1`: the mode fault's hold on the enable and its clearing sequence, the CRC registers `CRCEN`
/// resets, the fault a cleared `SSI` raises, and the start of a receive-only transfer.
fn write_cr1(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let status = registers.get(Register::Sr);
if sr::MODF.is_set(status) {
// Section 28.3.10: "hardware does not allow the setting of the SPE and MSTR bits while the
// MODF bit is set", and an access to SR while it stands, then this write, is what clears it.
let value = registers.get(Register::Cr1);
registers.set(Register::Cr1, cr1::MSTR.set(cr1::SPE.set(value, 0), 0));
if self.fault_sequence {
registers.set(Register::Sr, sr::MODF.set(status, 0));
self.fault_sequence = false;
}
self.store_control(registers);
self.refresh_interrupt(registers, context);
return;
}
let value = registers.get(Register::Cr1);
// Section 28.3.6: enabling the CRC "resets the CRC registers (SPI_RXCRCR and SPI_TXCRCR)".
if cr1::CRCEN.is_set(value) && !cr1::CRCEN.is_set(self.control) {
registers.set(Register::Rxcrcr, 0);
registers.set(Register::Txcrcr, 0);
}
self.store_control(registers);
// Section 28.3.10: the fault comes from "SSI bit low (in NSS software mode)" as well as the pin.
if cr1::MSTR.is_set(value) && cr1::SSM.is_set(value) && !cr1::SSI.is_set(value) {
self.fault(registers, context);
return;
}
if !cr1::SPE.is_set(value) {
// Section 28.3.7: BSY "is cleared ... when the SPI is disabled". The word already on the wire
// still lands: section 28.3.8's receive-only procedure has a firmware clear SPE and then wait
// for the last RXNE, which it could not do if the word were dropped here.
registers.set(Register::Sr, sr::BSY.set(status, 0));
return;
}
self.start(registers, context);
}
/// `SR`: every flag is hardware's but `CRCERR`, which section 28.5.3 types `rc_w0`. ST's file types it
/// read-write, so a written one reaches the register file and the model has to put it back.
fn write_sr(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let status = registers.get(Register::Sr);
if sr::CRCERR.is_set(written) {
registers.set(
Register::Sr,
sr::CRCERR.set(status, u64::from(self.crc_error)),
);
} else {
self.crc_error = false;
registers.set(Register::Sr, sr::CRCERR.set(status, 0));
}
// "Make a read or write access to the SPI_SR register while the MODF bit is set."
self.fault_sequence = sr::MODF.is_set(status);
self.refresh_interrupt(registers, context);
}
}