Skip to main content

pamoja_gpio/
pin.rs

1//! The GPIO pin model: levels, pull and drive configuration, interrupt edges, and active
2//! polarity.
3//!
4//! A GPIO pin is the simplest interface on a board: one line that is either high or low.
5//! The logic that still has to be right is the meaning of that level. A button wired to
6//! ground through a pull-up reads low when pressed; a relay board sold as "active low"
7//! switches on when its input is driven low. Treating "pressed" or "on" as if it always
8//! meant a high level is a classic inversion bug. This module carries the small set of
9//! GPIO concepts so that mapping is written down once rather than scattered through call
10//! sites.
11
12/// The physical voltage level on a pin.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Level {
15    /// A low level, near ground.
16    Low,
17    /// A high level, near the supply voltage.
18    High,
19}
20
21impl Level {
22    /// Returns the opposite level.
23    pub fn inverted(self) -> Level {
24        match self {
25            Level::Low => Level::High,
26            Level::High => Level::Low,
27        }
28    }
29
30    /// Returns `true` if this is [`High`](Level::High).
31    pub fn is_high(self) -> bool {
32        matches!(self, Level::High)
33    }
34
35    /// Returns `true` if this is [`Low`](Level::Low).
36    pub fn is_low(self) -> bool {
37        matches!(self, Level::Low)
38    }
39
40    /// Returns the level a boolean names.
41    ///
42    /// # Arguments
43    ///
44    /// * `high` - `true` for [`High`](Level::High), `false` for [`Low`](Level::Low).
45    pub fn from_bool(high: bool) -> Level {
46        if high {
47            Level::High
48        } else {
49            Level::Low
50        }
51    }
52}
53
54/// Whether a pin reads its line (input) or drives it (output).
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Direction {
57    /// The pin reads the level on its line.
58    Input,
59    /// The pin drives the level on its line.
60    Output,
61}
62
63/// The internal pull resistor applied to an input pin.
64///
65/// A floating input drifts and reads noise, so a pin reading a switch needs a defined
66/// resting level from a pull resistor (internal where the chip offers one, external
67/// otherwise).
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum Pull {
70    /// No internal pull; the line floats unless something external holds it.
71    None,
72    /// An internal pull-up holds the line high when nothing drives it.
73    Up,
74    /// An internal pull-down holds the line low when nothing drives it.
75    Down,
76}
77
78/// How an output pin drives its two states.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum Drive {
81    /// Push-pull: the pin actively drives both high and low.
82    PushPull,
83    /// Open-drain: the pin actively drives low and floats when high, so an external
84    /// pull-up sets the high level. This is what shared, multi-device lines like I2C use.
85    OpenDrain,
86}
87
88/// The signal transition that triggers a pin interrupt.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum Edge {
91    /// A low-to-high transition.
92    Rising,
93    /// A high-to-low transition.
94    Falling,
95    /// Either transition.
96    Both,
97}
98
99impl Edge {
100    /// Returns `true` if a change from `from` to `to` is an edge this trigger fires on.
101    ///
102    /// # Arguments
103    ///
104    /// * `from` - the level before the change.
105    /// * `to` - the level after the change.
106    ///
107    /// # Returns
108    ///
109    /// `true` if the transition matches this trigger; `false` for the other direction or
110    /// for no change at all.
111    pub fn triggered_by(self, from: Level, to: Level) -> bool {
112        match (from, to) {
113            (Level::Low, Level::High) => matches!(self, Edge::Rising | Edge::Both),
114            (Level::High, Level::Low) => matches!(self, Edge::Falling | Edge::Both),
115            _ => false,
116        }
117    }
118}
119
120/// Whether a signal is asserted by a high or a low physical level.
121///
122/// Active-low wiring is everywhere in cheap hardware: a button to ground with a pull-up
123/// reads [`Level::Low`] when pressed, and many relay boards energise when their input is
124/// driven low. This type maps between the logical idea of "asserted" and the physical
125/// [`Level`] so the mapping lives in one place instead of in scattered inversions.
126///
127/// # Examples
128///
129/// ```
130/// use pamoja_gpio::pin::{Level, Polarity};
131///
132/// // An active-low relay: asserting it (switching the relay on) drives the pin low.
133/// let relay = Polarity::ActiveLow;
134/// assert_eq!(relay.level(true), Level::Low);
135/// assert_eq!(relay.level(false), Level::High);
136/// assert!(relay.is_asserted(Level::Low));
137/// ```
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub enum Polarity {
140    /// A high level means asserted (the direct mapping).
141    ActiveHigh,
142    /// A low level means asserted (the inverted mapping).
143    ActiveLow,
144}
145
146impl Polarity {
147    /// Returns the physical level for a logical state.
148    ///
149    /// # Arguments
150    ///
151    /// * `asserted` - whether the signal should be asserted.
152    ///
153    /// # Returns
154    ///
155    /// The [`Level`] that represents that state under this polarity.
156    pub fn level(self, asserted: bool) -> Level {
157        match self {
158            Polarity::ActiveHigh => Level::from_bool(asserted),
159            Polarity::ActiveLow => Level::from_bool(!asserted),
160        }
161    }
162
163    /// Returns whether a physical level means the signal is asserted.
164    ///
165    /// # Arguments
166    ///
167    /// * `level` - the level read on the pin.
168    ///
169    /// # Returns
170    ///
171    /// `true` if `level` asserts the signal under this polarity.
172    pub fn is_asserted(self, level: Level) -> bool {
173        match self {
174            Polarity::ActiveHigh => level.is_high(),
175            Polarity::ActiveLow => level.is_low(),
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn level_helpers() {
186        assert_eq!(Level::Low.inverted(), Level::High);
187        assert_eq!(Level::High.inverted(), Level::Low);
188        assert!(Level::High.is_high() && !Level::High.is_low());
189        assert_eq!(Level::from_bool(true), Level::High);
190        assert_eq!(Level::from_bool(false), Level::Low);
191    }
192
193    #[test]
194    fn edges_fire_on_the_right_transition() {
195        assert!(Edge::Rising.triggered_by(Level::Low, Level::High));
196        assert!(!Edge::Rising.triggered_by(Level::High, Level::Low));
197        assert!(Edge::Falling.triggered_by(Level::High, Level::Low));
198        assert!(!Edge::Falling.triggered_by(Level::Low, Level::High));
199        assert!(Edge::Both.triggered_by(Level::Low, Level::High));
200        assert!(Edge::Both.triggered_by(Level::High, Level::Low));
201        // No transition never fires.
202        assert!(!Edge::Both.triggered_by(Level::High, Level::High));
203        assert!(!Edge::Rising.triggered_by(Level::Low, Level::Low));
204    }
205
206    #[test]
207    fn active_high_is_the_direct_mapping() {
208        assert_eq!(Polarity::ActiveHigh.level(true), Level::High);
209        assert_eq!(Polarity::ActiveHigh.level(false), Level::Low);
210        assert!(Polarity::ActiveHigh.is_asserted(Level::High));
211        assert!(!Polarity::ActiveHigh.is_asserted(Level::Low));
212    }
213
214    #[test]
215    fn active_low_inverts() {
216        assert_eq!(Polarity::ActiveLow.level(true), Level::Low);
217        assert_eq!(Polarity::ActiveLow.level(false), Level::High);
218        assert!(Polarity::ActiveLow.is_asserted(Level::Low));
219        assert!(!Polarity::ActiveLow.is_asserted(Level::High));
220    }
221
222    #[test]
223    fn level_and_is_asserted_are_inverses() {
224        for polarity in [Polarity::ActiveHigh, Polarity::ActiveLow] {
225            for asserted in [true, false] {
226                assert_eq!(polarity.is_asserted(polarity.level(asserted)), asserted);
227            }
228        }
229    }
230}