frclib_core/hal/gpio/mod.rs
1//! A module for the HAL GPIO driver.
2
3pub mod analog;
4pub mod digital;
5
6use std::fmt::Display;
7
8#[allow(clippy::upper_case_acronyms, missing_docs)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum GPIOPortType {
11 Analog,
12 Digital,
13 PWM,
14}
15impl Display for GPIOPortType {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 Self::Analog => write!(f, "Analog Port"),
19 Self::Digital => write!(f, "Digital Port"),
20 Self::PWM => write!(f, "PWM Port"),
21 }
22 }
23}
24
25#[allow(variant_size_differences, missing_docs)]
26#[non_exhaustive]
27#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq)]
28pub enum GPIOError {
29 #[error("GPIO port {0} is not available")]
30 PortNotAvailable(u8),
31 #[error("Value {0} is out of range for {1} port {2}")]
32 ValueOutOfRange(Volt, GPIOPortType, u8),
33 #[error("Wrong mode for {0} port {1}, expected output to be {2}")]
34 WrongMode(GPIOPortType, u8, bool),
35 #[error("GPIO port {0} is already in use")]
36 PortInUse(u8),
37}
38
39/// A trait for a GPIO channel.
40pub trait Channel {
41 /// The id the channel is opened on
42 fn channel_id(&self) -> u8;
43}
44
45use analog::{AnalogInput, AnalogOutput};
46use digital::{DigitalInput, DigitalOutput};
47
48use crate::units::energy::Volt;
49
50use super::NotSimError;
51
52/// A platform specific GPIO driver.
53///
54/// # Ports
55/// ## Identifying Ports
56/// Ports are identified by a `u8` value. The meaning of this value is up to driver developer but
57/// it is recommended to use the following convention of the ``NI RoboRio`` of starting at 0 and
58/// incrementing by 1 for each port. This is not enforced by the HAL.
59///
60/// ## Port Types
61/// There are three types of ports: analog, digital, and PWM.
62/// If the platform allows it one port can be multiple types.
63/// This means that id 0 could mean a different physical pin/port depending on the type
64/// or that a port could be both analog and digital and setting the analog value would overwrite the digital value.
65///
66/// ### Analog
67/// Analog ports are ports that can read a voltage value from 0 to 5 volts.
68/// If the platform supports a different range of voltages it is recommended to scale the value.
69///
70/// ### Digital
71/// Digital ports are ports that can read a boolean value.
72///
73/// ### PWM
74/// PWM ports are ports that can write a value from 0 to 1.
75///
76/// # Safety
77/// ## Thread Safety
78/// It's up to the driver developer to ensure that the driver is thread safe.
79///
80/// # Development Resources
81/// - [WPILib Rio DIO](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/athena/DIO.cpp)
82/// - [WPILib Sim DIO](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/sim/DIO.cpp)
83/// - [WPILib Rio AnalogIn](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/athena/AnalogInput.cpp)
84/// - [WPILib Sim AnalogIn](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/sim/AnalogInput.cpp)
85/// - [WPILib Rio AnalogOut](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/athena/AnalogOutput.cpp)
86/// - [WPILib Sim AnalogOut](https://github.com/wpilibsuite/allwpilib/blob/main/hal/src/main/native/sim/AnalogOutput.cpp)
87/// - [Raspberry Pi GPIO](https://github.com/golemparts/rppal/tree/master)
88/// - [Jetson GPIO](https://github.com/Kajatin/jetson-gpio-rust/tree/main)
89pub trait GPIODriver {
90 /// A list of all analog ports available on the platform.
91 const ANALOG_PORTS: &'static [u8];
92 /// A list of all digital ports available on the platform.
93 const DIGITAL_PORTS: &'static [u8];
94 /// A list of all PWM ports available on the platform.
95 const PWM_PORTS: &'static [u8];
96 /// A list of all relay ports available on the platform.
97 const RELAY_PORTS: &'static [u8];
98
99 //
100 // # Channels
101 //
102
103 /// Returns a new analog input for the specified port.
104 ///
105 /// # Errors
106 /// - [`GPIOError::PortNotAvailable`] if the port is not available for analog in use
107 /// - [`GPIOError::PortInUse`] if the port is already in use
108 fn new_analog_input(port: u8) -> Result<AnalogInput, GPIOError>;
109
110 /// Returns a new analog output for the specified port.
111 ///
112 /// # Errors
113 /// - [`GPIOError::PortNotAvailable`] if the port is not available for analog out use
114 /// - [`GPIOError::PortInUse`] if the port is already in use
115 fn new_analog_output(port: u8) -> Result<AnalogOutput, GPIOError>;
116
117 /// Returns a new digital input for the specified port.
118 ///
119 /// # Errors
120 /// - [`GPIOError::PortNotAvailable`] if the port is not available for digital in use
121 /// - [`GPIOError::PortInUse`] if the port is already in use
122 fn new_digital_input(port: u8) -> Result<DigitalInput, GPIOError>;
123
124 /// Returns a new digital output for the specified port.
125 ///
126 /// # Errors
127 /// - [`GPIOError::PortNotAvailable`] if the port is not available for digital out use
128 /// - [`GPIOError::PortInUse`] if the port is already in use
129 fn new_digital_output(port: u8) -> Result<DigitalOutput, GPIOError>;
130
131 //
132 // # Checks
133 //
134
135 /// Checks if the specified port is available for analog use.
136 /// Has a sensible default implementation but can be overriden if needed.
137 #[allow(clippy::missing_errors_doc)]
138 fn analog_available(port: u8) -> Result<(), GPIOError> {
139 if Self::ANALOG_PORTS.contains(&port) {
140 Ok(())
141 } else {
142 Err(GPIOError::PortNotAvailable(port))
143 }
144 }
145
146 /// Checks if the specified port is available for digital use.
147 /// Has a sensible default implementation but can be overriden if needed.
148 #[allow(clippy::missing_errors_doc)]
149 fn digital_available(port: u8) -> Result<(), GPIOError> {
150 if Self::DIGITAL_PORTS.contains(&port) {
151 Ok(())
152 } else {
153 Err(GPIOError::PortNotAvailable(port))
154 }
155 }
156
157 /// Checks if the specified port is available for PWM use.
158 /// Has a sensible default implementation but can be overriden if needed.
159 #[allow(clippy::missing_errors_doc)]
160 fn pwm_available(port: u8) -> Result<(), GPIOError> {
161 if Self::PWM_PORTS.contains(&port) {
162 Ok(())
163 } else {
164 Err(GPIOError::PortNotAvailable(port))
165 }
166 }
167}
168
169/// A platform specific GPIO driver extension for simulation.
170pub trait SimGPIODriver: GPIODriver {
171 /// Returns the other end of the analog input port as an [`AnalogOutput`].
172 fn sim_analog_input(port: u8) -> AnalogOutput;
173
174 /// Returns the other end of the analog output port as an [`AnalogInput`].
175 fn sim_analog_output(port: u8) -> AnalogInput;
176
177 /// Returns the other end of the digital input port as an [`DigitalOutput`].
178 fn sim_digital_input(port: u8) -> DigitalOutput;
179
180 /// Returns the other end of the digital output port as an [`DigitalInput`].
181 fn sim_digital_output(port: u8) -> DigitalInput;
182}
183
184/// A struct that defines a platform specific GPIO driver for user code.
185#[derive(Debug, Clone, Copy)]
186pub struct GPIOVTable {
187 pub(crate) new_analog_input: fn(u8) -> Result<AnalogInput, GPIOError>,
188 pub(crate) new_analog_output: fn(u8) -> Result<AnalogOutput, GPIOError>,
189 pub(crate) new_digital_input: fn(u8) -> Result<DigitalInput, GPIOError>,
190 pub(crate) new_digital_output: fn(u8) -> Result<DigitalOutput, GPIOError>,
191 //sim
192 pub(crate) sim_analog_input: Option<fn(u8) -> AnalogOutput>,
193 pub(crate) sim_analog_output: Option<fn(u8) -> AnalogInput>,
194 pub(crate) sim_digital_input: Option<fn(u8) -> DigitalOutput>,
195 pub(crate) sim_digital_output: Option<fn(u8) -> DigitalInput>,
196}
197
198impl GPIOVTable {
199 pub(crate) fn from_driver<T: GPIODriver>() -> Self {
200 assert!(
201 std::mem::size_of::<T>() == 0,
202 "GPIO Driver must be zero sized"
203 );
204 Self {
205 new_analog_input: T::new_analog_input,
206 new_analog_output: T::new_analog_output,
207 new_digital_input: T::new_digital_input,
208 new_digital_output: T::new_digital_output,
209 sim_analog_input: None,
210 sim_analog_output: None,
211 sim_digital_input: None,
212 sim_digital_output: None,
213 }
214 }
215
216 pub(crate) fn from_sim_driver<T: SimGPIODriver>() -> Self {
217 assert!(
218 std::mem::size_of::<T>() == 0,
219 "GPIO Driver must be zero sized"
220 );
221 Self {
222 new_analog_input: T::new_analog_input,
223 new_analog_output: T::new_analog_output,
224 new_digital_input: T::new_digital_input,
225 new_digital_output: T::new_digital_output,
226 sim_analog_input: Some(T::sim_analog_input),
227 sim_analog_output: Some(T::sim_analog_output),
228 sim_digital_input: Some(T::sim_digital_input),
229 sim_digital_output: Some(T::sim_digital_output),
230 }
231 }
232
233 /// Returns a new analog input for the specified port.
234 ///
235 /// # Errors
236 /// - [`GPIOError::PortNotAvailable`] if the port is not available for analog use
237 /// - [`GPIOError::PortInUse`] if the port is already in use
238 pub fn new_analog_input(&self, port: u8) -> Result<AnalogInput, GPIOError> {
239 (self.new_analog_input)(port)
240 }
241
242 /// Returns a new analog output for the specified port.
243 ///
244 /// # Errors
245 /// - [`GPIOError::PortNotAvailable`] if the port is not available for analog use
246 /// - [`GPIOError::PortInUse`] if the port is already in use
247 pub fn new_analog_output(&self, port: u8) -> Result<AnalogOutput, GPIOError> {
248 (self.new_analog_output)(port)
249 }
250
251 /// Returns a new digital input for the specified port.
252 ///
253 /// # Errors
254 /// - [`GPIOError::PortNotAvailable`] if the port is not available for digital in use
255 /// - [`GPIOError::PortInUse`] if the port is already in use
256 pub fn new_digital_input(&self, port: u8) -> Result<DigitalInput, GPIOError> {
257 (self.new_digital_input)(port)
258 }
259
260 /// Returns a new digital output for the specified port.
261 ///
262 /// # Errors
263 /// - [`GPIOError::PortNotAvailable`] if the port is not available for digital out use
264 /// - [`GPIOError::PortInUse`] if the port is already in use
265 pub fn new_digital_output(&self, port: u8) -> Result<DigitalOutput, GPIOError> {
266 (self.new_digital_output)(port)
267 }
268
269 /// Returns the other end of the analog input port as an [`AnalogOutput`] if initialized as sim.
270 /// If not sim this function will return `Err(NotSimError)`
271 #[allow(clippy::missing_errors_doc)]
272 pub fn sim_analog_input(&self, port: u8) -> Result<AnalogOutput, NotSimError> {
273 self.sim_analog_input
274 .map_or(Err(NotSimError), |f| Ok((f)(port)))
275 }
276
277 /// Returns the other end of the analog output port as an [`AnalogInput`] if initialized as sim.
278 /// If not sim this function will return `Err(NotSimError)`
279 #[allow(clippy::missing_errors_doc)]
280 pub fn sim_analog_output(&self, port: u8) -> Result<AnalogInput, NotSimError> {
281 self.sim_analog_output
282 .map_or(Err(NotSimError), |f| Ok((f)(port)))
283 }
284
285 /// Returns the other end of the digital input port as an [`DigitalOutput`] if initialized as sim.
286 /// If not sim this function will return `Err(NotSimError)`
287 #[allow(clippy::missing_errors_doc)]
288 pub fn sim_digital_input(&self, port: u8) -> Result<DigitalOutput, NotSimError> {
289 self.sim_digital_input
290 .map_or(Err(NotSimError), |f| Ok((f)(port)))
291 }
292
293 /// Returns the other end of the digital output port as an [`DigitalInput`] if initialized as sim.
294 /// If not sim this function will return `Err(NotSimError)`
295 #[allow(clippy::missing_errors_doc)]
296 pub fn sim_digital_output(&self, port: u8) -> Result<DigitalInput, NotSimError> {
297 self.sim_digital_output
298 .map_or(Err(NotSimError), |f| Ok((f)(port)))
299 }
300}