namespace Unosquare.WiringPi { using System; using System.Threading.Tasks; using Native; using RaspberryIO.Abstractions; using RaspberryIO.Abstractions.Native; using Swan.Diagnostics; using Definitions = RaspberryIO.Abstractions.Definitions; /// /// 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 int[] GpioToWiringPi; private static readonly int[] 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 int[] 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 int _pwmRegister; private PwmMode _pwmMode = PwmMode.Balanced; private uint _pwmRange = 1024; private int _pwmClockDivisor = 1; private int _softPwmValue = -1; private int _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) { BcmPin = bcmPinNumber; BcmPinNumber = (int)bcmPinNumber; WiringPiPinNumber = BcmToWiringPiPinNumber(bcmPinNumber); PhysicalPinNumber = Definitions.BcmToPhysicalPinNumber(SystemInfo.GetBoardRevision(), bcmPinNumber); Header = (BcmPinNumber >= 28 && BcmPinNumber <= 31) ? GpioHeader.P5 : GpioHeader.P1; } #endregion #region Pin Properties /// public BcmPin BcmPin { get; } /// public int BcmPinNumber { get; } /// public int 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 bool Value { get => Read(); set => Write(value); } #endregion #region Hardware-Specific Properties /// /// Thrown when a pin does not support the given operation mode. public GpioPinDriveMode PinMode { get => _pinMode; set { lock (_syncLock) { var mode = value; if ((mode == GpioPinDriveMode.GpioClock && !HasCapability(PinCapability.GPCLK)) || (mode == GpioPinDriveMode.PwmOutput && !HasCapability(PinCapability.PWM)) || (mode == GpioPinDriveMode.Input && !HasCapability(PinCapability.GP)) || (mode == GpioPinDriveMode.Output && !HasCapability(PinCapability.GP))) { throw new NotSupportedException( $"Pin {BcmPinNumber} '{Name}' does not support mode '{mode}'. Pin capabilities are limited to: {Capabilities}"); } WiringPi.PinMode(BcmPinNumber, (int)mode); _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; } /// /// Determines whether the specified capability has capability. /// /// The capability. /// /// true if the specified capability has capability; otherwise, false. /// public bool HasCapability(PinCapability capability) => (Capabilities & capability) == capability; #endregion #region Hardware PWM Members /// public GpioPinResistorPullMode InputPullMode { get => PinMode == GpioPinDriveMode.Input ? _resistorPullMode : GpioPinResistorPullMode.Off; set { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Input) { _resistorPullMode = GpioPinResistorPullMode.Off; throw new InvalidOperationException( $"Unable to set the {nameof(InputPullMode)} for pin {BcmPinNumber} because operating mode is {PinMode}." + $" Setting the {nameof(InputPullMode)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } WiringPi.PullUpDnControl(BcmPinNumber, (int)value); _resistorPullMode = value; } } } /// /// Gets or sets the PWM register. /// /// /// The PWM register. /// public int PwmRegister { get => _pwmRegister; set { lock (_syncLock) { if (!HasCapability(PinCapability.PWM)) { _pwmRegister = 0; throw new NotSupportedException( $"Pin {BcmPinNumber} '{Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {Capabilities}"); } WiringPi.PwmWrite(BcmPinNumber, value); _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 => PinMode == GpioPinDriveMode.PwmOutput ? _pwmMode : PwmMode.Balanced; set { lock (_syncLock) { if (!HasCapability(PinCapability.PWM)) { _pwmMode = PwmMode.Balanced; throw new NotSupportedException( $"Pin {BcmPinNumber} '{Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {Capabilities}"); } WiringPi.PwmSetMode((int)value); _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 ? _pwmRange : 0; set { lock (_syncLock) { if (!HasCapability(PinCapability.PWM)) { _pwmRange = 1024; throw new NotSupportedException( $"Pin {BcmPinNumber} '{Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {Capabilities}"); } WiringPi.PwmSetRange(value); _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 ? _pwmClockDivisor : 0; set { lock (_syncLock) { if (!HasCapability(PinCapability.PWM)) { _pwmClockDivisor = 1; throw new NotSupportedException( $"Pin {BcmPinNumber} '{Name}' does not support mode '{GpioPinDriveMode.PwmOutput}'. Pin capabilities are limited to: {Capabilities}"); } WiringPi.PwmSetClock(value); _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 => _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 => _softToneFrequency; set { lock (_syncLock) { if (IsInSoftToneMode == false) { var setupResult = WiringPi.SoftToneCreate(BcmPinNumber); if (setupResult != 0) { throw new InvalidOperationException( $"Unable to initialize soft tone on pin {BcmPinNumber}. Error Code: {setupResult}"); } } WiringPi.SoftToneWrite(BcmPinNumber, value); _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 => _softPwmValue >= 0; /// /// Gets or sets the software PWM value on the pin. /// /// /// The soft PWM value. /// /// StartSoftPwm. public int SoftPwmValue { get => _softPwmValue; set { lock (_syncLock) { if (IsInSoftPwmMode && value >= 0) { WiringPi.SoftPwmWrite(BcmPinNumber, value); _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 (!HasCapability(PinCapability.GP)) throw new NotSupportedException($"Pin {BcmPinNumber} does not support software PWM"); if (IsInSoftPwmMode) throw new InvalidOperationException($"{nameof(StartSoftPwm)} has already been called."); var startResult = WiringPi.SoftPwmCreate(BcmPinNumber, value, range); if (startResult == 0) { _softPwmValue = value; SoftPwmRange = range; } else { throw new InvalidOperationException( $"Could not start software based PWM on pin {BcmPinNumber}. Error code: {startResult}"); } } } #endregion #region Output Mode (Write) Members /// public void Write(GpioPinValue value) { lock (_syncLock) { if (PinMode != GpioPinDriveMode.Output) { throw new InvalidOperationException( $"Unable to write to pin {BcmPinNumber} because operating mode is {PinMode}." + $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.DigitalWrite(BcmPinNumber, (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 {BcmPinNumber} because operating mode is {PinMode}." + $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}"); } WiringPi.AnalogWrite(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(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 {BcmPinNumber} 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; } 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 {BcmPinNumber} because operating mode is {PinMode}." + $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}"); } return WiringPi.DigitalRead(BcmPinNumber) != 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 {BcmPinNumber} because operating mode is {PinMode}." + $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } return WiringPi.AnalogRead(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(ReadLevel); #endregion #region Interrupts /// /// callback. public void RegisterInterruptCallback(EdgeDetection edgeDetection, Action callback) { if (callback == null) throw new ArgumentNullException(nameof(callback)); if (PinMode != GpioPinDriveMode.Input) { throw new InvalidOperationException( $"Unable to {nameof(RegisterInterruptCallback)} for pin {BcmPinNumber} because operating mode is {PinMode}." + $" Calling {nameof(RegisterInterruptCallback)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}"); } lock (_syncLock) { var isrCallback = new InterruptServiceRoutineCallback(callback); var registerResult = WiringPi.WiringPiISR(BcmPinNumber, GetWiringPiEdgeDetection(edgeDetection), isrCallback); if (registerResult == 0) { InterruptEdgeDetection = edgeDetection; 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[(int)pin]; private static int GetWiringPiEdgeDetection(EdgeDetection edgeDetection) => GpioController.WiringPiEdgeDetectionMapping[edgeDetection]; #endregion } }