COSA
An Object-Oriented Platform for Arduino Programming
Rotary.cpp
Go to the documentation of this file.
1 
22 #include "Rotary.hh"
23 
24 /*
25  * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
26  * Contact: bb@cactii.net
27  *
28  * A typical mechanical rotary encoder emits a two bit gray code
29  * on 3 output pins. Every step in the output (often accompanied
30  * by a physical 'click') generates a specific sequence of output
31  * codes on the pins.
32  *
33  * There are 3 pins used for the rotary encoding - one common and
34  * two 'bit' pins.
35  *
36  * The following is the typical sequence of code on the output when
37  * moving from one step to the next:
38  *
39  * Position Bit1 Bit2
40  * ----------------------
41  * Step1 0 0
42  * 1/4 1 0
43  * 1/2 1 1
44  * 3/4 0 1
45  * Step2 0 0
46  *
47  * From this table, we can see that when moving from one 'click' to
48  * the next, there are 4 changes in the output code.
49  *
50  * - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
51  * - Then both bits are high, halfway through the step.
52  * - Then Bit1 goes low, but Bit2 stays high.
53  * - Finally at the end of the step, both bits return to 0.
54  *
55  * Detecting the direction is easy - the table simply goes in the other
56  * direction (read up instead of down).
57  *
58  * To decode this, we use a simple state machine. Every time the output
59  * code changes, it follows state, until finally a full steps worth of
60  * code is received (in the correct order). At the final 0-0, it returns
61  * a value indicating a step in one direction or the other.
62  *
63  * It's also possible to use 'half-step' mode. This just emits an event
64  * at both the 0-0 and 1-1 positions. This might be useful for some
65  * encoders where you want to detect all positions.
66  *
67  * If an invalid state happens (for example we go from '0-1' straight
68  * to '1-0'), the state machine resets to the start until 0-0 and the
69  * next valid codes occur.
70  *
71  * The biggest advantage of using a state machine over other algorithms
72  * is that this has inherent debounce built in. Other algorithms emit
73  * spurious output with switch bounce, but this one will simply flip
74  * between sub-states until the bounce settles, then continue along the
75  * state machine.
76  *
77  * A side effect of debounce is that fast rotations can cause steps to
78  * be skipped. By not requiring debounce, fast rotations can be accurately
79  * measured.
80  *
81  * Another advantage is the ability to properly handle bad state, such
82  * as due to EMI, etc. It is also a lot simpler than others - a static
83  * state table and less than 10 lines of logic.
84  *
85  * @see also
86  * http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
87  */
88 
89 // No complete step yet
90 #define DIR_NONE 0x0
91 // Clockwise step
92 #define DIR_CW 0x10
93 // Anti-clockwise step
94 #define DIR_CCW 0x20
95 
96 // Half-cycle states
97 #define R_START 0x0
98 #define R_CCW_BEGIN 0x1
99 #define R_CW_BEGIN 0x2
100 #define R_START_M 0x3
101 #define R_CW_BEGIN_M 0x4
102 #define R_CCW_BEGIN_M 0x5
103 
137 // R_START (00)
139 // R_CCW_BEGIN
140  {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START},
141 // R_CW_BEGIN
142  {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START},
143 // R_START_M (11)
144  {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START},
145 // R_CW_BEGIN_M
146  {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW},
147 // R_CCW_BEGIN_M
148  {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW},
149 };
150 #undef R_CCW_BEGIN
151 
152 // Full-cycle states
153 #define R_CW_FINAL 0x1
154 #define R_CW_BEGIN 0x2
155 #define R_CW_NEXT 0x3
156 #define R_CCW_BEGIN 0x4
157 #define R_CCW_FINAL 0x5
158 #define R_CCW_NEXT 0x6
159 
196 const uint8_t Rotary::Encoder::full_cycle_table[7][4] __PROGMEM = {
197 // R_START
199 // R_CW_FINAL
200  {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
201 // R_CW_BEGIN
202  {R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
203 // R_CW_NEXT
204  {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
205 // R_CCW_BEGIN
206  {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
207 // R_CCW_FINAL
208  {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
209 // R_CCW_NEXT
210  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
211 };
212 
213 void
214 Rotary::Encoder::SignalPin::on_interrupt(uint16_t arg)
215 {
216  UNUSED(arg);
217  Rotary::Encoder::Direction change = m_encoder->detect();
218  if (change) Event::push(Event::CHANGE_TYPE, m_encoder, change);
219 }
220 
223 {
224  uint8_t pins = ((m_dt.is_set() << 1) | m_clk.is_set());
225  if (m_mode == FULL_CYCLE)
226  m_state = pgm_read_byte(&full_cycle_table[m_state & 0xf][pins]);
227  else
228  m_state = pgm_read_byte(&half_cycle_table[m_state & 0xf][pins]);
229  return ((Direction) (m_state & 0xf0));
230 }
231 
#define R_CCW_FINAL
Definition: Rotary.cpp:157
const uint8_t Rotary::Encoder::half_cycle_table[6][4] __PROGMEM
Definition: Rotary.cpp:136
static const uint8_t half_cycle_table[6][4]
Definition: Rotary.hh:158
#define R_CCW_NEXT
Definition: Rotary.cpp:158
static const uint8_t full_cycle_table[7][4]
Definition: Rotary.hh:161
#define R_START_M
Definition: Rotary.cpp:100
#define DIR_CW
Definition: Rotary.cpp:92
bool is_set() const
Definition: Pin.hh:112
#define R_CW_NEXT
Definition: Rotary.cpp:155
#define UNUSED(x)
Definition: ATmega328P.hh:31
#define R_CCW_BEGIN_M
Definition: Rotary.cpp:102
#define DIR_CCW
Definition: Rotary.cpp:94
#define R_CW_FINAL
Definition: Rotary.cpp:153
SignalPin m_dt
Definition: Rotary.hh:165
SignalPin m_clk
Definition: Rotary.hh:164
#define R_START
Definition: Rotary.cpp:97
#define R_CCW_BEGIN
Definition: Rotary.cpp:156
uint8_t m_state
Definition: Rotary.hh:166
#define R_CW_BEGIN
Definition: Rotary.cpp:154
Direction detect()
Definition: Rotary.cpp:222
static bool push(uint8_t type, Handler *target, uint16_t value=0)
Definition: Event.hh:180
#define R_CW_BEGIN_M
Definition: Rotary.cpp:101