1
+ /*
2
+ * dmRrotary.h - A simple decoder to use a KY-040 rotary encoder for browse
3
+ * naviation in Pi1541.
4
+ *
5
+ * Copyright © 2019 devMash.com
6
+ *
7
+ * https://devMash.com
8
+ * https://github.com/devMashHub
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
11
+ * software and associated documentation files (the “Software”), to deal in the Software
12
+ * without restriction, including without limitation the rights to use, copy, modify, merge,
13
+ * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
14
+ * to whom the Software is furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in all copies or
17
+ * substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
20
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
22
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
+ * DEALINGS IN THE SOFTWARE.
25
+ *
26
+ */
27
+
28
+ #ifndef DM_ROTARY_H
29
+ #define DM_ROTARY_H
30
+
31
+ extern " C"
32
+ {
33
+ #include " rpi-aux.h"
34
+ #include " rpiHardware.h"
35
+ }
36
+
37
+ // Enable debugging messages on the mini uart
38
+ // #define DM_ROTARY_DEBUG
39
+
40
+
41
+ // ------------------------------------------------------------------------------
42
+ // rotary_result_t
43
+ //
44
+ typedef enum {
45
+ NoChange = 0 ,
46
+ ButtonDown = 1 ,
47
+ ButtonUp = 2 ,
48
+ RotatePositive = 3 , // clockwise rotation
49
+ RotateNegative = 4 // counter-clockwise rotation
50
+ } rotary_result_t ;
51
+
52
+
53
+ // ------------------------------------------------------------------------------
54
+ // RotaryPin
55
+ //
56
+ // NOTES:
57
+ //
58
+ // This helper class represents a single rotary encoder pin to encapsulate
59
+ // debouncing and state engine logic.
60
+ //
61
+ class RotaryPin
62
+ {
63
+
64
+ private:
65
+
66
+ rpi_gpio_pin_t _gpioPin = RPI_GPIO0;
67
+
68
+ int _count = 0 ;
69
+ int _threshold = 256 ; // I just like powers of two
70
+
71
+ bool _currentState = false ;
72
+
73
+ public:
74
+
75
+ rpi_gpio_pin_t GetGpioPin () const { return _gpioPin; }
76
+ void SetGpioPin (rpi_gpio_pin_t value) { _gpioPin = value; }
77
+
78
+ unsigned GetGpioPinMask () { return (1 << _gpioPin); }
79
+
80
+ bool GetState () const { return _currentState; }
81
+
82
+ void Update (bool state)
83
+ {
84
+
85
+ _count += state ? 1 : -1 ;
86
+
87
+ bool newState = _currentState;
88
+
89
+ if (_count <= 0 )
90
+ {
91
+ _count = 0 ;
92
+ newState = false ;
93
+ }
94
+ else if (_count >= _threshold)
95
+ {
96
+ _count = _threshold;
97
+ newState = true ;
98
+ }
99
+
100
+ _currentState = newState;
101
+
102
+ }
103
+
104
+ };
105
+
106
+
107
+ // ------------------------------------------------------------------------------
108
+ // RotaryEncoder
109
+ //
110
+ // NOTES:
111
+ //
112
+ // The KY-040 rotary encoder has the following pins:
113
+ //
114
+ // CLK - Encoder pin A
115
+ // DT - Encoder pin B
116
+ // SW - Pushbutton switch
117
+ // VCC - Supply voltage
118
+ // GND - Ground
119
+ //
120
+ //
121
+ // Decoding the KY-040
122
+ //
123
+ // Determining which encoder pin changes state first is how the direction
124
+ // of rotation is determined. If CLK changes state first, the encoder
125
+ // is rotating in a positive (clockwise) direction. If DT changes state
126
+ // first, the encoder is rotating in a negative (counter-clockwise) direction.
127
+ //
128
+ // The entire rotation sequence can be represented as a predictable bit
129
+ // pattern by mapping the CLK state in the higher bit and mapping the DT
130
+ // state in the lower bit:
131
+ //
132
+ // Positive rotation - 00 -> 10 -> 11 -> 01 -> 00 (0xb4)
133
+ // Negative rotation - 00 -> 01 -> 11 -> 10 -> 00 (0x78)
134
+ //
135
+ // To decode, we simply monitor the CLK and DT and watch for one of these
136
+ // two patterns.
137
+ //
138
+ // However, in the real world, some pin transitions can be missed due to
139
+ // lag during the polling interval or contact bounce - resulting in less
140
+ // than perfect decode sequences. To combat against this, the code will
141
+ // also accept several permutations that are 'close enough':
142
+ //
143
+ // Positive (clockwise) rotation:
144
+ //
145
+ // 0xb4 - 00 10 11 01 00 - Received and decoded perfect sequence
146
+ // 0x2c - 00 10 11 00 - Missed data but decoded unique sequence
147
+ // 0x34 - 00 11 01 00 - Missed data but decoded unique sequence
148
+ // 0xb8 - 00 10 11 10 00 - Invalid but usually denotes positive
149
+ //
150
+ // Detect negative (counter-clockwise) rotation:
151
+ //
152
+ // 0x78 - 00 01 11 10 00 - Received and decoded perfect sequence
153
+ // 0x1c - 00 01 11 00 - Missed data but decoded unique sequence
154
+ // 0x38 - 00 11 10 00 - Missed data but decoded unique sequence
155
+ // 0x74 - 00 01 11 01 00 - Invalid but usually denotes negative
156
+ //
157
+ //
158
+ // Wiring the KY-040
159
+ //
160
+ // The GPIO pins used for the rotary encoder are specified when initializing
161
+ // the class. However, it is probably a good idea to reuse the same pins as
162
+ // the original Pi1541 pushbuttons:
163
+ //
164
+ // GPIO 22 - Menu up - Encoder pin A (CLK)
165
+ // GPIO 23 - Menu down - Encoder pin B (DT)
166
+ // GPIO 27 - Enter/Select - Encoder pushbutton (SW)
167
+ //
168
+ //
169
+ // USAGE:
170
+ //
171
+ // *** Please Note! I have only tried this with a Raspberry Pi 3. I have no
172
+ // reason to believe this wouldn't also work on an earlier model, but your
173
+ // results may vary!
174
+ //
175
+ // To use the RotaryEncoder, instance the class and initialize with the desired
176
+ // GPIO pins like this:
177
+ //
178
+ // //Initialize using CLK on GPIO22, DT on GPIO32 and SW on GPIO27
179
+ // RotaryEncoder rotaryEncoder;
180
+ // rotaryEncoder.Initialize(RPI_GPIO22, RPI_GPIO23, RPI_GPIO27);
181
+ //
182
+ // Monitor the encoder by calling the Poll() method during your main processing
183
+ // loop. The polling logic is constrained by only evaluating GPLEV0, which
184
+ // restricts usable pins to GPIO00 through GPIO31. An overloaded version of
185
+ // Poll() exists to accept a value representing GPLEV0 without having to
186
+ // perform a re-read (providing a small optimization when the current value of
187
+ // GPLEV0 is already available).
188
+ //
189
+ // // Read GPLEV0 and decode the rotary state
190
+ // rotary_result_t result = rotaryEncoder.Poll();
191
+ //
192
+ // or
193
+ //
194
+ // // Read GPLEV0 locally and decode the rotary state
195
+ // unsigned gplev0 = read32(ARM_GPIO_GPLEV0);
196
+ // { some other logic here }
197
+ // rotary_result_t result = rotaryEncoder.Poll(gplev0);
198
+ //
199
+ // Note, the Poll() logic depends on frequent polling of the encoder. Calling
200
+ // Poll() as often as possible/permissable will yield better decode results.
201
+ //
202
+ // Any event detected by the polling logic will be returned as the result
203
+ // from the polling method as a rotary_result_t. The controlling logic can
204
+ // then take whatever action is appropriate based on the result.
205
+ //
206
+ //
207
+ // HISTORY:
208
+ //
209
+ // 09/03/2019 - Initial implementation and notes
210
+ // 09/04/2019 - Integration into Pi1541 with shim logic to simulate 'original style' button presses
211
+ // 09/05/2019 - Code cleanup, improved documentation, options.txt logic, dynamic button indexes
212
+ //
213
+ class RotaryEncoder {
214
+
215
+ private:
216
+
217
+ // Switch data
218
+
219
+ RotaryPin _switchPin;
220
+
221
+ bool _currentSwitchState = false ;
222
+
223
+ // Rotation data
224
+
225
+ RotaryPin _clockPin;
226
+ RotaryPin _dataPin;
227
+
228
+ int _currentRotaryState = 0 ;
229
+ int _currentRotarySequence = 0 ;
230
+
231
+ // Private methods
232
+
233
+ void SetGpioPullUpDown (unsigned controlSignal, unsigned gpioPinMask);
234
+
235
+ #ifdef DM_ROTARY_DEBUG
236
+ void WriteToMiniUart (char * pMessage);
237
+ #endif
238
+
239
+ public:
240
+
241
+ // Public methods
242
+
243
+ void Initialize (rpi_gpio_pin_t clkGpioPin, rpi_gpio_pin_t dtGpioPin, rpi_gpio_pin_t swGpioPin);
244
+
245
+ rotary_result_t Poll ();
246
+ rotary_result_t Poll (unsigned gplev0);
247
+
248
+ };
249
+
250
+ #endif
0 commit comments