-
-
Notifications
You must be signed in to change notification settings - Fork 110
/
QuadratureRotaryEncoder.cs
151 lines (130 loc) · 6.07 KB
/
QuadratureRotaryEncoder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.Gpio;
using System.Diagnostics;
namespace Iot.Device.RotaryEncoder
{
/// <summary>
/// Event handler to allow the notification of value changes.
/// </summary>
/// <param name="sender">Object that created an event.</param>
/// <param name="e">Event args.</param>
public delegate void RotaryEncoderEventHandler(
object sender,
RotaryEncoderEventArgs e);
/// <summary>
/// Binding that exposes a quadrature rotary encoder.
/// </summary>
public class QuadratureRotaryEncoder : IDisposable
{
private GpioController _controller;
private int _pinA;
private int _pinB;
private bool _disposeController = true;
private Stopwatch _debouncer = new Stopwatch();
private uint _debounceMillisec;
/// <summary>
/// Gets the number of pulses expected per rotation of the encoder.
/// </summary>
public int PulsesPerRotation { get; private set; }
/// <summary>
/// Gets or sets the number of pulses before or after the start position of the encoder.
/// </summary>
public long PulseCount { get; set; }
/// <summary>
/// Gets the number of rotations backwards or forwards from the initial position of the encoder.
/// </summary>
public float Rotations { get => (float)PulseCount / PulsesPerRotation; }
/// <summary>
/// Gets or sets the Debounce property represents the minimum amount of delay
/// allowed between falling edges of the A (clk) pin. The recommended value are few milliseconds typically around 5.
/// This depends from your usage.
/// </summary>
public TimeSpan Debounce
{
get => TimeSpan.FromMilliseconds(_debounceMillisec);
set
{
_debounceMillisec = (uint)value.TotalMilliseconds;
}
}
/// <summary>
/// EventHandler to allow the notification of value changes.
/// </summary>
public event RotaryEncoderEventHandler? PulseCountChanged;
/// <summary>
/// Initializes a new instance of the <see cref="QuadratureRotaryEncoder" /> class.
/// </summary>
/// <param name="pinA">Pin A that is connected to the rotary encoder. Sometimes called clk.</param>
/// <param name="pinB">Pin B that is connected to the rotary encoder. Sometimes called data.</param>
/// <param name="edges">The pin event types to 'listen' for.</param>
/// <param name="pulsesPerRotation">The number of pulses to be received for every full rotation of the encoder.</param>
/// <param name="controller">GpioController that hosts Pins A and B.</param>
/// <param name="shouldDispose">True to dispose the controller.</param>
public QuadratureRotaryEncoder(int pinA, int pinB, PinEventTypes edges, int pulsesPerRotation, GpioController? controller = null, bool shouldDispose = true)
{
_disposeController = controller == null | shouldDispose;
_controller = controller ?? new GpioController();
PulsesPerRotation = pulsesPerRotation;
_debounceMillisec = 5;
Initialize(pinA, pinB, edges);
}
/// <summary>
/// Initializes a new instance of the <see cref="QuadratureRotaryEncoder" /> class.
/// </summary>
/// <param name="pinA">Pin A that is connected to the rotary encoder. Sometimes called clk.</param>
/// <param name="pinB">Pin B that is connected to the rotary encoder. Sometimes called data.</param>
/// <param name="pulsesPerRotation">The number of pulses to be received for every full rotation of the encoder.</param>
public QuadratureRotaryEncoder(int pinA, int pinB, int pulsesPerRotation)
: this(pinA, pinB, PinEventTypes.Falling, pulsesPerRotation, new GpioController(), false)
{
}
/// <summary>
/// Modify the current value on receipt of a pulse from the rotary encoder.
/// </summary>
/// <param name="blnUp">When true then the value should be incremented otherwise it should be decremented.</param>
/// <param name="milliSecondsSinceLastPulse">The number of miliseconds since the last pulse.</param>
protected virtual void OnPulse(bool blnUp, int milliSecondsSinceLastPulse)
{
PulseCount += blnUp ? 1 : -1;
// fire an event if an event handler has been attached
PulseCountChanged?.Invoke(this, new RotaryEncoderEventArgs(PulseCount));
}
/// <summary>
/// Initialize an QuadratureRotaryEncoder.
/// </summary>
/// <param name="pinA">Pin A that is connected to the rotary encoder. Sometimes called clk.</param>
/// <param name="pinB">Pin B that is connected to the rotary encoder. Sometimes called data.</param>
/// <param name="edges">The pin event types to 'listen' for.</param>
private void Initialize(int pinA, int pinB, PinEventTypes edges)
{
_pinA = pinA;
_pinB = pinB;
_controller.OpenPin(_pinA, PinMode.Input);
_controller.OpenPin(_pinB, PinMode.Input);
_debouncer.Start();
_controller.RegisterCallbackForPinValueChangedEvent(
_pinA,
edges,
(o, e) =>
{
if (_debounceMillisec == 0 | _debouncer.ElapsedMilliseconds > _debounceMillisec)
{
OnPulse(_controller.Read(_pinA) == _controller.Read(_pinB), (int)_debouncer.ElapsedMilliseconds);
}
_debouncer.Restart();
});
}
/// <inheritdoc/>
public void Dispose()
{
_controller?.ClosePin(_pinA);
_controller?.ClosePin(_pinB);
if (_disposeController)
{
_controller?.Dispose();
}
}
}
}