Skip to content

Commit febca6f

Browse files
authored
Adding Pulse Count for ESP32 and ESP32-S2/S3 (#179)
1 parent 3132838 commit febca6f

File tree

6 files changed

+365
-1
lines changed

6 files changed

+365
-1
lines changed

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,70 @@ S2 can only be woke up with 1 touch pad. It is recommended to do tests to find t
106106
Sleep.EnableWakeupByTouchPad(PadForSleep, thresholdCoefficient: 90);
107107
```
108108

109+
## Pulse Counter
110+
111+
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.
112+
113+
### Pulse Counter with 1 pin
114+
115+
The following code illustrate how to setup a counter for 1 pin:
116+
117+
```csharp
118+
GpioPulseCounter counter = new GpioPulseCounter(26);
119+
counter.Polarity = GpioPulsePolarity.Rising;
120+
counter.FilterPulses = 0;
121+
122+
counter.Start();
123+
int inc = 0;
124+
GpioPulseCount counterCount;
125+
while (inc++ < 100)
126+
{
127+
counterCount = counter.Read();
128+
Console.WriteLine($"{counterCount.RelativeTime}: {counterCount.Count}");
129+
Thread.Sleep(1000);
130+
}
131+
132+
counter.Stop();
133+
counter.Dispose();
134+
```
135+
136+
The counter will always be positive and incremental. You can reset to 0 the count by calling the `Reset` function:
137+
138+
```csharp
139+
GpioPulseCount pulses = counter.Reset();
140+
// pulses.Count contains the actual count, it is then put to 0 once the function is called
141+
```
142+
143+
## Pulse Counter with 2 pins
144+
145+
This is typically a rotary encoder scenario. In this case, you need 2 pins and they'll act like in this graphic:**
146+
147+
![rotary encoder principal](https://github.com/nanoframework/nanoFramework.IoT.Device/blob/develop/devices/RotaryEncoder/encoder.png?raw=true)
148+
149+
You can then use a rotary encoder connected to 2 pins:
150+
151+
![rotary encoder](https://github.com/nanoframework/nanoFramework.IoT.Device/blob/develop/devices/RotaryEncoder/RotaryEncoder.Sample_bb.png?raw=true)
152+
153+
The associated code is the same as for 1 pin except in the constructor:
154+
155+
```csharp
156+
GpioPulseCounter encoder = new GpioPulseCounter(12, 14);
157+
encoder.Start();
158+
int incEncod = 0;
159+
GpioPulseCount counterCountEncode;
160+
while (incEncod++ < 100)
161+
{
162+
counterCountEncode = encoder.Read();
163+
Console.WriteLine($"{counterCountEncode.RelativeTime}: {counterCountEncode.Count}");
164+
Thread.Sleep(1000);
165+
}
166+
167+
encoder.Stop();
168+
encoder.Dispose();
169+
```
170+
171+
As a result, you'll get positives and negative pulses
172+
109173
## Feedback and documentation
110174

111175
For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
// See LICENSE file in the project root for full license information.
3+
4+
namespace System.Device.Gpio
5+
{
6+
/// <summary>
7+
/// Represents a near-simultaneous sampling of the number of times a pin has changed value, and the time at which this count was sampled.
8+
/// This structure can be used to determine the number of pin value changes over a period of time.
9+
/// </summary>
10+
public struct GpioPulseCount
11+
{
12+
// NOTE: This structure is used by the native code, so it must be kept in sync with the native code.
13+
// So no auto property used as not supported by nanoCLR. For simplicity, no backing field either.
14+
/// <summary>
15+
/// The number of times the transition of polarity specified by <see cref="GpioPulsePolarity"/> occured on the pin.
16+
/// </summary>
17+
public long Count;
18+
19+
/// <summary>
20+
/// The time at which this count was sampled. The time is sampled close to (but not simultaneously with) the count.
21+
/// This timestamp can be used to determine the elapsed time between two GpioChangeCount records.
22+
/// It does not correspond to any absolute or system time.
23+
/// </summary>
24+
public TimeSpan RelativeTime;
25+
}
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
// See LICENSE file in the project root for full license information.
3+
4+
namespace System.Device.Gpio
5+
{
6+
/// <summary>
7+
/// Represents the polarity of changes that are relevant to the associated action.
8+
/// </summary>
9+
public enum GpioPulsePolarity
10+
{
11+
/// <summary>
12+
/// Transitions from both low to high and high to low should trigger the associated action.
13+
/// </summary>
14+
Both,
15+
/// <summary>
16+
/// Transitions from high to low should trigger the associated action.
17+
/// </summary>
18+
Falling,
19+
/// <summary>
20+
/// Transitions from low to high should trigger the associated action.
21+
/// </summary>
22+
Rising
23+
}
24+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
// See LICENSE file in the project root for full license information.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
namespace System.Device.Gpio
7+
{
8+
/// <summary>
9+
/// Counts changes of a specified polarity on a general-purpose I/O (GPIO) pin.
10+
/// </summary>
11+
/// <remarks>
12+
/// <para>
13+
/// When the pin is an input, interrupts are used to detect pin changes unless the MCU supports a counter in hardware.
14+
/// Changes of the pin are enabled for the specified polarity, and the count is incremented when a change occurs.
15+
/// </para>
16+
/// <para>
17+
/// When the pin is an output, the count will increment whenever the specified transition occurs on the pin.
18+
/// 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.
19+
/// </para>
20+
/// </remarks>
21+
public sealed class Gpio​PulseCounter : IDisposable
22+
{
23+
// property backing fields
24+
private readonly int _pinNumberA;
25+
private readonly int _pinNumberB;
26+
private GpioPulsePolarity _polarity = GpioPulsePolarity.Falling;
27+
private bool _countActive = false;
28+
private ushort _filter;
29+
30+
// this is used as the lock object
31+
// a lock is required because multiple threads can access the Gpio​Change​Counter
32+
private readonly object _syncLock = new object();
33+
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="Gpio​PulseCounter"/> class associated with the specified pin.
36+
/// Only a single <see cref="Gpio​PulseCounter"/> may be associated with a pin at any given time.
37+
/// </summary>
38+
/// <param name="pinNumberA">The first pin on which to count changes.</param>
39+
/// <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>
40+
/// <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>
41+
public GpioPulseCounter(int pinNumberA, int pinNumberB = -1)
42+
{
43+
if (pinNumberA < 0)
44+
{
45+
throw new ArgumentException();
46+
}
47+
48+
_pinNumberA = pinNumberA;
49+
_pinNumberB = pinNumberB;
50+
51+
NativeInit();
52+
}
53+
54+
/// <summary>
55+
/// Gets whether pin change counting is currently active.
56+
/// </summary>
57+
/// <returns><c>TRUE</c> if this pin change counting is active and <c>FALSE</c> otherwise.</returns>
58+
public bool IsStarted => _countActive;
59+
60+
/// <summary>
61+
/// Gets or sets the polarity of transitions that will be counted. The polarity may only be changed when pin counting is not started.
62+
/// </summary>
63+
/// <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>
64+
/// </remarks>
65+
/// <exception cref="InvalidOperationException">Change counting is currently active. Polarity can only be set before calling Start() or after calling Stop().</exception>
66+
public GpioPulsePolarity Polarity
67+
{
68+
get => _polarity;
69+
70+
set
71+
{
72+
CheckIfActive(true);
73+
74+
_polarity = value;
75+
}
76+
}
77+
78+
/// <summary>
79+
/// Gets or sets the signal filter value in microseconds. The filter value may only be changed when pin counting is not started.
80+
/// Valid values from 0 to 1023. The clock used is 80 MHz, filter is a multiple of the period of the clock.
81+
/// </summary>
82+
/// <exception cref="ArgumentException">Value must be between 0 and 1023.</exception>
83+
public ushort FilterPulses
84+
{
85+
get => _filter;
86+
87+
set
88+
{
89+
// filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023.
90+
// PCNT signal filter value, counter in APB_CLK cycles
91+
// APB_CLK = 80 MHz
92+
if (value > 1023)
93+
{
94+
throw new ArgumentException();
95+
}
96+
97+
_filter = value;
98+
}
99+
}
100+
101+
/// <summary>
102+
/// Reads the current count of polarity changes. Before counting has been started, this will return 0.
103+
/// </summary>
104+
/// <returns>A <see cref="GpioPulseCount" /> structure containing a count and an associated timestamp.</returns>
105+
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
106+
public GpioPulseCount Read()
107+
{
108+
return ReadInternal(false);
109+
}
110+
111+
internal GpioPulseCount ReadInternal(bool reset)
112+
{
113+
GpioPulseCount changeCount;
114+
115+
lock (_syncLock)
116+
{
117+
if (_disposedValue) { throw new ObjectDisposedException(); }
118+
119+
changeCount = NativeRead(reset);
120+
}
121+
return changeCount;
122+
}
123+
124+
/// <summary>
125+
/// Resets the count to 0 and returns the previous count.
126+
/// </summary>
127+
/// <returns>A <see cref="GpioPulseCount" /> structure containing a count and an associated timestamp.</returns>
128+
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
129+
public GpioPulseCount Reset()
130+
{
131+
return ReadInternal(true);
132+
}
133+
134+
/// <summary>
135+
/// Starts counting changes in pin polarity. This method may only be called when change counting is not already active.
136+
/// </summary>
137+
/// <remarks>
138+
/// <para>Calling Start() may enable or reconfigure interrupts for the pin.</para>
139+
/// <para>The following exceptions can be thrown by this method:</para>
140+
/// </remarks>
141+
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
142+
/// <exception cref="InvalidOperationException">Change counting has already been started.</exception>
143+
public void Start()
144+
{
145+
lock (_syncLock)
146+
{
147+
if (_disposedValue) { throw new ObjectDisposedException(); }
148+
149+
CheckIfActive(true);
150+
151+
_countActive = true;
152+
153+
NativeStart();
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Stop counting changes in pin polarity. This method may only be called when change counting is currently active.
159+
/// </summary>
160+
/// <remarks>
161+
/// <para>Calling Stop() may enable or reconfigure interrupts for the pin.</para>
162+
/// <para>The following exceptions can be thrown by this method:</para>
163+
/// </remarks>
164+
/// <exception cref="ObjectDisposedException">The change counter or the associated pin has been disposed.</exception>
165+
/// <exception cref="InvalidOperationException">Change counting has not been started.</exception>
166+
public void Stop()
167+
{
168+
lock (_syncLock)
169+
{
170+
if (_disposedValue) { throw new ObjectDisposedException(); }
171+
172+
CheckIfActive(false);
173+
174+
_countActive = false;
175+
176+
NativeStop();
177+
}
178+
}
179+
180+
181+
private void CheckIfActive(bool state)
182+
{
183+
if (_countActive == state)
184+
{
185+
throw (new InvalidOperationException());
186+
}
187+
}
188+
189+
190+
#region IDisposable Support
191+
192+
private bool _disposedValue = false;
193+
194+
void Dispose(bool disposing)
195+
{
196+
if (!_disposedValue)
197+
{
198+
if (disposing)
199+
{
200+
// No managed object to dispose.
201+
}
202+
203+
NativeDispose();
204+
205+
_disposedValue = true;
206+
}
207+
}
208+
209+
#pragma warning disable 1591
210+
~Gpio​PulseCounter()
211+
{
212+
Dispose(false);
213+
}
214+
215+
/// <summary>
216+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
217+
/// </summary>
218+
public void Dispose()
219+
{
220+
lock (_syncLock)
221+
{
222+
Dispose(true);
223+
224+
GC.SuppressFinalize(this);
225+
}
226+
}
227+
#endregion
228+
229+
230+
#region Native
231+
[MethodImpl(MethodImplOptions.InternalCall)]
232+
private extern void NativeInit();
233+
234+
[MethodImpl(MethodImplOptions.InternalCall)]
235+
private extern GpioPulseCount NativeRead(bool Reset);
236+
237+
[MethodImpl(MethodImplOptions.InternalCall)]
238+
private extern void NativeStart();
239+
240+
[MethodImpl(MethodImplOptions.InternalCall)]
241+
private extern void NativeStop();
242+
243+
[MethodImpl(MethodImplOptions.InternalCall)]
244+
private extern void NativeDispose();
245+
#endregion
246+
}
247+
}

nanoFramework.Hardware.Esp32/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
////////////////////////////////////////////////////////////////
1414
// update this whenever the native assembly signature changes //
15-
[assembly: AssemblyNativeVersion("100.0.8.0")]
15+
[assembly: AssemblyNativeVersion("100.0.9.0")]
1616
////////////////////////////////////////////////////////////////
1717

1818
// Setting ComVisible to false makes the types in this assembly not visible

nanoFramework.Hardware.Esp32/nanoFramework.Hardware.Esp32.nfproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
5454
<ItemGroup>
5555
<Compile Include="GpioPins.cs" />
56+
<Compile Include="Gpio\GpioPulseCount.cs" />
57+
<Compile Include="Gpio\GpioPulsePolarity.cs" />
58+
<Compile Include="Gpio\Gpio​PulseCounter.cs" />
5659
<Compile Include="HighResEventListener.cs" />
5760
<Compile Include="HighResTimer.cs" />
5861
<Compile Include="HighResTimerEvent.cs" />

0 commit comments

Comments
 (0)