using Unosquare.RaspberryIO.Native; using Unosquare.Swan; using System; using System.Linq; using System.Threading.Tasks; namespace Unosquare.RaspberryIO.Gpio { /// /// 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 Int32 m_PwmRegister; private PwmMode m_PwmMode = PwmMode.Balanced; private UInt32 m_PwmRange = 1024; private Int32 m_PwmClockDivisor = 1; private Int32 m_SoftPwmValue = -1; private Int32 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, Int32 headerPinNumber) { this.PinNumber = (Int32)wiringPiPinNumber; this.WiringPiPinNumber = wiringPiPinNumber; this.BcmPinNumber = GpioController.WiringPiToBcmPinNumber((Int32)wiringPiPinNumber); this.HeaderPinNumber = headerPinNumber; this.Header = (this.PinNumber >= 17 && this.PinNumber <= 20) ? GpioHeader.P5 : GpioHeader.P1; } #endregion #region Pin Properties /// /// Gets or sets the Wiring Pi pin number as an integer. /// public Int32 PinNumber { get; } /// /// Gets the WiringPi Pin number /// public WiringPiPin WiringPiPinNumber { get; } /// /// Gets the BCM chip (hardware) pin number. /// public Int32 BcmPinNumber { get; } /// /// Gets or the physical header (physical board) pin number. /// public Int32 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 => this.m_PinMode; set { lock(this._syncLock) { GpioPinDriveMode mode = value; if(mode == GpioPinDriveMode.GpioClock && this.Capabilities.Contains(PinCapability.GPCLK) == false || mode == GpioPinDriveMode.PwmOutput && this.Capabilities.Contains(PinCapability.PWM) == false || mode == GpioPinDriveMode.Input && this.Capabilities.Contains(PinCapability.GP) == false || mode == GpioPinDriveMode.Output && this.Capabilities.Contains(PinCapability.GP) == false) { throw new NotSupportedException( $"Pin {this.WiringPiPinNumber} '{this.Name}' does not support mode '{mode}'. Pin capabilities are limited to: {String.Join(", ", this.Capabilities)}"); } WiringPi.PinMode(this.PinNumber, (Int32)mode); this.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 => this.PinMode == GpioPinDriveMode.Input ? this.m_ResistorPullMode : GpioPinResistorPullMode.Off; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Input) { this.m_ResistorPullMode = GpioPinResistorPullMode.Off; throw new InvalidOperationException( $"Unable to set the {nameof(this.InputPullMode)} for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Setting the {nameof(this.InputPullMode)} is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } WiringPi.PullUpDnControl(this.PinNumber, (Int32)value); this.m_ResistorPullMode = value; } } } /// /// Gets or sets the PWM register. Values should be between 0 and 1024 /// /// /// The PWM register. /// public Int32 PwmRegister { get => this.m_PwmRegister; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.PwmOutput) { this.m_PwmRegister = 0; throw new InvalidOperationException( $"Unable to write PWM register for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Writing the PWM register is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } Int32 val = value.Clamp(0, 1024); WiringPi.PwmWrite(this.PinNumber, val); this.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 => this.PinMode == GpioPinDriveMode.PwmOutput ? this.m_PwmMode : PwmMode.Balanced; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.PwmOutput) { this.m_PwmMode = PwmMode.Balanced; throw new InvalidOperationException( $"Unable to set PWM mode for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Setting the PWM mode is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetMode((Int32)value); this.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 UInt32 PwmRange { get => this.PinMode == GpioPinDriveMode.PwmOutput ? this.m_PwmRange : 0; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.PwmOutput) { this.m_PwmRange = 1024; throw new InvalidOperationException( $"Unable to set PWM range for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Setting the PWM range is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetRange(value); this.m_PwmRange = value; } } } /// /// Gets or sets the PWM clock divisor. /// /// /// The PWM clock divisor. /// /// When pin mode is not set to PWM output public Int32 PwmClockDivisor { get => this.PinMode == GpioPinDriveMode.PwmOutput ? this.m_PwmClockDivisor : 0; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.PwmOutput) { this.m_PwmClockDivisor = 1; throw new InvalidOperationException( $"Unable to set PWM range for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Setting the PWM range is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.PwmOutput}"); } WiringPi.PwmSetClock(value); this.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 Boolean IsInSoftToneMode => this.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 Int32 SoftToneFrequency { get => this.m_SoftToneFrequency; set { lock(this._syncLock) { if(this.IsInSoftToneMode == false) { Int32 setupResult = WiringPi.SoftToneCreate(this.PinNumber); if(setupResult != 0) { throw new InvalidOperationException( $"Unable to initialize soft tone on pin {this.PinNumber}. Error Code: {setupResult}"); } } WiringPi.SoftToneWrite(this.PinNumber, value); this.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 Boolean IsInSoftPwmMode => this.m_SoftPwmValue >= 0; /// /// Gets or sets the software PWM value on the pin. /// /// /// The soft PWM value. /// /// StartSoftPwm public Int32 SoftPwmValue { get => this.m_SoftPwmValue; set { lock(this._syncLock) { if(this.IsInSoftPwmMode && value >= 0) { WiringPi.SoftPwmWrite(this.PinNumber, value); this.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 Int32 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(Int32 value, Int32 range) { lock(this._syncLock) { if(this.Capabilities.Contains(PinCapability.GP) == false) { throw new NotSupportedException($"Pin {this.PinNumber} does not support software PWM"); } if(this.IsInSoftPwmMode) { throw new InvalidOperationException($"{nameof(StartSoftPwm)} has already been called."); } Int32 startResult = WiringPi.SoftPwmCreate(this.PinNumber, value, range); if(startResult == 0) { this.m_SoftPwmValue = value; this.SoftPwmRange = range; } else { throw new InvalidOperationException( $"Could not start software based PWM on pin {this.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(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to write to pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Writes are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.DigitalWrite(this.PinNumber, (Int32)value); } } /// /// Writes the value asynchronously. /// /// The value. /// The awaitable task public Task WriteAsync(GpioPinValue value) => Task.Run(() => this.Write(value)); /// /// Writes the specified bit value. /// This method performs a digital write /// /// if set to true [value]. public void Write(Boolean value) => this.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(Boolean value) => Task.Run(() => this.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(Int32 value) => this.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(Int32 value) => Task.Run(() => this.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(Int32 value) { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to write to pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Writes are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.AnalogWrite(this.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(Int32 value) => Task.Run(() => this.WriteLevel(value)); #endregion #region Input Mode (Read) Members /// /// Wait for specific pin status /// /// status to check /// timeout to reach status /// true/false public Boolean WaitForValue(GpioPinValue status, Int32 timeOutMillisecond) { if(this.PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to read from pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Reads are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } HighResolutionTimer hrt = new HighResolutionTimer(); hrt.Start(); do { if(this.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 Boolean Read() { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Input && this.PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to read from pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Reads are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}"); } return WiringPi.DigitalRead(this.PinNumber) != 0; } } /// /// Reads the digital value on the pin as a boolean value. /// /// The state of the pin public Task ReadAsync() => Task.Run(() => this.Read()); /// /// Reads the digital value on the pin as a High or Low value. /// /// The state of the pin public GpioPinValue ReadValue() => this.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(() => this.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 Int32 ReadLevel() { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to read from pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Reads are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } return WiringPi.AnalogRead(this.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(() => this.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(this.InterruptCallback != null) { throw new InvalidOperationException("An interrupt callback was already registered."); } if(this.PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to {nameof(RegisterInterruptCallback)} for pin {this.PinNumber} because operating mode is {this.PinMode}." + $" Calling {nameof(RegisterInterruptCallback)} is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } lock(this._syncLock) { Int32 registerResult = WiringPi.WiringPiISR(this.PinNumber, (Int32)edgeDetection, callback); if(registerResult == 0) { this.InterruptEdgeDetection = edgeDetection; this.InterruptCallback = callback; } else { HardwareException.Throw(nameof(GpioPin), nameof(RegisterInterruptCallback)); } } } #endregion } }