altos/driver: Improve quadrature debouncing for mechanical encoders
[fw/altos] / src / drivers / ao_quadrature.c
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #include <ao.h>
20 #include <ao_quadrature.h>
21 #include <ao_exti.h>
22 #include <ao_fast_timer.h>
23 #include <ao_event.h>
24
25 __xdata int32_t ao_quadrature_count[AO_QUADRATURE_COUNT];
26 #ifndef AO_QUADRATURE_SINGLE_CODE
27 static int8_t ao_quadrature_step[AO_QUADRATURE_COUNT];
28 #endif
29
30 static uint8_t  ao_quadrature_state[AO_QUADRATURE_COUNT];
31
32 struct ao_debounce {
33         uint8_t state;
34         uint8_t count;
35 };
36
37 static struct ao_debounce ao_debounce_state[AO_QUADRATURE_COUNT][2];
38
39 #define port(q) AO_QUADRATURE_ ## q ## _PORT
40 #define bita(q) AO_QUADRATURE_ ## q ## _A
41 #define bitb(q) AO_QUADRATURE_ ## q ## _B
42 #define pina(q) AO_QUADRATURE_ ## q ## _A ## _PIN
43 #define pinb(q) AO_QUADRATURE_ ## q ## _B ## _PIN
44 #define isr(q)  ao_quadrature_isr_ ## q
45
46 #ifndef AO_QUADRATURE_DEBOUNCE
47 #error must define AO_QUADRATURE_DEBOUNCE
48 #endif
49
50 static uint8_t
51 ao_debounce(uint8_t cur, struct ao_debounce *debounce)
52 {
53 #if AO_QUADRATURE_DEBOUNCE > 0
54         if (debounce->count > 0) {
55                 debounce->count--;
56         } else if (cur != debounce->state) {
57                 debounce->state = cur;
58                 debounce->count = AO_QUADRATURE_DEBOUNCE;
59         }
60         return debounce->state;
61 #else
62         (void) debounce;
63         return cur;
64 #endif
65 }
66
67 static uint16_t
68 ao_quadrature_read(struct stm_gpio *gpio, uint8_t pin_a, uint8_t pin_b, struct ao_debounce debounce_state[2]) {
69         uint16_t        v = ~stm_gpio_get_all(gpio);
70         uint8_t         a = (v >> pin_a) & 1;
71         uint8_t         b = (v >> pin_b) & 1;
72
73         a = ao_debounce(a, &debounce_state[0]);
74         b = ao_debounce(b, &debounce_state[1]);
75
76         return a | (b << 1);
77 }
78
79 #define _ao_quadrature_get(q)   ao_quadrature_read(port(q), bita(q), bitb(q), ao_debounce_state[q])
80
81 static void
82 _ao_quadrature_step(uint8_t q, int8_t step)
83 {
84 #ifndef AO_QUADRATURE_SINGLE_CODE
85         ao_quadrature_step[q] += step;
86         if (ao_quadrature_state[q] != 0)
87                 return;
88         if (ao_quadrature_step[q] >= 4) {
89                 ao_quadrature_step[q] = 0;
90                 step = 1;
91         } else if (ao_quadrature_step[q] <= -4) {
92                 ao_quadrature_step[q] = 0;
93                 step = -1;
94         } else
95                 return;
96 #endif
97         ao_quadrature_count[q] += step;
98 #if AO_EVENT
99         ao_event_put_isr(AO_EVENT_QUADRATURE, q, step);
100 #endif
101         ao_wakeup(&ao_quadrature_count[q]);
102 }
103
104 static const struct {
105         uint8_t prev, next;
106 } ao_quadrature_steps[4] = {
107         [0] { .prev = 2, .next = 1 },
108         [1] { .prev = 0, .next = 3 },
109         [3] { .prev = 1, .next = 2 },
110         [2] { .prev = 3, .next = 0 },
111 };
112
113 static void
114 _ao_quadrature_set(uint8_t q, uint8_t new) {
115         uint8_t old;
116
117         ao_arch_block_interrupts();
118         old = ao_quadrature_state[q];
119         ao_quadrature_state[q] = new;
120         ao_arch_release_interrupts();
121
122         if (new == ao_quadrature_steps[old].next)
123                 _ao_quadrature_step(q, 1);
124         else if (new == ao_quadrature_steps[old].prev)
125                 _ao_quadrature_step(q, -1);
126 }
127
128 static void
129 ao_quadrature_isr(void)
130 {
131 #if AO_QUADRATURE_COUNT > 0
132         _ao_quadrature_set(0, _ao_quadrature_get(0));
133 #endif
134 #if AO_QUADRATURE_COUNT > 1
135         _ao_quadrature_set(1, _ao_quadrature_get(1));
136 #endif
137 }
138
139 int32_t
140 ao_quadrature_poll(uint8_t q)
141 {
142         int32_t ret;
143         ao_arch_critical(ret = ao_quadrature_count[q];);
144         return ret;
145 }
146
147 int32_t
148 ao_quadrature_wait(uint8_t q)
149 {
150         ao_sleep(&ao_quadrature_count[q]);
151         return ao_quadrature_poll(q);
152 }
153
154 static void
155 ao_quadrature_test(void)
156 {
157         uint8_t q;
158         int32_t c;
159         uint8_t s;
160 #ifndef AO_QUADRATURE_SINGLE_CODE
161         int8_t t = 0;
162 #endif
163
164         ao_cmd_decimal();
165         q = ao_cmd_lex_i;
166         if (q >= AO_QUADRATURE_COUNT)
167                 ao_cmd_status = ao_cmd_syntax_error;
168         if (ao_cmd_status != ao_cmd_success)
169                 return;
170
171         c = -10000;
172         s = 0;
173         while (ao_quadrature_count[q] != 10) {
174                 if (ao_quadrature_count[q] != c ||
175 #ifndef AO_QUADRATURE_SINGLE_CODE
176                     ao_quadrature_step[q] != t ||
177 #endif
178                     ao_quadrature_state[q] != s)
179                 {
180                         c = ao_quadrature_count[q];
181                         s = ao_quadrature_state[q];
182 #ifndef AO_QUADRATURE_SINGLE_CODE
183                         t = ao_quadrature_step[q];
184                         printf("step %3d ", t);
185 #endif
186                         printf ("count %3d state %2x\n", c, s);
187                         flush();
188                 }
189         }
190 #if 0
191         for (;;) {
192                 int32_t c;
193                 flush();
194                 c = ao_quadrature_wait(q);
195                 printf ("new count %6d\n", c);
196                 if (c == 100)
197                         break;
198         }
199 #endif
200 }
201
202 static const struct ao_cmds ao_quadrature_cmds[] = {
203         { ao_quadrature_test,   "q <unit>\0Test quadrature" },
204         { 0, NULL }
205 };
206
207 #define init(q) do {                                    \
208                 ao_enable_input(port(q), bita(q), 0);   \
209                 ao_enable_input(port(q), bitb(q), 0);   \
210         } while (0)
211
212 void
213 ao_quadrature_init(void)
214 {
215 #if AO_QUADRATURE_COUNT > 0
216         init(0);
217 #endif
218 #if AO_QUADRATURE_COUNT > 1
219         init(1);
220 #endif
221         ao_fast_timer_init();
222         ao_fast_timer_on(ao_quadrature_isr);
223         ao_cmd_register(&ao_quadrature_cmds[0]);
224 }