Skip to main content

pamoja_gpio/
spi.rs

1//! SPI clock modes and bit order.
2//!
3//! SPI has no addressing and no framing of its own: a transfer is just bytes clocked in
4//! and out at the same time. What a controller and a peripheral must agree on is when the
5//! clock idles and which clock edge samples data - the two bits CPOL (clock polarity) and
6//! CPHA (clock phase) - and whether each byte travels most- or least-significant bit
7//! first. Datasheets quote the CPOL/CPHA pair as a single mode number from 0 to 3, and the
8//! commonest cause of a dead SPI link is a transposed pair or the wrong mode. This module
9//! makes the mode a checked value rather than two loose booleans a caller can swap.
10
11/// An SPI clock mode: the `(CPOL, CPHA)` pair a controller and peripheral must share.
12///
13/// The mode number is `(CPOL << 1) | CPHA`, so the four modes are:
14///
15/// | Mode | CPOL | CPHA | Clock idles | Data sampled on |
16/// | --- | --- | --- | --- | --- |
17/// | 0 | 0 | 0 | low | leading edge (rising) |
18/// | 1 | 0 | 1 | low | trailing edge (falling) |
19/// | 2 | 1 | 0 | high | leading edge (falling) |
20/// | 3 | 1 | 1 | high | trailing edge (rising) |
21///
22/// # Examples
23///
24/// ```
25/// use pamoja_gpio::spi::Mode;
26///
27/// // An SD card and most LoRa radios use mode 0.
28/// assert_eq!(Mode::Mode0.number(), 0);
29/// assert_eq!(Mode::Mode0.cpol_cpha(), (false, false));
30/// assert_eq!(Mode::from_number(3), Some(Mode::Mode3));
31/// assert_eq!(Mode::from_cpol_cpha(true, false), Mode::Mode2);
32/// ```
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Mode {
35    /// CPOL 0, CPHA 0: clock idles low, data sampled on the rising (leading) edge.
36    Mode0,
37    /// CPOL 0, CPHA 1: clock idles low, data sampled on the falling (trailing) edge.
38    Mode1,
39    /// CPOL 1, CPHA 0: clock idles high, data sampled on the falling (leading) edge.
40    Mode2,
41    /// CPOL 1, CPHA 1: clock idles high, data sampled on the rising (trailing) edge.
42    Mode3,
43}
44
45impl Mode {
46    /// Returns the mode number, `0..=3`, as datasheets quote it.
47    ///
48    /// # Returns
49    ///
50    /// The number `(CPOL << 1) | CPHA`.
51    pub fn number(self) -> u8 {
52        match self {
53            Mode::Mode0 => 0,
54            Mode::Mode1 => 1,
55            Mode::Mode2 => 2,
56            Mode::Mode3 => 3,
57        }
58    }
59
60    /// Returns the mode a number names, if it is in range.
61    ///
62    /// # Arguments
63    ///
64    /// * `number` - a mode number.
65    ///
66    /// # Returns
67    ///
68    /// The matching [`Mode`], or [`None`] if `number` is above `3`.
69    pub fn from_number(number: u8) -> Option<Mode> {
70        match number {
71            0 => Some(Mode::Mode0),
72            1 => Some(Mode::Mode1),
73            2 => Some(Mode::Mode2),
74            3 => Some(Mode::Mode3),
75            _ => None,
76        }
77    }
78
79    /// Returns the `(CPOL, CPHA)` pair for this mode.
80    ///
81    /// # Returns
82    ///
83    /// `(clock idles high, data sampled on the trailing edge)`.
84    pub fn cpol_cpha(self) -> (bool, bool) {
85        match self {
86            Mode::Mode0 => (false, false),
87            Mode::Mode1 => (false, true),
88            Mode::Mode2 => (true, false),
89            Mode::Mode3 => (true, true),
90        }
91    }
92
93    /// Returns the mode for a `(CPOL, CPHA)` pair.
94    ///
95    /// # Arguments
96    ///
97    /// * `cpol` - clock polarity: `true` if the clock idles high.
98    /// * `cpha` - clock phase: `true` if data is sampled on the trailing edge.
99    ///
100    /// # Returns
101    ///
102    /// The matching [`Mode`]. Every pair maps to a mode, so this never fails.
103    pub fn from_cpol_cpha(cpol: bool, cpha: bool) -> Mode {
104        match (cpol, cpha) {
105            (false, false) => Mode::Mode0,
106            (false, true) => Mode::Mode1,
107            (true, false) => Mode::Mode2,
108            (true, true) => Mode::Mode3,
109        }
110    }
111
112    /// Returns `true` if the clock idles high (CPOL = 1), which is modes 2 and 3.
113    pub fn clock_idles_high(self) -> bool {
114        self.cpol_cpha().0
115    }
116
117    /// Returns `true` if data is sampled on the trailing clock edge (CPHA = 1), which is
118    /// modes 1 and 3.
119    pub fn samples_on_trailing_edge(self) -> bool {
120        self.cpol_cpha().1
121    }
122}
123
124/// The order bits travel within each SPI byte.
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub enum BitOrder {
127    /// Most-significant bit first. The common default for nearly every SPI peripheral.
128    MsbFirst,
129    /// Least-significant bit first.
130    LsbFirst,
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    const ALL: [Mode; 4] = [Mode::Mode0, Mode::Mode1, Mode::Mode2, Mode::Mode3];
138
139    #[test]
140    fn number_round_trips() {
141        for mode in ALL {
142            assert_eq!(Mode::from_number(mode.number()), Some(mode));
143        }
144        assert_eq!(Mode::from_number(4), None);
145    }
146
147    #[test]
148    fn number_is_cpol_shifted_over_cpha() {
149        // The defining relation: mode = (CPOL << 1) | CPHA.
150        for mode in ALL {
151            let (cpol, cpha) = mode.cpol_cpha();
152            assert_eq!(mode.number(), (u8::from(cpol) << 1) | u8::from(cpha));
153        }
154    }
155
156    #[test]
157    fn cpol_cpha_round_trips() {
158        for mode in ALL {
159            let (cpol, cpha) = mode.cpol_cpha();
160            assert_eq!(Mode::from_cpol_cpha(cpol, cpha), mode);
161        }
162    }
163
164    #[test]
165    fn the_named_pairs_are_correct() {
166        assert_eq!(Mode::Mode0.cpol_cpha(), (false, false));
167        assert_eq!(Mode::Mode1.cpol_cpha(), (false, true));
168        assert_eq!(Mode::Mode2.cpol_cpha(), (true, false));
169        assert_eq!(Mode::Mode3.cpol_cpha(), (true, true));
170        assert!(!Mode::Mode0.clock_idles_high() && !Mode::Mode0.samples_on_trailing_edge());
171        assert!(Mode::Mode3.clock_idles_high() && Mode::Mode3.samples_on_trailing_edge());
172    }
173}