namespace Unosquare.RaspberryIO.Gpio { using Native; using Swan; using System; using System.Linq; using System.Threading.Tasks; /// /// Represents a GPIO Pin, its location and its capabilities. /// Full pin reference available here: /// http://pinout.xyz/pinout/pin31_gpio6 and http://wiringpi.com/pins/ /// public sealed partial class GpioPin { #region Property Backing private readonly object _syncLock = new object(); private GpioPinDriveMode m_PinMode; private GpioPinResistorPullMode m_ResistorPullMode; private int m_PwmRegister; private PwmMode m_PwmMode = PwmMode.Balanced; private uint m_PwmRange = 1024; private int m_PwmClockDivisor = 1; private int m_SoftPwmValue = -1; private int m_SoftToneFrequency = -1; #endregion #region Constructor /// /// Initializes a new instance of the class. /// /// The wiring pi pin number. /// The header pin number. private GpioPin(WiringPiPin wiringPiPinNumber, int headerPinNumber) { PinNumber = (int)wiringPiPinNumber; WiringPiPinNumber = wiringPiPinNumber; BcmPinNumber = GpioController.WiringPiToBcmPinNumber((int)wiringPiPinNumber); HeaderPinNumber = headerPinNumber; Header = (PinNumber >= 17 && PinNumber <= 20) ? GpioHeader.P5 : GpioHeader.P1; } #endregion #region Pin Properties /// /// Gets or sets the Wiring Pi pin number as an integer. /// public int PinNumber { get; } /// /// Gets the WiringPi Pin number /// public WiringPiPin WiringPiPinNumber { get; } /// /// Gets the BCM chip (hardware) pin number. /// public int BcmPinNumber { get; } /// /// Gets or the physical header (physical board) pin number. /// public int HeaderPinNumber { get; } /// /// Gets the pin's header (physical board) location. /// public GpioHeader Header { get; } /// /// Gets the friendly name of the pin. /// public string Name { get; private set; } /// /// Gets the hardware mode capabilities of this pin. /// public PinCapability[] Capabilities { get; private set; } #endregion #region Hardware-Specific Properties /// /// Gets or sets the pin operating mode. /// /// /// The pin mode. /// /// Thrown when a pin does not support the given operation mode. public GpioPinDriveMode PinMode { get => m_PinMode; set { lock (_syncLock) { var mode = value; if ((mode == GpioPinDriveMode.GpioClock && Capabilities.Contains(PinCapability.GPCLK) == false) || (mode == GpioPinDriveMode.PwmOutput && Capabilities.Contains(PinCapability.PWM) == false) || (mode == GpioPinDriveMode.Input && Capabilities.Contains(PinCapability.GP) == false) || (mode == GpioPinDriveMode.Output && Capabilities.Contains(PinCapability.GP) == false)) { throw new NotSupportedException( $"Pin {WiringPiPinNumber} '{Name}' does not support mode '{mode}'. Pin capabilities are limited to: {string.Join(", ", Capabilities)}"); } WiringPi.PinMode(PinNumber, (int)mode); m_PinMode = mode; } } } /// /// Gets the interrupt callback. Returns null if no interrupt /// has been registered. /// public InterruptServiceRoutineCallback InterruptCallback { get; private set; } /// /// Gets the interrupt edge detection mode. /// public EdgeDetection InterruptEdgeDetection { get; private set; } = EdgeDetection.ExternalSetup; #endregion #region Hardware PWM Members /// /// This sets or gets the pull-up or pull-down resistor mode on the pin, which should be set as an input. /// Unlike the Arduino, the BCM2835 has both pull-up an down internal resistors. /// The parameter pud should be; PUD_OFF, (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) /// The internal pull up/down resistors have a value of approximately 50KΩ on the Raspberry Pi. /// public GpioPinResistorPullMode InputPullMode { get => PinMode == GpioPinDriveMode.Input ? m_ResistorPullMode : GpioPinResistorPullMode.Off; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Input) { m_ResistorPullMode = GpioPinResistorPullMode.Off; throw new InvalidOperationException( $"Unable to set the {nameof(InputPullMode)} for pin {PinNumber} because operating mode is {PinMode}." + $" Setting the {nameof(InputPullMode)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } WiringPi.PullUpDnControl(PinNumber, (int)value); m_ResistorPullMode = value; } } } /// /// Gets or sets the PWM register. Values should be between 0 and 1024 /// /// /// The PWM register. /// public int PwmRegister { get => m_PwmRegister; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.PwmOutput) { m_PwmRegister = 0; throw new InvalidOperationException( $"Unable to write PWM register for pin {PinNumber} because operating mode is {PinMode}." + $" Writing the PWM register is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } var val = value.Clamp(0, 1024); WiringPi.PwmWrite(PinNumber, val); m_PwmRegister = val; } } } /// /// The PWM generator can run in 2 modes – “balanced” and “mark:space”. The mark:space mode is traditional, /// however the default mode in the Pi is “balanced”. /// /// /// The PWM mode. /// /// When pin mode is not set a Pwn output public PwmMode PwmMode { get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmMode : PwmMode.Balanced; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.PwmOutput) { m_PwmMode = PwmMode.Balanced; throw new InvalidOperationException( $"Unable to set PWM mode for pin {PinNumber} because operating mode is {PinMode}." + $" Setting the PWM mode is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetMode((int)value); m_PwmMode = value; } } } /// /// This sets the range register in the PWM generator. The default is 1024. /// /// /// The PWM range. /// /// When pin mode is not set to PWM output public uint PwmRange { get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmRange : 0; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.PwmOutput) { m_PwmRange = 1024; throw new InvalidOperationException( $"Unable to set PWM range for pin {PinNumber} because operating mode is {PinMode}." + $" Setting the PWM range is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetRange(value); m_PwmRange = value; } } } /// /// Gets or sets the PWM clock divisor. /// /// /// The PWM clock divisor. /// /// When pin mode is not set to PWM output public int PwmClockDivisor { get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmClockDivisor : 0; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.PwmOutput) { m_PwmClockDivisor = 1; throw new InvalidOperationException( $"Unable to set PWM range for pin {PinNumber} because operating mode is {PinMode}." + $" Setting the PWM range is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetClock(value); m_PwmClockDivisor = value; } } } #endregion #region Software Tone Members /// /// Gets a value indicating whether this instance is in software based tone generator mode. /// /// /// true if this instance is in soft tone mode; otherwise, false. /// public bool IsInSoftToneMode => m_SoftToneFrequency >= 0; /// /// Gets or sets the soft tone frequency. 0 to 5000 Hz is typical /// /// /// The soft tone frequency. /// /// When soft tones cannot be initialized on the pin public int SoftToneFrequency { get => m_SoftToneFrequency; set { lock (_syncLock) { if (IsInSoftToneMode == false) { var setupResult = WiringPi.SoftToneCreate(PinNumber); if (setupResult != 0) { throw new InvalidOperationException( $"Unable to initialize soft tone on pin {PinNumber}. Error Code: {setupResult}"); } } WiringPi.SoftToneWrite(PinNumber, value); m_SoftToneFrequency = value; } } } #endregion #region Software PWM Members /// /// Gets a value indicating whether this pin is in software based PWM mode. /// /// /// true if this instance is in soft PWM mode; otherwise, false. /// public bool IsInSoftPwmMode => m_SoftPwmValue >= 0; /// /// Gets or sets the software PWM value on the pin. /// /// /// The soft PWM value. /// /// StartSoftPwm public int SoftPwmValue { get => m_SoftPwmValue; set { lock (_syncLock) { if (IsInSoftPwmMode && value >= 0) { WiringPi.SoftPwmWrite(PinNumber, value); m_SoftPwmValue = value; } else { throw new InvalidOperationException($"Software PWM requires a call to {nameof(StartSoftPwm)}."); } } } } /// /// Gets the software PWM range used upon starting the PWM. /// public int SoftPwmRange { get; private set; } = -1; /// /// Starts the software based PWM on this pin. /// /// The value. /// The range. /// When the pin does not suppoert PWM /// StartSoftPwm /// or public void StartSoftPwm(int value, int range) { lock (_syncLock) { if (Capabilities.Contains(PinCapability.GP) == false) throw new NotSupportedException($"Pin {PinNumber} does not support software PWM"); if (IsInSoftPwmMode) throw new InvalidOperationException($"{nameof(StartSoftPwm)} has already been called."); var startResult = WiringPi.SoftPwmCreate(PinNumber, value, range); if (startResult == 0) { m_SoftPwmValue = value; SoftPwmRange = range; } else { throw new InvalidOperationException( $"Could not start software based PWM on pin {PinNumber}. Error code: {startResult}"); } } } #endregion #region Output Mode (Write) Members /// /// Writes the specified pin value. /// This method performs a digital write /// /// The value. public void Write(GpioPinValue value) { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to write to pin {PinNumber} because operating mode is {PinMode}." + $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.DigitalWrite(PinNumber, (int)value); } } /// /// Writes the value asynchronously. /// /// The value. /// The awaitable task public Task WriteAsync(GpioPinValue value) => Task.Run(() => { Write(value); }); /// /// Writes the specified bit value. /// This method performs a digital write /// /// if set to true [value]. public void Write(bool value) => Write(value ? GpioPinValue.High : GpioPinValue.Low); /// /// Writes the specified bit value. /// This method performs a digital write /// /// The value. /// /// The awaitable task /// public Task WriteAsync(bool value) => Task.Run(() => { Write(value); }); /// /// Writes the specified value. 0 for low, any other value for high /// This method performs a digital write /// /// The value. public void Write(int value) => Write(value != 0 ? GpioPinValue.High : GpioPinValue.Low); /// /// Writes the specified value. 0 for low, any other value for high /// This method performs a digital write /// /// The value. /// The awaitable task public Task WriteAsync(int value) => Task.Run(() => { Write(value); }); /// /// Writes the specified value as an analog level. /// You will need to register additional analog modules to enable this function for devices such as the Gertboard. /// /// The value. public void WriteLevel(int value) { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to write to pin {PinNumber} because operating mode is {PinMode}." + $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.AnalogWrite(PinNumber, value); } } /// /// Writes the specified value as an analog level. /// You will need to register additional analog modules to enable this function for devices such as the Gertboard. /// /// The value. /// The awaitable task public Task WriteLevelAsync(int value) => Task.Run(() => { WriteLevel(value); }); #endregion #region Input Mode (Read) Members /// /// Wait for specific pin status /// /// status to check /// timeout to reach status /// true/false public bool WaitForValue(GpioPinValue status, int timeOutMillisecond) { if (PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to read from pin {PinNumber} because operating mode is {PinMode}." + $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } var hrt = new HighResolutionTimer(); hrt.Start(); do { if (ReadValue() == status) return true; Pi.Timing.SleepMicroseconds(101); // 101 uses nanosleep as opposed to a loop. } while (hrt.ElapsedMilliseconds <= timeOutMillisecond); return false; } /// /// Reads the digital value on the pin as a boolean value. /// /// The state of the pin public bool Read() { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Input && PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to read from pin {PinNumber} because operating mode is {PinMode}." + $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}"); } return WiringPi.DigitalRead(PinNumber) != 0; } } /// /// Reads the digital value on the pin as a boolean value. /// /// The state of the pin public Task ReadAsync() => Task.Run(() => Read()); /// /// Reads the digital value on the pin as a High or Low value. /// /// The state of the pin public GpioPinValue ReadValue() => Read() ? GpioPinValue.High : GpioPinValue.Low; /// /// Reads the digital value on the pin as a High or Low value. /// /// The state of the pin public Task ReadValueAsync() => Task.Run(() => ReadValue()); /// /// Reads the analog value on the pin. /// This returns the value read on the supplied analog input pin. You will need to register /// additional analog modules to enable this function for devices such as the Gertboard, /// quick2Wire analog board, etc. /// /// The analog level /// When the pin mode is not configured as an input. public int ReadLevel() { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to read from pin {PinNumber} because operating mode is {PinMode}." + $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } return WiringPi.AnalogRead(PinNumber); } } /// /// Reads the analog value on the pin. /// This returns the value read on the supplied analog input pin. You will need to register /// additional analog modules to enable this function for devices such as the Gertboard, /// quick2Wire analog board, etc. /// /// The analog level public Task ReadLevelAsync() => Task.Run(() => ReadLevel()); #endregion #region Interrupts /// /// Registers the interrupt callback on the pin. Pin mode has to be set to Input. /// /// The edge detection. /// The callback. /// callback /// /// An interrupt callback was already registered. /// or /// RegisterInterruptCallback /// public void RegisterInterruptCallback(EdgeDetection edgeDetection, InterruptServiceRoutineCallback callback) { if (callback == null) throw new ArgumentException($"{nameof(callback)} cannot be null"); if (InterruptCallback != null) throw new InvalidOperationException("An interrupt callback was already registered."); if (PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to {nameof(RegisterInterruptCallback)} for pin {PinNumber} because operating mode is {PinMode}." + $" Calling {nameof(RegisterInterruptCallback)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } lock (_syncLock) { var registerResult = WiringPi.WiringPiISR(PinNumber, (int)edgeDetection, callback); if (registerResult == 0) { InterruptEdgeDetection = edgeDetection; InterruptCallback = callback; } else { HardwareException.Throw(nameof(GpioPin), nameof(RegisterInterruptCallback)); } } } #endregion } }