Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Pulse Count for ESP32 and ESP32-S2/S3 #179

Merged
merged 2 commits into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,70 @@ S2 can only be woke up with 1 touch pad. It is recommended to do tests to find t
Sleep.EnableWakeupByTouchPad(PadForSleep, thresholdCoefficient: 90);
```

## Pulse Counter

Pulse counter allows to count pulses without having to setup a GPIO controller and events. It's a fast way to get count during a specific amount of time. This pulse counter allows as well to use 2 different pins and get a pulse count depending on their respective polarities.

### Pulse Counter with 1 pin

The following code illustrate how to setup a counter for 1 pin:

```csharp
Gpio​PulseCounter counter = new Gpio​PulseCounter(26);
counter.Polarity = GpioPulsePolarity.Rising;
counter.FilterPulses = 0;

counter.Start();
int inc = 0;
GpioPulseCount counterCount;
while (inc++ < 100)
{
counterCount = counter.Read();
Console.WriteLine($"{counterCount.RelativeTime}: {counterCount.Count}");
Thread.Sleep(1000);
}

counter.Stop();
counter.Dispose();
```

The counter will always be positive and incremental. You can reset to 0 the count by calling the `Reset` function:

```csharp
GpioPulseCount pulses = counter.Reset();
// pulses.Count contains the actual count, it is then put to 0 once the function is called
```

## Pulse Counter with 2 pins

This is typically a rotary encoder scenario. In this case, you need 2 pins and they'll act like in this graphic:**

![rotary encoder principal](https://github.com/nanoframework/nanoFramework.IoT.Device/blob/develop/devices/RotaryEncoder/encoder.png?raw=true)

You can then use a rotary encoder connected to 2 pins:

![rotary encoder](https://github.com/nanoframework/nanoFramework.IoT.Device/blob/develop/devices/RotaryEncoder/RotaryEncoder.Sample_bb.png?raw=true)

The associated code is the same as for 1 pin except in the constructor:

```csharp
Gpio​PulseCounter encoder = new Gpio​PulseCounter(12, 14);
encoder.Start();
int incEncod = 0;
GpioPulseCount counterCountEncode;
while (incEncod++ < 100)
{
counterCountEncode = encoder.Read();
Console.WriteLine($"{counterCountEncode.RelativeTime}: {counterCountEncode.Count}");
Thread.Sleep(1000);
}

encoder.Stop();
encoder.Dispose();
```

As a result, you'll get positives and negative pulses

## Feedback and documentation

For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).
Expand Down
26 changes: 26 additions & 0 deletions nanoFramework.Hardware.Esp32/Gpio/GpioPulseCount.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.

namespace System.Device.Gpio
{
/// <summary>
/// Represents a near-simultaneous sampling of the number of times a pin has changed value, and the time at which this count was sampled.
/// This structure can be used to determine the number of pin value changes over a period of time.
/// </summary>
public struct GpioPulseCount
{
// NOTE: This structure is used by the native code, so it must be kept in sync with the native code.
// So no auto property used as not supported by nanoCLR. For simplicity, no backing field either.
/// <summary>
/// The number of times the transition of polarity specified by <see cref="GpioPulsePolarity"/> occured on the pin.
/// </summary>
public long Count;

/// <summary>
/// The time at which this count was sampled. The time is sampled close to (but not simultaneously with) the count.
/// This timestamp can be used to determine the elapsed time between two GpioChangeCount records.
/// It does not correspond to any absolute or system time.
/// </summary>
public TimeSpan RelativeTime;
}
}
24 changes: 24 additions & 0 deletions nanoFramework.Hardware.Esp32/Gpio/GpioPulsePolarity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.

namespace System.Device.Gpio
{
/// <summary>
/// Represents the polarity of changes that are relevant to the associated action.
/// </summary>
public enum GpioPulsePolarity
{
/// <summary>
/// Transitions from both low to high and high to low should trigger the associated action.
/// </summary>
Both,
/// <summary>
/// Transitions from high to low should trigger the associated action.
/// </summary>
Falling,
/// <summary>
/// Transitions from low to high should trigger the associated action.
/// </summary>
Rising
}
}
247 changes: 247 additions & 0 deletions nanoFramework.Hardware.Esp32/Gpio/Gpio​PulseCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.

using System.Runtime.CompilerServices;

namespace System.Device.Gpio
{
/// <summary>
/// Counts changes of a specified polarity on a general-purpose I/O (GPIO) pin.
/// </summary>
/// <remarks>
/// <para>
/// When the pin is an input, interrupts are used to detect pin changes unless the MCU supports a counter in hardware.
/// Changes of the pin are enabled for the specified polarity, and the count is incremented when a change occurs.
/// </para>
/// <para>
/// When the pin is an output, the count will increment whenever the specified transition occurs on the pin.
/// For example, if the pin is configured as an output and counting is enabled for rising edges, writing a 0 and then a 1 will cause the count to be incremented.
/// </para>
/// </remarks>
public sealed class Gpio​PulseCounter : IDisposable
{
// property backing fields
private readonly int _pinNumberA;
private readonly int _pinNumberB;
private GpioPulsePolarity _polarity = GpioPulsePolarity.Falling;
private bool _countActive = false;
private ushort _filter;

// this is used as the lock object
// a lock is required because multiple threads can access the Gpio​Change​Counter
private readonly object _syncLock = new object();

/// <summary>
/// Initializes a new instance of the <see cref="Gpio​PulseCounter"/> class associated with the specified pin.
/// Only a single <see cref="Gpio​PulseCounter"/> may be associated with a pin at any given time.
/// </summary>
/// <param name="pinNumberA">The first pin on which to count changes.</param>
/// <param name="pinNumberB">The second pin which can be used to control how the count are done on the first pin. If no use, leave at at -1.</param>
/// <exception cref="ArgumentException">TThe pin is already associated with a change counter.That change counter must be disposed before the pin can be associated with a new change counter.</exception>
public Gpio​PulseCounter(int pinNumberA, int pinNumberB = -1)
{
if (pinNumberA < 0)
{
throw new ArgumentException();
}

_pinNumberA = pinNumberA;
_pinNumberB = pinNumberB;

NativeInit();
}

/// <summary>
/// Gets whether pin change counting is currently active.
/// </summary>
/// <returns><c>TRUE</c> if this pin change counting is active and <c>FALSE</c> otherwise.</returns>
public bool IsStarted => _countActive;

/// <summary>
/// Gets or sets the polarity of transitions that will be counted. The polarity may only be changed when pin counting is not started.
/// </summary>
/// <remarks><para>The default polarity value is Falling. See <see cref="GpioPulsePolarity"></see> for more information on polarity values. Counting a single edge can be considerably more efficient than counting both edges.</para>
/// </remarks>
/// <exception cref="InvalidOperationException">Change counting is currently active. Polarity can only be set before calling Start() or after calling Stop().</exception>
public GpioPulsePolarity Polarity
{
get => _polarity;

set
{
CheckIfActive(true);

_polarity = value;
}
}

/// <summary>
/// Gets or sets the signal filter value in microseconds. The filter value may only be changed when pin counting is not started.
/// Valid values from 0 to 1023. The clock used is 80 MHz, filter is a multiple of the period of the clock.
/// </summary>
/// <exception cref="ArgumentException">Value must be between 0 and 1023.</exception>
public ushort FilterPulses
{
get => _filter;

set
{
// filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023.
// PCNT signal filter value, counter in APB_CLK cycles
// APB_CLK = 80 MHz
if (value > 1023)
{
throw new ArgumentException();
}

_filter = value;
}
}

/// <summary>
/// Reads the current count of polarity changes. Before counting has been started, this will return 0.
/// </summary>
/// <returns>A <see cref="GpioPulseCount" /> structure containing a count and an associated timestamp.</returns>
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
public GpioPulseCount Read()
{
return ReadInternal(false);
}

internal GpioPulseCount ReadInternal(bool reset)
{
GpioPulseCount changeCount;

lock (_syncLock)
{
if (_disposedValue) { throw new ObjectDisposedException(); }

changeCount = NativeRead(reset);
}
return changeCount;
}

/// <summary>
/// Resets the count to 0 and returns the previous count.
/// </summary>
/// <returns>A <see cref="GpioPulseCount" /> structure containing a count and an associated timestamp.</returns>
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
public GpioPulseCount Reset()
{
return ReadInternal(true);
}

/// <summary>
/// Starts counting changes in pin polarity. This method may only be called when change counting is not already active.
/// </summary>
/// <remarks>
/// <para>Calling Start() may enable or reconfigure interrupts for the pin.</para>
/// <para>The following exceptions can be thrown by this method:</para>
/// </remarks>
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
/// <exception cref="InvalidOperationException">Change counting has already been started.</exception>
public void Start()
{
lock (_syncLock)
{
if (_disposedValue) { throw new ObjectDisposedException(); }

CheckIfActive(true);

_countActive = true;

NativeStart();
}
}

/// <summary>
/// Stop counting changes in pin polarity. This method may only be called when change counting is currently active.
/// </summary>
/// <remarks>
/// <para>Calling Stop() may enable or reconfigure interrupts for the pin.</para>
/// <para>The following exceptions can be thrown by this method:</para>
/// </remarks>
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
/// <exception cref="InvalidOperationException">Change counting has not been started.</exception>
public void Stop()
{
lock (_syncLock)
{
if (_disposedValue) { throw new ObjectDisposedException(); }

CheckIfActive(false);

_countActive = false;

NativeStop();
}
}


private void CheckIfActive(bool state)
{
if (_countActive == state)
{
throw (new InvalidOperationException());
}
}


#region IDisposable Support

private bool _disposedValue = false;

void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// No managed object to dispose.
}

NativeDispose();

_disposedValue = true;
}
}

#pragma warning disable 1591
~Gpio​PulseCounter()
{
Dispose(false);
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
lock (_syncLock)
{
Dispose(true);

GC.SuppressFinalize(this);
}
}
#endregion


#region Native
[MethodImpl(MethodImplOptions.InternalCall)]
private extern void NativeInit();

[MethodImpl(MethodImplOptions.InternalCall)]
private extern GpioPulseCount NativeRead(bool Reset);

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void NativeStart();

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void NativeStop();

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void NativeDispose();
#endregion
}
}
2 changes: 1 addition & 1 deletion nanoFramework.Hardware.Esp32/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

////////////////////////////////////////////////////////////////
// update this whenever the native assembly signature changes //
[assembly: AssemblyNativeVersion("100.0.8.0")]
[assembly: AssemblyNativeVersion("100.0.9.0")]
////////////////////////////////////////////////////////////////

// Setting ComVisible to false makes the types in this assembly not visible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
<ItemGroup>
<Compile Include="GpioPins.cs" />
<Compile Include="Gpio\GpioPulseCount.cs" />
<Compile Include="Gpio\GpioPulsePolarity.cs" />
<Compile Include="Gpio\Gpio​PulseCounter.cs" />
<Compile Include="HighResEventListener.cs" />
<Compile Include="HighResTimer.cs" />
<Compile Include="HighResTimerEvent.cs" />
Expand Down