Skip to main content

omnitiles/hw/
spi.rs

1// SPDX-License-Identifier: MIT
2// © 2025–2026 Christopher Liu
3
4//! Serial Peripheral Interface (SPI) abstraction layer.
5//!
6//! - `SpiBus` wraps a configured HAL SPI instance with 8-bit words.
7//! - `ChipSelect` is an active-low GPIO output wrappr for manual CS control.
8
9use stm32f7xx_hal::{
10    gpio::{self, Output, PinState, PushPull},
11    prelude::*,
12    spi::{self, Enabled, Spi},
13};
14
15/// Wrapper around an enabled HAL SPI instance (8-bit words).
16pub struct SpiBus<I, P> {
17    spi: Spi<I, P, Enabled<u8>>,
18}
19
20impl<I, P> SpiBus<I, P>
21where
22    I: spi::Instance,
23    P: spi::Pins<I>,
24{
25    pub fn new(spi: Spi<I, P, Enabled<u8>>) -> Self {
26        Self { spi }
27    }
28
29    /// Perform a blocking, full-duplex transfer of one byte.
30    pub fn transfer_byte(&mut self, byte: u8) -> Result<u8, spi::Error> {
31        let mut tmp = [byte];
32        self.spi.transfer(&mut tmp)?;
33        Ok(tmp[0])
34    }
35
36    /// Send a byte, ignoring the response.
37    #[inline]
38    pub fn write_byte(&mut self, byte: u8) -> Result<(), spi::Error> {
39        let _ = self.transfer_byte(byte)?;
40        Ok(())
41    }
42
43    /// Read a byte, sending 0x00.
44    #[inline]
45    pub fn read_byte(&mut self) -> Result<u8, spi::Error> {
46        self.transfer_byte(0x00)
47    }
48
49    /// Transfer a byte buffer in-place.
50    pub fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), spi::Error> {
51        for b in buf.iter_mut() {
52            *b = self.transfer_byte(*b)?;
53        }
54        Ok(())
55    }
56
57    pub fn free(self) -> Spi<I, P, Enabled<u8>> {
58        self.spi
59    }
60}
61
62/// Trait for chip-select control, allowing real pins or a no-op stub.
63pub trait CsControl {
64    fn select(&mut self);
65    fn deselect(&mut self);
66}
67
68/// Stub chip-select for when no SPI connection is used.
69/// Panics if `select()` or `deselect()` is ever called.
70pub struct NoChipSelect;
71
72impl CsControl for NoChipSelect {
73    fn select(&mut self) {
74        panic!("SPI chip select used on a driver constructed without one");
75    }
76    fn deselect(&mut self) {
77        panic!("SPI chip select used on a driver constructed without one");
78    }
79}
80
81/// Manual chip-select line, active-low, generic over any GPIO pin.
82pub struct ChipSelect<const P: char, const N: u8> {
83    pin: gpio::Pin<P, N, Output<PushPull>>,
84}
85
86impl<const P: char, const N: u8> ChipSelect<P, N> {
87    /// Create an active-low chip select and set to the inactive state (i.e., high).
88    pub fn active_low<MODE>(pin: gpio::Pin<P, N, MODE>) -> Self {
89        let mut pin = pin.into_push_pull_output();
90        pin.set_state(PinState::High);
91        Self { pin }
92    }
93
94    /// Assert the chip select.
95    #[inline]
96    pub fn select(&mut self) {
97        self.pin.set_low();
98    }
99
100    /// Deassert the chip select.
101    #[inline]
102    pub fn deselect(&mut self) {
103        self.pin.set_high();
104    }
105
106    pub fn free(self) -> gpio::Pin<P, N, Output<PushPull>> {
107        self.pin
108    }
109}
110
111impl<const P: char, const N: u8> CsControl for ChipSelect<P, N> {
112    #[inline]
113    fn select(&mut self) {
114        self.pin.set_low();
115    }
116    #[inline]
117    fn deselect(&mut self) {
118        self.pin.set_high();
119    }
120}