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}