2019-12-06 21:09:52 +01:00
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 ;
2019-12-04 18:57:18 +01:00
/// <summary>
2019-12-06 21:09:52 +01:00
/// Initializes a new instance of the <see cref="GpioPin"/> class.
2019-12-04 18:57:18 +01:00
/// </summary>
2019-12-06 21:09:52 +01:00
/// <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>
2019-12-09 17:27:40 +01:00
public Task WriteAsync ( GpioPinValue value ) = > Task . Run ( ( ) = > this . Write ( value ) ) ;
2019-12-06 21:09:52 +01:00
/// <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>
2019-12-09 17:27:40 +01:00
public Task WriteAsync ( Boolean value ) = > Task . Run ( ( ) = > this . Write ( value ) ) ;
2019-12-06 21:09:52 +01:00
/// <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>
2019-12-09 17:27:40 +01:00
public Task WriteAsync ( Int32 value ) = > Task . Run ( ( ) = > this . Write ( value ) ) ;
2019-12-06 21:09:52 +01:00
/// <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>
2019-12-09 17:27:40 +01:00
public Task WriteLevelAsync ( Int32 value ) = > Task . Run ( ( ) = > this . WriteLevel ( value ) ) ;
2019-12-06 21:09:52 +01:00
#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
}
2019-12-04 18:57:18 +01:00
}