1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! Tools for interfacing with GPIO on the uC
//!
//! # See Also
//! - [Digital Pins - Arduino Reference](https://www.arduino.cc/en/Tutorial/DigitalPins)

/// The modes that a pin can be configured in
///
/// # See Also
/// - [Digital Pins - Arduino Reference](https://www.arduino.cc/en/Tutorial/DigitalPins)
#[repr(u8)]
pub enum PinMode {
    /// Pins configured this way are said to be in a high-impedance state.
    /// Input pins make extremely small demands on the circuit that they are
    /// sampling, equivalent to a series resistor of 100 megohm in front of
    /// the pin.
    ///
    /// This also means however, that pins configured as pinMode(pin, INPUT)
    /// with nothing connected to them, or with wires connected to them that
    /// are not connected to other circuits, will report seemingly random
    /// changes in pin state, picking up electrical noise from the environment,
    /// or capacitively coupling the state of a nearby pin.
    ///
    /// # Pullup Resistors
    /// Often it is useful to steer an input pin to a known state if no input
    /// is present. This can be done by adding a pullup resistor (to +5V), or
    /// a pulldown resistor (resistor to ground) on the input. A 10K resistor
    /// is a good value for a pullup or pulldown resistor.
    Input = 0,
    /// Pins configured as OUTPUT with pinMode() are said to be in a low-impedance
    /// state. This means that they can provide a substantial amount of current to
    /// other circuits. Atmega pins can source (provide positive current) or sink
    /// (provide negative current) up to 40 mA (milliamps) of current to other
    /// devices/circuits. This is enough current to brightly light up an LED
    /// (don't forget the series resistor), or run many sensors, for example,
    /// but not enough current to run most relays, solenoids, or motors.
    ///
    /// Short circuits on Arduino pins, or attempting to run high current devices
    /// from them, can damage or destroy the output transistors in the pin, or
    /// damage the entire Atmega chip. Often this will result in a "dead" pin in
    /// the microcontroller but the remaining chip will still function adequately.
    /// For this reason it is a good idea to connect OUTPUT pins to other devices
    /// with 470Ω or 1k resistors, unless maximum current draw from the pins is
    /// required for a particular application.
    Output = 1,
    /// There are 20K pullup resistors built into the Atmega chip that can be
    /// accessed from software. These built-in pullup resistors are accessed
    /// by setting the pinMode() as INPUT_PULLUP. This effectively inverts the
    /// behavior of the INPUT mode, where HIGH means the sensor is off, and
    /// LOW means the sensor is on.
    ///
    /// The value of this pullup depends on the microcontroller used. On most
    /// AVR-based boards, the value is guaranteed to be between 20kΩ and 50kΩ.
    /// On the Arduino Due, it is between 50kΩ and 150kΩ. For the exact value,
    /// consult the datasheet of the microcontroller on your board.
    ///
    /// When connecting a sensor to a pin configured with INPUT_PULLUP, the
    /// other end should be connected to ground. In the case of a simple switch,
    /// this causes the pin to read HIGH when the switch is open, and LOW when
    /// the switch is pressed.
    ///
    /// The pullup resistors provide enough current to dimly light an LED
    /// connected to a pin that has been configured as an input. If LEDs in a
    /// project seem to be working, but very dimly, this is likely what is going
    /// on.
    InputPullUp = 2,
    /// See the description of InputPullUp
    InputPullDown = 3,
    /// Open drain outputs require a pull-up resistor for the output to be able to
    /// properly “output high”. The pull-up resistor is connected between the output
    /// pin and the output voltage (Vcc in the image above) that is desired for a high
    /// state. When the internal N-FET of the IC is off, R “pulls up” the output pin to
    /// Vcc and at that point only a very small amount of “leakage current” should flow
    /// through the N-FET transistor. When the internal N-FET of the IC is on, it “pulls
    /// down” the output pin to very nearly GND and the current flow is set by Ohm’s Law
    /// (I = Vcc/R).
    OutputOpenDrain = 4,
    /// Disable the pin (High Impedance)
    InputDisable = 5,
}

/// A container for a pin number that has been verified to exist
/// This method was chosen to limit the use of unsafe when dealing with pins
///
/// # See Also
/// - [Digital Pins - Arduino Reference](https://www.arduino.cc/en/Tutorial/DigitalPins)
pub struct Pin(u8);

impl Pin {
    /// Create a new Pin to drive a GPIO
    ///
    /// # Safety
    /// The caller must verify that the pin provided is a
    /// valid pin on the board they are using. Passing an
    /// invalid pin number will result in undefined behavior
    #[unsafe_fn]
    pub const fn new(pin: u8) -> Pin {
        Pin(pin)
    }

    /// Configures the specified pin to behave either as an input or an output.
    /// See the [Digital Pins] page for details on the functionality of the pins.
    ///
    /// As of Arduino 1.0.1, it is possible to enable the internal pullup resistors
    /// with the mode INPUT_PULLUP. Additionally, the INPUT mode explicitly disables
    /// the internal pullups.
    ///
    /// # Notes and Warnings
    /// The analog input pins can be used as digital pins, referred to as A0, A1, etc.
    ///
    /// # See Also
    /// - [pinMode - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/digital-io/pinmode/)
    ///
    /// [Digital Pins]: https://www.arduino.cc/en/Tutorial/DigitalPins
    pub fn mode(&self, mode: PinMode) {
        extern "C" {
            fn pinMode(pin: u8, mode: u8);
        }

        unsafe { pinMode(self.0, mode as _) }
    }

    /// Set the pins output to high. A proxy for `digital_write(true)`
    pub fn set_high(&self) {
        self.digital_write(true)
    }

    /// Set the pins output to low. A proxy for `digital_write(false)`
    pub fn set_low(&self) {
        self.digital_write(false)
    }

    /// Set the pins output to the opposite of what it currently is
    pub fn toggle(&self) {
        extern "C" {
            fn digitalToggle(pin: u8);
        }

        unsafe { digitalToggle(self.0) }
    }

    /// Write a HIGH (true) or a LOW (false) value to a digital pin.
    ///
    /// If the pin has been configured as an OUTPUT with pinMode(),
    /// its voltage will be set to the corresponding value: 5V
    /// (or 3.3V on 3.3V boards) for HIGH, 0V (ground) for LOW.
    ///
    /// If the pin is configured as an INPUT, `digital_write()` will enable
    /// (HIGH) or disable (LOW) the internal pullup on the input pin.
    /// It is recommended to set the `mode()` to `PinMode::InputPullup` to enable
    /// the internal pull-up resistor. See the Digital Pins tutorial for
    /// more information.
    ///
    /// If you do not set the `mode()` to OUTPUT, and connect an LED to
    /// a pin, when calling `digital_write(true)`, the LED may appear dim.
    /// Without explicitly setting `mode()`, `digital_write()` will have
    /// enabled the internal pull-up resistor, which acts like a large
    /// current-limiting resistor.
    ///
    /// # See Also
    /// - [digitalWrite() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalwrite/)
    pub fn digital_write(&self, value: bool) {
        extern "C" {
            fn digitalWrite(pin: u8, value: u8);
        }

        unsafe {
            digitalWrite(self.0, if value { 1 } else { 0 });
        }
    }

    /// Reads the value from a specified digital pin, either HIGH or LOW.
    ///
    /// Notes and Warnings
    ///
    /// If the pin isn’t connected to anything, digitalRead() can return either
    /// HIGH or LOW (and this can change randomly).
    ///
    /// The analog input pins can be used as digital pins, referred to as A0, A1,
    /// etc. The exception is the Arduino Nano, Pro Mini, and Mini’s A6 and A7 pins,
    /// which can only be used as analog inputs.
    /// 
    /// # See Also
    /// - [digitalRead() - Arduino Reference](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalread/)
    pub fn digital_read(&self) -> bool {
        extern "C" {
            fn digitalRead(pin: u8) -> u8;
        }

        unsafe {
            digitalRead(self.0) == 0
        }
    }
}

impl Into<u8> for Pin {
    fn into(self) -> u8 {
        self.0
    }
}

impl Into<u8> for &Pin {
    fn into(self) -> u8 {
        self.0
    }
}

/// The builtin LED, normally on pin 13
/// 
/// **Requires the feature `led`**
///
/// # Safety
/// On boards with the led not on pin 13, this will not work,
/// and could cause undefined behavior
#[cfg(feature = "led")]
pub const LED_BUILTIN: Pin = unsafe { Pin::new(13) };