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}