RaspberryIO_26/Unosquare.WiringPi/GpioPin.cs

584 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
/// <summary>
/// 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/.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="GpioPin"/> class.
/// </summary>
/// <param name="bcmPinNumber">The BCM pin number.</param>
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
/// <inheritdoc />
public BcmPin BcmPin {
get;
}
/// <inheritdoc />
public Int32 BcmPinNumber {
get;
}
/// <inheritdoc />
public Int32 PhysicalPinNumber {
get;
}
/// <summary>
/// Gets the WiringPi Pin number.
/// </summary>
public WiringPiPin WiringPiPinNumber {
get;
}
/// <inheritdoc />
public GpioHeader Header {
get;
}
/// <summary>
/// Gets the friendly name of the pin.
/// </summary>
public String Name {
get; private set;
}
/// <summary>
/// Gets the hardware mode capabilities of this pin.
/// </summary>
public PinCapability Capabilities {
get; private set;
}
/// <inheritdoc />
public Boolean Value {
get => this.Read();
set => this.Write(value);
}
#endregion
#region Hardware-Specific Properties
/// <inheritdoc />
/// <exception cref="T:System.NotSupportedException">Thrown when a pin does not support the given operation mode.</exception>
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;
}
}
}
/// <summary>
/// Gets the interrupt callback. Returns null if no interrupt
/// has been registered.
/// </summary>
public Native.InterruptServiceRoutineCallback InterruptCallback {
get; private set;
}
/// <summary>
/// Gets the interrupt edge detection mode.
/// </summary>
public EdgeDetection InterruptEdgeDetection {
get; private set;
}
/// <summary>
/// Determines whether the specified capability has capability.
/// </summary>
/// <param name="capability">The capability.</param>
/// <returns>
/// <c>true</c> if the specified capability has capability; otherwise, <c>false</c>.
/// </returns>
public Boolean HasCapability(PinCapability capability) => (this.Capabilities & capability) == capability;
#endregion
#region Hardware PWM Members
/// <inheritdoc />
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;
}
}
}
/// <summary>
/// Gets or sets the PWM register.
/// </summary>
/// <value>
/// The PWM register.
/// </value>
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;
}
}
}
/// <summary>
/// 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”.
/// </summary>
/// <value>
/// The PWM mode.
/// </value>
/// <exception cref="InvalidOperationException">When pin mode is not set a Pwn output.</exception>
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;
}
}
}
/// <summary>
/// This sets the range register in the PWM generator. The default is 1024.
/// </summary>
/// <value>
/// The PWM range.
/// </value>
/// <exception cref="InvalidOperationException">When pin mode is not set to PWM output.</exception>
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;
}
}
}
/// <summary>
/// Gets or sets the PWM clock divisor.
/// </summary>
/// <value>
/// The PWM clock divisor.
/// </value>
/// <exception cref="InvalidOperationException">When pin mode is not set to PWM output.</exception>
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
/// <summary>
/// Gets a value indicating whether this instance is in software based tone generator mode.
/// </summary>
/// <value>
/// <c>true</c> if this instance is in soft tone mode; otherwise, <c>false</c>.
/// </value>
public Boolean IsInSoftToneMode => this._softToneFrequency >= 0;
/// <summary>
/// Gets or sets the soft tone frequency. 0 to 5000 Hz is typical.
/// </summary>
/// <value>
/// The soft tone frequency.
/// </value>
/// <exception cref="InvalidOperationException">When soft tones cannot be initialized on the pin.</exception>
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
/// <summary>
/// Gets a value indicating whether this pin is in software based PWM mode.
/// </summary>
/// <value>
/// <c>true</c> if this instance is in soft PWM mode; otherwise, <c>false</c>.
/// </value>
public Boolean IsInSoftPwmMode => this._softPwmValue >= 0;
/// <summary>
/// Gets or sets the software PWM value on the pin.
/// </summary>
/// <value>
/// The soft PWM value.
/// </value>
/// <exception cref="InvalidOperationException">StartSoftPwm.</exception>
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)}.");
}
}
}
}
/// <summary>
/// Gets the software PWM range used upon starting the PWM.
/// </summary>
public Int32 SoftPwmRange { get; private set; } = -1;
/// <summary>
/// Starts the software based PWM on this pin.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="range">The range.</param>
/// <exception cref="NotSupportedException">When the pin does not suppoert PWM.</exception>
/// <exception cref="InvalidOperationException">StartSoftPwm
/// or.</exception>
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
/// <inheritdoc />
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);
}
}
/// <summary>
/// Writes the value asynchronously.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The awaitable task.</returns>
public Task WriteAsync(GpioPinValue value) => Task.Run(() => this.Write(value));
/// <summary>
/// Writes the specified bit value.
/// This method performs a digital write.
/// </summary>
/// <param name="value">if set to <c>true</c> [value].</param>
public void Write(Boolean value) => this.Write(value ? GpioPinValue.High : GpioPinValue.Low);
/// <summary>
/// Writes the specified bit value.
/// This method performs a digital write.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// The awaitable task.
/// </returns>
public Task WriteAsync(Boolean value) => Task.Run(() => this.Write(value));
/// <summary>
/// Writes the specified value. 0 for low, any other value for high
/// This method performs a digital write.
/// </summary>
/// <param name="value">The value.</param>
public void Write(Int32 value) => this.Write(value != 0 ? GpioPinValue.High : GpioPinValue.Low);
/// <summary>
/// Writes the specified value. 0 for low, any other value for high
/// This method performs a digital write.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The awaitable task.</returns>
public Task WriteAsync(Int32 value) => Task.Run(() => this.Write(value));
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The value.</param>
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The awaitable task.</returns>
public Task WriteLevelAsync(Int32 value) => Task.Run(() => this.WriteLevel(value));
#endregion
#region Input Mode (Read) Members
/// <summary>
/// Wait for specific pin status.
/// </summary>
/// <param name="status">status to check.</param>
/// <param name="timeOutMillisecond">timeout to reach status.</param>
/// <returns>true/false.</returns>
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;
}
/// <summary>
/// Reads the digital value on the pin as a boolean value.
/// </summary>
/// <returns>The state of the pin.</returns>
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;
}
}
/// <summary>
/// Reads the digital value on the pin as a boolean value.
/// </summary>
/// <returns>The state of the pin.</returns>
public Task<Boolean> ReadAsync() => Task.Run(this.Read);
/// <summary>
/// Reads the digital value on the pin as a High or Low value.
/// </summary>
/// <returns>The state of the pin.</returns>
public GpioPinValue ReadValue() => this.Read() ? GpioPinValue.High : GpioPinValue.Low;
/// <summary>
/// Reads the digital value on the pin as a High or Low value.
/// </summary>
/// <returns>The state of the pin.</returns>
public Task<GpioPinValue> ReadValueAsync() => Task.Run(this.ReadValue);
/// <summary>
/// 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.
/// </summary>
/// <returns>The analog level.</returns>
/// <exception cref="InvalidOperationException">When the pin mode is not configured as an input.</exception>
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The analog level.</returns>
public Task<Int32> ReadLevelAsync() => Task.Run(this.ReadLevel);
#endregion
#region Interrupts
/// <inheritdoc />
/// <exception cref="ArgumentNullException">callback.</exception>
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));
}
}
}
/// <inheritdoc />
public void RegisterInterruptCallback(EdgeDetection edgeDetection, Action<Int32, Int32, UInt32> 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
}
}