RaspberryIO/Unosquare.RaspberryIO/Gpio/GpioPin.cs
2019-12-03 18:43:54 +01:00

606 lines
21 KiB
C#
Raw Permalink 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 Unosquare.RaspberryIO.Native;
using Unosquare.Swan;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Unosquare.RaspberryIO.Gpio {
/// <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 {
#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
/// <summary>
/// Initializes a new instance of the <see cref="GpioPin"/> class.
/// </summary>
/// <param name="wiringPiPinNumber">The wiring pi pin number.</param>
/// <param name="headerPinNumber">The header pin number.</param>
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
/// <summary>
/// Gets or sets the Wiring Pi pin number as an integer.
/// </summary>
public Int32 PinNumber {
get;
}
/// <summary>
/// Gets the WiringPi Pin number
/// </summary>
public WiringPiPin WiringPiPinNumber {
get;
}
/// <summary>
/// Gets the BCM chip (hardware) pin number.
/// </summary>
public Int32 BcmPinNumber {
get;
}
/// <summary>
/// Gets or the physical header (physical board) pin number.
/// </summary>
public Int32 HeaderPinNumber {
get;
}
/// <summary>
/// Gets the pin's header (physical board) location.
/// </summary>
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;
}
#endregion
#region Hardware-Specific Properties
/// <summary>
/// Gets or sets the pin operating mode.
/// </summary>
/// <value>
/// The pin mode.
/// </value>
/// <exception cref="NotSupportedException">Thrown when a pin does not support the given operation mode.</exception>
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;
}
}
}
/// <summary>
/// Gets the interrupt callback. Returns null if no interrupt
/// has been registered.
/// </summary>
public InterruptServiceRoutineCallback InterruptCallback {
get; private set;
}
/// <summary>
/// Gets the interrupt edge detection mode.
/// </summary>
public EdgeDetection InterruptEdgeDetection { get; private set; } = EdgeDetection.ExternalSetup;
#endregion
#region Hardware PWM Members
/// <summary>
/// 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.
/// </summary>
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;
}
}
}
/// <summary>
/// Gets or sets the PWM register. Values should be between 0 and 1024
/// </summary>
/// <value>
/// The PWM register.
/// </value>
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;
}
}
}
/// <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.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;
}
}
}
/// <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.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;
}
}
}
/// <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.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
/// <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.m_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.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
/// <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.m_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.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)}.");
}
}
}
}
/// <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.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
/// <summary>
/// Writes the specified pin value.
/// This method performs a digital write
/// </summary>
/// <param name="value">The value.</param>
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);
}
}
/// <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.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);
}
}
/// <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.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;
}
/// <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.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;
}
}
/// <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.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);
}
}
/// <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
/// <summary>
/// Registers the interrupt callback on the pin. Pin mode has to be set to Input.
/// </summary>
/// <param name="edgeDetection">The edge detection.</param>
/// <param name="callback">The callback.</param>
/// <exception cref="ArgumentException">callback</exception>
/// <exception cref="InvalidOperationException">
/// An interrupt callback was already registered.
/// or
/// RegisterInterruptCallback
/// </exception>
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
}
}