using System; using System.Threading.Tasks; using Swan.Diagnostics; using Unosquare.RaspberryIO.Abstractions; using Unosquare.RaspberryIO.Abstractions.Native; using Definitions = Unosquare.RaspberryIO.Abstractions.Definitions; namespace Unosquare.WiringPi { /// /// 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 : IGpioPin { #region Property Backing private static readonly Int32[] GpioToWiringPi; private static readonly Int32[] GpioToWiringPiR1 = { 8, 9, -1, -1, 7, -1, -1, 11, 10, 13, 12, 14, -1, -1, 15, 16, -1, 0, 1, -1, -1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1 }; private static readonly Int32[] GpioToWiringPiR2 = { 30, 31, 8, 9, 7, 21, 22, 11, 10, 13, 12, 14, 26, 23, 15, 16, 27, 0, 1, 24, 28, 29, 3, 4, 5, 6, 25, 2, 17, 18, 19, 20 }; private readonly Object _syncLock = new Object(); private GpioPinDriveMode _pinMode; private GpioPinResistorPullMode _resistorPullMode; private Int32 _pwmRegister; private PwmMode _pwmMode = PwmMode.Balanced; private UInt32 _pwmRange = 1024; private Int32 _pwmClockDivisor = 1; private Int32 _softPwmValue = -1; private Int32 _softToneFrequency = -1; #endregion #region Constructor static GpioPin() => GpioToWiringPi = SystemInfo.GetBoardRevision() == BoardRevision.Rev1 ? GpioToWiringPiR1 : GpioToWiringPiR2; /// /// Initializes a new instance of the class. /// /// The BCM pin number. private GpioPin(BcmPin bcmPinNumber) { this.BcmPin = bcmPinNumber; this.BcmPinNumber = (Int32)bcmPinNumber; this.WiringPiPinNumber = BcmToWiringPiPinNumber(bcmPinNumber); this.PhysicalPinNumber = Definitions.BcmToPhysicalPinNumber(SystemInfo.GetBoardRevision(), bcmPinNumber); this.Header = (this.BcmPinNumber >= 28 && this.BcmPinNumber <= 31) ? GpioHeader.P5 : GpioHeader.P1; } #endregion #region Pin Properties /// public BcmPin BcmPin { get; } /// public Int32 BcmPinNumber { get; } /// public Int32 PhysicalPinNumber { get; } /// /// Gets the WiringPi Pin number. /// public WiringPiPin WiringPiPinNumber { get; } /// 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; } /// public Boolean Value { get => this.Read(); set => this.Write(value); } #endregion #region Hardware-Specific Properties /// /// Thrown when a pin does not support the given operation mode. public GpioPinDriveMode PinMode { get => this._pinMode; set { lock(this._syncLock) { GpioPinDriveMode mode = value; if(mode == GpioPinDriveMode.GpioClock && !this.HasCapability(PinCapability.GPCLK) || mode == GpioPinDriveMode.PwmOutput && !this.HasCapability(PinCapability.PWM) || mode == GpioPinDriveMode.Input && !this.HasCapability(PinCapability.GP) || mode == GpioPinDriveMode.Output && !this.HasCapability(PinCapability.GP)) { throw new NotSupportedException($"Pin {this.BcmPinNumber} '{this.Name}' does not support mode '{mode}'. Pin capabilities are limited to: {this.Capabilities}"); } Native.WiringPi.PinMode(this.BcmPinNumber, (Int32)mode); this._pinMode = mode; } } } /// /// Gets the interrupt callback. Returns null if no interrupt /// has been registered. /// public Native.InterruptServiceRoutineCallback InterruptCallback { get; private set; } /// /// Gets the interrupt edge detection mode. /// public EdgeDetection InterruptEdgeDetection { get; private set; } /// /// Determines whether the specified capability has capability. /// /// The capability. /// /// true if the specified capability has capability; otherwise, false. /// public Boolean HasCapability(PinCapability capability) => (this.Capabilities & capability) == capability; #endregion #region Hardware PWM Members /// public GpioPinResistorPullMode InputPullMode { get => this.PinMode == GpioPinDriveMode.Input ? this._resistorPullMode : GpioPinResistorPullMode.Off; set { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Input) { this._resistorPullMode = GpioPinResistorPullMode.Off; throw new InvalidOperationException($"Unable to set the {nameof(this.InputPullMode)} for pin {this.BcmPinNumber} because operating mode is {this.PinMode}." + $" Setting the {nameof(this.InputPullMode)} is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } Native.WiringPi.PullUpDnControl(this.BcmPinNumber, (Int32)value); this._resistorPullMode = value; } } } /// /// Gets or sets the PWM register. /// /// /// The PWM register. /// public Int32 PwmRegister { get => this._pwmRegister; set { lock(this._syncLock) { if(!this.HasCapability(PinCapability.PWM)) { this._pwmRegister = 0; throw new NotSupportedException($"Pin {this.BcmPinNumber} '{this.Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {this.Capabilities}"); } Native.WiringPi.PwmWrite(this.BcmPinNumber, value); this._pwmRegister = value; } } } /// /// 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._pwmMode : PwmMode.Balanced; set { lock(this._syncLock) { if(!this.HasCapability(PinCapability.PWM)) { this._pwmMode = PwmMode.Balanced; throw new NotSupportedException($"Pin {this.BcmPinNumber} '{this.Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {this.Capabilities}"); } Native.WiringPi.PwmSetMode((Int32)value); this._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._pwmRange : 0; set { lock(this._syncLock) { if(!this.HasCapability(PinCapability.PWM)) { this._pwmRange = 1024; throw new NotSupportedException($"Pin {this.BcmPinNumber} '{this.Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {this.Capabilities}"); } Native.WiringPi.PwmSetRange(value); this._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._pwmClockDivisor : 0; set { lock(this._syncLock) { if(!this.HasCapability(PinCapability.PWM)) { this._pwmClockDivisor = 1; throw new NotSupportedException($"Pin {this.BcmPinNumber} '{this.Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {this.Capabilities}"); } Native.WiringPi.PwmSetClock(value); this._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._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._softToneFrequency; set { lock(this._syncLock) { if(this.IsInSoftToneMode == false) { Int32 setupResult = Native.WiringPi.SoftToneCreate(this.BcmPinNumber); if(setupResult != 0) { throw new InvalidOperationException($"Unable to initialize soft tone on pin {this.BcmPinNumber}. Error Code: {setupResult}"); } } Native.WiringPi.SoftToneWrite(this.BcmPinNumber, value); this._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._softPwmValue >= 0; /// /// Gets or sets the software PWM value on the pin. /// /// /// The soft PWM value. /// /// StartSoftPwm. public Int32 SoftPwmValue { get => this._softPwmValue; set { lock(this._syncLock) { if(this.IsInSoftPwmMode && value >= 0) { Native.WiringPi.SoftPwmWrite(this.BcmPinNumber, value); this._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.HasCapability(PinCapability.GP)) { throw new NotSupportedException($"Pin {this.BcmPinNumber} does not support software PWM"); } if(this.IsInSoftPwmMode) { throw new InvalidOperationException($"{nameof(StartSoftPwm)} has already been called."); } Int32 startResult = Native.WiringPi.SoftPwmCreate(this.BcmPinNumber, value, range); if(startResult == 0) { this._softPwmValue = value; this.SoftPwmRange = range; } else { throw new InvalidOperationException($"Could not start software based PWM on pin {this.BcmPinNumber}. Error code: {startResult}"); } } } #endregion #region Output Mode (Write) Members /// public void Write(GpioPinValue value) { lock(this._syncLock) { if(this.PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException($"Unable to write to pin {this.BcmPinNumber} because operating mode is {this.PinMode}." + $" Writes are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Output}"); } Native.WiringPi.DigitalWrite(this.BcmPinNumber, (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.BcmPinNumber} because operating mode is {this.PinMode}." + $" Writes are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Output}"); } Native.WiringPi.AnalogWrite(this.BcmPinNumber, 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.BcmPinNumber} 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; } } 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.BcmPinNumber} because operating mode is {this.PinMode}." + $" Reads are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}"); } return Native.WiringPi.DigitalRead(this.BcmPinNumber) != 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.BcmPinNumber} because operating mode is {this.PinMode}." + $" Reads are only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } return Native.WiringPi.AnalogRead(this.BcmPinNumber); } } /// /// 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 /// /// callback. public void RegisterInterruptCallback(EdgeDetection edgeDetection, Action callback) { if(callback == null) { throw new ArgumentNullException(nameof(callback)); } if(this.PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException($"Unable to {nameof(RegisterInterruptCallback)} for pin {this.BcmPinNumber} because operating mode is {this.PinMode}." + $" Calling {nameof(RegisterInterruptCallback)} is only allowed if {nameof(this.PinMode)} is set to {GpioPinDriveMode.Input}"); } lock(this._syncLock) { Native.InterruptServiceRoutineCallback isrCallback = new Native.InterruptServiceRoutineCallback(callback); Int32 registerResult = Native.WiringPi.WiringPiISR(this.BcmPinNumber, GetWiringPiEdgeDetection(edgeDetection), isrCallback); if(registerResult == 0) { this.InterruptEdgeDetection = edgeDetection; this.InterruptCallback = isrCallback; } else { HardwareException.Throw(nameof(GpioPin), nameof(RegisterInterruptCallback)); } } } /// public void RegisterInterruptCallback(EdgeDetection edgeDetection, Action callback) => throw new NotSupportedException("WiringPi does only support a simple interrupt callback that has no parameters."); internal static WiringPiPin BcmToWiringPiPinNumber(BcmPin pin) => (WiringPiPin)GpioToWiringPi[(Int32)pin]; private static Int32 GetWiringPiEdgeDetection(EdgeDetection edgeDetection) => GpioController.WiringPiEdgeDetectionMapping[edgeDetection]; #endregion } }