Skip to content

Commit 8e4de83

Browse files
authored
Merge pull request #1 from huadiweilaoqy/master
RotaryEncoder Arduino Library
2 parents 331df7c + 0b9a1a8 commit 8e4de83

File tree

6 files changed

+441
-0
lines changed

6 files changed

+441
-0
lines changed

GroveEncoder.cpp

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/**
2+
* GroveEncoder.cpp - Grove Encoder library
3+
*
4+
* Copyright (C) 2016 David Antler
5+
* All rights reserved.
6+
*
7+
* This software may be modified and distributed under the terms
8+
* of the included license. See the LICENSE file for details.
9+
*/
10+
11+
#include "GroveEncoder.h"
12+
#include "Arduino.h"
13+
14+
/***************** HELPER FUNCTIONS ********************/
15+
16+
#define GROVE_ENC_LOGGING 0
17+
18+
#if GROVE_ENC_LOGGING
19+
#define DUMP_STATE() { \
20+
Serial.println( "* * * DUMP * * *" ); \
21+
Serial.print( " pinA : " ); Serial.print( pinA ); Serial.println( "" ); \
22+
Serial.print( " pinB : " ); Serial.print( pinB ); Serial.println( "" ); \
23+
Serial.print( " stage : " ); Serial.print( stage - STAGE_ZERO, HEX ); Serial.println( "" ); \
24+
Serial.print( " rot : " ); Serial.print( rotation ); Serial.println( "" ); \
25+
}
26+
#else
27+
#define DUMP_STATE() {}
28+
#endif
29+
30+
/* Unfortunately we need to use a singleton */
31+
GroveEncoder* singleton;
32+
33+
/* State machine for parsing encoded data */
34+
enum Stage {
35+
STAGE_UNDEF = 0x0f,
36+
STAGE_ZERO = 0x10,
37+
STAGE_ONE = 0x11,
38+
STAGE_TWO = 0x12,
39+
STAGE_THREE = 0x13,
40+
STAGE_FOUR = 0x14
41+
};
42+
43+
/* Another piece of state for rotation state machine */
44+
enum Rotation {
45+
CLOCKWISE = 2,
46+
COUNTERCLOCKWISE = 3,
47+
UNCERTAIN = 4
48+
};
49+
50+
#define INDEX_MASK( indexValue ) (indexValue & (PIN_DATA_SIZE - 1) )
51+
52+
inline bool GroveEncoder::pushToQueue( unsigned char myValue )
53+
{
54+
if ( queueIsFull() )
55+
{
56+
Serial.println( "Queue full, overwriting!" );
57+
}
58+
pinDataQueue[INDEX_MASK( writeIndex )] = myValue;
59+
writeIndex++;
60+
return(true);
61+
}
62+
63+
64+
inline bool GroveEncoder::queueIsEmpty()
65+
{
66+
return(writeIndex == readIndex);
67+
}
68+
69+
70+
inline bool GroveEncoder::queueIsFull()
71+
{
72+
return( (writeIndex - readIndex) >= PIN_DATA_SIZE);
73+
}
74+
75+
76+
inline unsigned char GroveEncoder::popFromQueue()
77+
{
78+
unsigned char outValue = 0xFF;
79+
if ( !queueIsEmpty() )
80+
{
81+
outValue = pinDataQueue[INDEX_MASK( readIndex )];
82+
readIndex++;
83+
} else {
84+
Serial.println( "Popped from empty queue" );
85+
}
86+
return(outValue);
87+
}
88+
89+
90+
/* This function only exists due to the singleton issue. */
91+
void GroveEncoder::privateIntHandler()
92+
{
93+
singleton->updateEncoderFast();
94+
}
95+
96+
97+
/* This routine is supposed to be very very fast. */
98+
void GroveEncoder::updateEncoderFast()
99+
{
100+
unsigned char newValue;
101+
newValue = (unsigned char) (digitalRead( LOW_PIN ) + (digitalRead( HIGH_PIN ) << 1) );
102+
/* Push to Queue */
103+
pushToQueue( newValue );
104+
/* Do deferrable "slow" work when steady (e.g. both pins are high) */
105+
if ( newValue == 3)
106+
{
107+
processQueue();
108+
109+
if ( optCallBack != NULL )
110+
{
111+
//Serial.println("done");
112+
/* Only process the callback if there's new data to post. */
113+
static int prevValue = 0xDEADBEEF; /* squelch the warning. */
114+
bool bt_flag = button_flag;
115+
if ( value != prevValue || bt_flag )
116+
{
117+
prevValue = value;
118+
optCallBack( value, bt_flag );
119+
}
120+
}
121+
}
122+
}
123+
124+
125+
#define GET_A_NEW_BYTE() { \
126+
if ( queueIsEmpty() ) return; \
127+
unsigned char pins = popFromQueue(); \
128+
pinA = pins & 0x01; \
129+
pinB = (pins & 0x02) >> 1; \
130+
}
131+
132+
133+
/**
134+
* Processes the queue of bytes
135+
*/
136+
void GroveEncoder::processQueue()
137+
{
138+
int pinA, pinB;
139+
char stage = STAGE_ZERO;
140+
Rotation rotation = UNCERTAIN;
141+
142+
GET_A_NEW_BYTE();
143+
144+
do
145+
{
146+
DUMP_STATE();
147+
if ( (pinA == pinB) && (pinB == HIGH) )
148+
{
149+
GET_A_NEW_BYTE();
150+
stage = STAGE_ZERO;
151+
rotation = UNCERTAIN;
152+
continue;
153+
}
154+
switch ( stage )
155+
{
156+
case STAGE_ZERO:
157+
/*
158+
* Stage zero means that we might be at the "rising" edge of our rotation.
159+
* Decide if CW, CCW, or throw-away.
160+
*/
161+
if ( pinA > pinB )
162+
{
163+
/* clockwise */
164+
button_flag = 0;
165+
rotation = CLOCKWISE;
166+
stage = STAGE_ONE;
167+
} else if ( pinA < pinB )
168+
{
169+
/* ccw */
170+
button_flag = 0;
171+
//Serial.println("ccw");
172+
rotation = COUNTERCLOCKWISE;
173+
stage = STAGE_ONE;
174+
}
175+
else if(pinA == pinB)
176+
{
177+
/* button */
178+
//Serial.println("bt");
179+
button_flag = 1;
180+
stage = STAGE_ONE;
181+
}
182+
GET_A_NEW_BYTE();
183+
break;
184+
185+
case STAGE_ONE:
186+
/*
187+
* In stage one, we've identified our direction, but have not yet seen double-zeros.
188+
* Once we see double zeros, lets fall through to stage two.
189+
*/
190+
if ( (LOW == pinB) && (pinA == LOW) )
191+
{
192+
stage = STAGE_TWO;
193+
continue;
194+
}
195+
196+
stage = STAGE_ZERO;
197+
break;
198+
199+
case STAGE_TWO:
200+
/* Stage two means we have seen double zeros, but can escape by getting anything else. */
201+
if ( (pinB == LOW) && (pinA == LOW) )
202+
{
203+
GET_A_NEW_BYTE();
204+
continue;
205+
} else if ( pinA ^ pinB )
206+
{
207+
stage = STAGE_THREE;
208+
continue;
209+
} else {
210+
stage = STAGE_ZERO;
211+
continue;
212+
}
213+
214+
case STAGE_THREE:
215+
/*
216+
* Stage three means that we are at the "falling" edge of our rotation.
217+
* Lets check to make sure we are being consistent.
218+
*/
219+
if ( rotation == CLOCKWISE )
220+
{
221+
if ( pinA < pinB )
222+
{
223+
stage = STAGE_FOUR;
224+
} else {
225+
/* failed to stay consistent! */
226+
stage = STAGE_ZERO;
227+
}
228+
}else if ( rotation == COUNTERCLOCKWISE )
229+
{
230+
if ( pinA > pinB )
231+
{
232+
stage = STAGE_FOUR;
233+
} else {
234+
/* failed to stay consistent! */
235+
stage = STAGE_ZERO;
236+
}
237+
}
238+
continue;
239+
240+
case STAGE_FOUR:
241+
/* Stage four is logging the results! */
242+
if ( rotation == CLOCKWISE )
243+
{
244+
value++;
245+
} else if ( rotation == COUNTERCLOCKWISE )
246+
{
247+
value--;
248+
}
249+
stage = STAGE_ZERO;
250+
GET_A_NEW_BYTE();
251+
continue;
252+
253+
default:
254+
Serial.println( "Default is bad" );
255+
DUMP_STATE();
256+
break;
257+
}
258+
}
259+
while ( !queueIsEmpty() );
260+
}
261+
262+
263+
/***************** API FUNCTIONS ********************/
264+
265+
/* Gets the value on the encoder. It's an integer, positive or negative. */
266+
int GroveEncoder::getValue()
267+
{
268+
return(value);
269+
}
270+
271+
272+
void GroveEncoder::resetValue()
273+
{
274+
setValue( 0 );
275+
}
276+
277+
278+
void GroveEncoder::setValue( int newValue )
279+
{
280+
value = newValue;
281+
}
282+
283+
284+
/**
285+
* This will enumerate a GroveEncoder on a particular pin.
286+
* You can provide an optional callback, or poll the "getValue()" API.
287+
*/
288+
GroveEncoder::GroveEncoder( int pin, void(*optionalCallBack)(int, bool) )
289+
{
290+
/* Needed to make GroveEncoder a singleton because attachInterrupt doesn't take a parameter! */
291+
singleton = this;
292+
#if GROVE_ENC_LOGGING
293+
Serial.begin( 9600 );
294+
Serial.print( "Starting Grove Encoder debug logging..." );
295+
#endif
296+
297+
/* Initialize values */
298+
writeIndex = readIndex = 0;
299+
LOW_PIN = pin;
300+
HIGH_PIN = pin + 1;
301+
value = 0;
302+
optCallBack = optionalCallBack;
303+
/* Initialize pins */
304+
pinMode( LOW_PIN, INPUT_PULLUP );
305+
pinMode( HIGH_PIN, INPUT_PULLUP );
306+
307+
#if GROVE_ENC_LOGGING
308+
/* Expect these to be low right now. */
309+
if ( digitalRead( LOW_PIN ) || digitalRead( HIGH_PIN ) )
310+
{
311+
/* Unless you're actively playing with the encoder, these will be low... */
312+
Serial.print( "Grove Encoder library thinks you have a wiring issue!" );
313+
}
314+
#endif
315+
316+
/* Set up interrupts */
317+
attachInterrupt( digitalPinToInterrupt( LOW_PIN ), privateIntHandler, CHANGE );
318+
attachInterrupt( digitalPinToInterrupt( HIGH_PIN ), privateIntHandler, CHANGE );
319+
}
320+
321+

GroveEncoder.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* GroveEncoder.h - Header for Grove Encoder library
3+
*
4+
* Copyright (C) 2016 David Antler
5+
* All rights reserved.
6+
*
7+
* This software may be modified and distributed under the terms
8+
* of the included license. See the LICENSE file for details.
9+
*/
10+
11+
#ifndef GROVE_ENCODER_LIB_H_
12+
#define GROVE_ENCODER_LIB_H_
13+
14+
/* Note: PIN_DATA_SIZE must be a power of two due to the queue implementation. */
15+
#define PIN_DATA_SIZE 128
16+
17+
class GroveEncoder {
18+
public:
19+
GroveEncoder( int pin, void (*optionalCallBack)( int, bool ) );
20+
int getValue();
21+
int button_flag;
22+
void setValue( int newValue );
23+
24+
25+
void resetValue();
26+
27+
28+
/* This is private, but it needs to be public/static */
29+
static void privateIntHandler();
30+
31+
32+
void updateEncoderFast();
33+
34+
35+
private:
36+
int LOW_PIN, HIGH_PIN;
37+
volatile int value;
38+
int readIndex;
39+
int writeIndex;
40+
unsigned char pinDataQueue[PIN_DATA_SIZE];
41+
void (*optCallBack)( int, bool );
42+
inline bool pushToQueue( unsigned char myValue );
43+
44+
45+
inline bool queueIsEmpty();
46+
47+
48+
inline bool queueIsFull();
49+
50+
51+
inline unsigned char popFromQueue();
52+
53+
54+
void processQueue();
55+
};
56+
57+
#endif
58+

0 commit comments

Comments
 (0)