|
1 /* |
|
2 * Copyright (c) 2019 David Antliff |
|
3 * Copyright 2011 Ben Buxton |
|
4 * |
|
5 * This file is part of the esp32-rotary-encoder component. |
|
6 * |
|
7 * esp32-rotary-encoder is free software: you can redistribute it and/or modify |
|
8 * it under the terms of the GNU General Public License as published by |
|
9 * the Free Software Foundation, either version 3 of the License, or |
|
10 * (at your option) any later version. |
|
11 * |
|
12 * esp32-rotary-encoder is distributed in the hope that it will be useful, |
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 * GNU General Public License for more details. |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License |
|
18 * along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>. |
|
19 */ |
|
20 |
|
21 /** |
|
22 * @file rotary_encoder.c |
|
23 * @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component. |
|
24 * |
|
25 * Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary |
|
26 * Original header follows: |
|
27 * |
|
28 * Rotary encoder handler for arduino. v1.1 |
|
29 * |
|
30 * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. |
|
31 * Contact: bb@cactii.net |
|
32 * |
|
33 * A typical mechanical rotary encoder emits a two bit gray code |
|
34 * on 3 output pins. Every step in the output (often accompanied |
|
35 * by a physical 'click') generates a specific sequence of output |
|
36 * codes on the pins. |
|
37 * |
|
38 * There are 3 pins used for the rotary encoding - one common and |
|
39 * two 'bit' pins. |
|
40 * |
|
41 * The following is the typical sequence of code on the output when |
|
42 * moving from one step to the next: |
|
43 * |
|
44 * Position Bit1 Bit2 |
|
45 * ---------------------- |
|
46 * Step1 0 0 |
|
47 * 1/4 1 0 |
|
48 * 1/2 1 1 |
|
49 * 3/4 0 1 |
|
50 * Step2 0 0 |
|
51 * |
|
52 * From this table, we can see that when moving from one 'click' to |
|
53 * the next, there are 4 changes in the output code. |
|
54 * |
|
55 * - From an initial 0 - 0, Bit1 goes high, Bit0 stays low. |
|
56 * - Then both bits are high, halfway through the step. |
|
57 * - Then Bit1 goes low, but Bit2 stays high. |
|
58 * - Finally at the end of the step, both bits return to 0. |
|
59 * |
|
60 * Detecting the direction is easy - the table simply goes in the other |
|
61 * direction (read up instead of down). |
|
62 * |
|
63 * To decode this, we use a simple state machine. Every time the output |
|
64 * code changes, it follows state, until finally a full steps worth of |
|
65 * code is received (in the correct order). At the final 0-0, it returns |
|
66 * a value indicating a step in one direction or the other. |
|
67 * |
|
68 * It's also possible to use 'half-step' mode. This just emits an event |
|
69 * at both the 0-0 and 1-1 positions. This might be useful for some |
|
70 * encoders where you want to detect all positions. |
|
71 * |
|
72 * If an invalid state happens (for example we go from '0-1' straight |
|
73 * to '1-0'), the state machine resets to the start until 0-0 and the |
|
74 * next valid codes occur. |
|
75 * |
|
76 * The biggest advantage of using a state machine over other algorithms |
|
77 * is that this has inherent debounce built in. Other algorithms emit spurious |
|
78 * output with switch bounce, but this one will simply flip between |
|
79 * sub-states until the bounce settles, then continue along the state |
|
80 * machine. |
|
81 * A side effect of debounce is that fast rotations can cause steps to |
|
82 * be skipped. By not requiring debounce, fast rotations can be accurately |
|
83 * measured. |
|
84 * Another advantage is the ability to properly handle bad state, such |
|
85 * as due to EMI, etc. |
|
86 * It is also a lot simpler than others - a static state table and less |
|
87 * than 10 lines of logic. |
|
88 */ |
|
89 |
|
90 #include "rotary_encoder.h" |
|
91 |
|
92 #include "esp_log.h" |
|
93 #include "driver/gpio.h" |
|
94 |
|
95 #define TAG "rotary_encoder" |
|
96 |
|
97 //#define ROTARY_ENCODER_DEBUG |
|
98 |
|
99 // Use a single-item queue so that the last value can be easily overwritten by the interrupt handler |
|
100 #define EVENT_QUEUE_LENGTH 1 |
|
101 |
|
102 #define TABLE_ROWS 7 |
|
103 |
|
104 #define DIR_NONE 0x0 // No complete step yet. |
|
105 #define DIR_CW 0x10 // Clockwise step. |
|
106 #define DIR_CCW 0x20 // Anti-clockwise step. |
|
107 |
|
108 // Create the half-step state table (emits a code at 00 and 11) |
|
109 #define R_START 0x0 |
|
110 #define H_CCW_BEGIN 0x1 |
|
111 #define H_CW_BEGIN 0x2 |
|
112 #define H_START_M 0x3 |
|
113 #define H_CW_BEGIN_M 0x4 |
|
114 #define H_CCW_BEGIN_M 0x5 |
|
115 |
|
116 static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = { |
|
117 // 00 01 10 11 // BA |
|
118 {H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00) |
|
119 {H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN |
|
120 {H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN |
|
121 {H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11) |
|
122 {H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M |
|
123 {H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M |
|
124 }; |
|
125 |
|
126 // Create the full-step state table (emits a code at 00 only) |
|
127 # define F_CW_FINAL 0x1 |
|
128 # define F_CW_BEGIN 0x2 |
|
129 # define F_CW_NEXT 0x3 |
|
130 # define F_CCW_BEGIN 0x4 |
|
131 # define F_CCW_FINAL 0x5 |
|
132 # define F_CCW_NEXT 0x6 |
|
133 |
|
134 static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = { |
|
135 // 00 01 10 11 // BA |
|
136 {R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START |
|
137 {F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL |
|
138 {F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN |
|
139 {F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT |
|
140 {F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN |
|
141 {F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL |
|
142 {F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT |
|
143 }; |
|
144 |
|
145 static uint8_t _process(rotary_encoder_info_t * info) |
|
146 { |
|
147 uint8_t event = 0; |
|
148 if (info != NULL) |
|
149 { |
|
150 // Get state of input pins. |
|
151 uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a); |
|
152 |
|
153 // Determine new state from the pins and state table. |
|
154 #ifdef ROTARY_ENCODER_DEBUG |
|
155 uint8_t old_state = info->table_state; |
|
156 #endif |
|
157 info->table_state = info->table[info->table_state & 0xf][pin_state]; |
|
158 |
|
159 // Return emit bits, i.e. the generated event. |
|
160 event = info->table_state & 0x30; |
|
161 #ifdef ROTARY_ENCODER_DEBUG |
|
162 ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x", |
|
163 pin_state >> 1, pin_state & 1, old_state, info->table_state, event); |
|
164 #endif |
|
165 } |
|
166 return event; |
|
167 } |
|
168 |
|
169 static void _isr_rotenc(void * args) |
|
170 { |
|
171 rotary_encoder_info_t * info = (rotary_encoder_info_t *)args; |
|
172 uint8_t event = _process(info); |
|
173 bool send_event = false; |
|
174 |
|
175 switch (event) |
|
176 { |
|
177 case DIR_CW: |
|
178 ++info->state.position; |
|
179 info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE; |
|
180 send_event = true; |
|
181 break; |
|
182 case DIR_CCW: |
|
183 --info->state.position; |
|
184 info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE; |
|
185 send_event = true; |
|
186 break; |
|
187 default: |
|
188 break; |
|
189 } |
|
190 |
|
191 if (send_event && info->queue) |
|
192 { |
|
193 rotary_encoder_event_t queue_event = |
|
194 { |
|
195 .state = |
|
196 { |
|
197 .position = info->state.position, |
|
198 .direction = info->state.direction, |
|
199 }, |
|
200 }; |
|
201 BaseType_t task_woken = pdFALSE; |
|
202 xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken); |
|
203 if (task_woken) |
|
204 { |
|
205 portYIELD_FROM_ISR(); |
|
206 } |
|
207 } |
|
208 } |
|
209 |
|
210 esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b) |
|
211 { |
|
212 esp_err_t err = ESP_OK; |
|
213 if (info) |
|
214 { |
|
215 info->pin_a = pin_a; |
|
216 info->pin_b = pin_b; |
|
217 info->table = &_ttable_full[0]; //enable_half_step ? &_ttable_half[0] : &_ttable_full[0]; |
|
218 info->table_state = R_START; |
|
219 info->state.position = 0; |
|
220 info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET; |
|
221 |
|
222 // configure GPIOs |
|
223 gpio_pad_select_gpio(info->pin_a); |
|
224 gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY); |
|
225 gpio_set_direction(info->pin_a, GPIO_MODE_INPUT); |
|
226 gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE); |
|
227 |
|
228 gpio_pad_select_gpio(info->pin_b); |
|
229 gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY); |
|
230 gpio_set_direction(info->pin_b, GPIO_MODE_INPUT); |
|
231 gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE); |
|
232 |
|
233 // install interrupt handlers |
|
234 gpio_isr_handler_add(info->pin_a, _isr_rotenc, info); |
|
235 gpio_isr_handler_add(info->pin_b, _isr_rotenc, info); |
|
236 } |
|
237 else |
|
238 { |
|
239 ESP_LOGE(TAG, "info is NULL"); |
|
240 err = ESP_ERR_INVALID_ARG; |
|
241 } |
|
242 return err; |
|
243 } |
|
244 |
|
245 esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable) |
|
246 { |
|
247 esp_err_t err = ESP_OK; |
|
248 if (info) |
|
249 { |
|
250 info->table = enable ? &_ttable_half[0] : &_ttable_full[0]; |
|
251 info->table_state = R_START; |
|
252 } |
|
253 else |
|
254 { |
|
255 ESP_LOGE(TAG, "info is NULL"); |
|
256 err = ESP_ERR_INVALID_ARG; |
|
257 } |
|
258 return err; |
|
259 } |
|
260 |
|
261 esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info) |
|
262 { |
|
263 esp_err_t err = ESP_OK; |
|
264 if (info) |
|
265 { |
|
266 gpio_num_t temp = info->pin_a; |
|
267 info->pin_a = info->pin_b; |
|
268 info->pin_b = temp; |
|
269 } |
|
270 else |
|
271 { |
|
272 ESP_LOGE(TAG, "info is NULL"); |
|
273 err = ESP_ERR_INVALID_ARG; |
|
274 } |
|
275 return err; |
|
276 } |
|
277 |
|
278 esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info) |
|
279 { |
|
280 esp_err_t err = ESP_OK; |
|
281 if (info) |
|
282 { |
|
283 gpio_isr_handler_remove(info->pin_a); |
|
284 gpio_isr_handler_remove(info->pin_b); |
|
285 } |
|
286 else |
|
287 { |
|
288 ESP_LOGE(TAG, "info is NULL"); |
|
289 err = ESP_ERR_INVALID_ARG; |
|
290 } |
|
291 return err; |
|
292 } |
|
293 |
|
294 QueueHandle_t rotary_encoder_create_queue(void) |
|
295 { |
|
296 return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t)); |
|
297 } |
|
298 |
|
299 esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue) |
|
300 { |
|
301 esp_err_t err = ESP_OK; |
|
302 if (info) |
|
303 { |
|
304 info->queue = queue; |
|
305 } |
|
306 else |
|
307 { |
|
308 ESP_LOGE(TAG, "info is NULL"); |
|
309 err = ESP_ERR_INVALID_ARG; |
|
310 } |
|
311 return err; |
|
312 } |
|
313 |
|
314 esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state) |
|
315 { |
|
316 esp_err_t err = ESP_OK; |
|
317 if (info && state) |
|
318 { |
|
319 // make a snapshot of the state |
|
320 state->position = info->state.position; |
|
321 state->direction = info->state.direction; |
|
322 } |
|
323 else |
|
324 { |
|
325 ESP_LOGE(TAG, "info and/or state is NULL"); |
|
326 err = ESP_ERR_INVALID_ARG; |
|
327 } |
|
328 return err; |
|
329 } |
|
330 |
|
331 esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info) |
|
332 { |
|
333 esp_err_t err = ESP_OK; |
|
334 if (info) |
|
335 { |
|
336 info->state.position = 0; |
|
337 info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET; |
|
338 } |
|
339 else |
|
340 { |
|
341 ESP_LOGE(TAG, "info is NULL"); |
|
342 err = ESP_ERR_INVALID_ARG; |
|
343 } |
|
344 return err; |
|
345 } |