grblHAL core  20260225
encoders.h
Go to the documentation of this file.
1 /*
2  encoders.c - quadrature encoders interface (API)
3 
4  Part of grblHAL
5 
6  Copyright (c) 2026 Terje Io
7 
8  grblHAL is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12 
13  grblHAL is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #ifndef _ENCODERS_H_
23 #define _ENCODERS_H_
24 
25 #include "plugins.h"
26 
27 // Quadrature encoder interface
28 
29 typedef union {
30  uint8_t value;
31  uint8_t events;
32  struct {
33  uint8_t position_changed :1,
35  click :1,
39  unused :2;
40  };
42 
43 typedef union {
44  uint8_t value;
45  uint8_t mask;
46  struct {
47  uint8_t bidirectional :1,
48  select :1,
49  index :1,
52  unused :3;
53  };
55 
56 typedef struct {
57  uint32_t vel_timeout;
58  uint32_t dbl_click_window;
60 
61 typedef struct {
62  int32_t position;
63  uint32_t velocity;
65 
66 struct encoder;
67 typedef struct encoder encoder_t;
68 
74 typedef void (*encoder_on_event_ptr)(encoder_t *encoder, encoder_event_t *events, void *context);
75 
80 
86 typedef bool (*encoder_claim_ptr)(encoder_on_event_ptr event_handler, void *context);
87 
92 typedef encoder_data_t *(*encoder_get_data_ptr)(encoder_t *encoder);
93 
98 typedef bool (*encoder_enumerate_callback_ptr)(encoder_t *encoder, void *data);
99 
106 
108 bool encoders_enumerate (encoder_enumerate_callback_ptr callback, void *data);
109 uint8_t encoders_get_num (void);
110 
111 struct encoder {
117 };
118 
119 #endif // _ENCODERS_H_
120 
121 // Quadrature Encoder Interface - static code for drivers/plugins
122 
123 #if QEI_ENABLE && defined(QEI_A_PIN) && defined(QEI_B_PIN)
124 
125 typedef enum {
126  QEI_DirUnknown = 0,
127  QEI_DirCW,
128  QEI_DirCCW
129 } qei_dir_t;
130 
131 typedef union {
132  uint_fast8_t pins;
133  struct {
134  uint_fast8_t a :1,
135  b :1;
136  };
137 } qei_state_t;
138 
139 typedef struct {
141  encoder_data_t data;
142  encoder_event_t event;
143  void *context;
144  int32_t vel_count;
145  uint_fast16_t state;
146  qei_dir_t dir;
147  uint8_t port_a, port_b, port_select;
148  volatile uint32_t dbl_click_timeout;
149  volatile uint32_t vel_timeout;
150  uint32_t vel_timestamp;
151  encoder_on_event_ptr on_event;
153 } qei_t;
154 
155 static qei_t qei = {
156  .port_a = IOPORT_UNASSIGNED,
157  .port_b = IOPORT_UNASSIGNED,
158  .port_select = IOPORT_UNASSIGNED,
159  .settings.dbl_click_window = 500,
160  .encoder.caps.bidirectional = On
161 };
162 
163 static void qei_post_event (void *data)
164 {
165  qei.on_event(&qei.encoder, &qei.event, qei.context);
166 }
167 
168 static void qei_dblclk_event (void *data)
169 {
170  qei.event.dbl_click = On;
171  qei.on_event(&qei.encoder, &qei.event, qei.context);
172 }
173 
174 static void qei_reset (encoder_t *encoder)
175 {
176  qei.vel_timeout = 0;
177  qei.dir = QEI_DirUnknown;
178  qei.data.position = qei.vel_count = 0;
179  qei.vel_timestamp = hal.get_elapsed_ticks();
180  qei.vel_timeout = qei.settings.vel_timeout;
181 }
182 
183 static bool qei_configure (encoder_t *encoder, encoder_cfg_t *settings)
184 {
185  if(qei.vel_timeout != settings->vel_timeout)
186  qei.vel_timestamp = hal.get_elapsed_ticks();
187 
188  memcpy(&qei.settings, settings, sizeof(encoder_cfg_t));
189 
190  return true;
191 }
192 
193 static encoder_data_t *qei_get_data (encoder_t *encoder)
194 {
195  return &qei.data;
196 }
197 
198 static void qei_poll (void *data)
199 {
200  if(qei.vel_timeout && !(--qei.vel_timeout)) {
201 
202  uint32_t time = hal.get_elapsed_ticks();
203 
204  qei.data.velocity = abs(qei.data.position - qei.vel_count) * 1000 / (time - qei.vel_timestamp);
205  qei.vel_timestamp = time;
206  qei.vel_timeout = qei.settings.vel_timeout;
207  if((qei.event.position_changed = !qei.dbl_click_timeout || qei.data.velocity == 0))
208  qei.on_event(&qei.encoder, &qei.event, qei.context);
209  qei.vel_count = qei.data.position;
210  }
211 
212  if(qei.dbl_click_timeout && !(--qei.dbl_click_timeout)) {
213  qei.event.click = On;
214  task_delete(qei_dblclk_event, NULL);
215  qei.on_event(&qei.encoder, &qei.event, qei.context);
216  }
217 }
218 
219 static void qei_ab_irq (uint8_t port, bool high)
220 {
221  const uint8_t encoder_valid_state[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
222 
223  static qei_state_t state = {0};
224 
225  if(port == qei.port_a)
226  state.a = high;
227  else
228  state.b = high;
229 
230  uint_fast8_t idx = (((qei.state << 2) & 0x0F) | state.pins);
231 
232  if(encoder_valid_state[idx] ) {
233 
234 // int32_t count = qei.count;
235 
236  qei.state = ((qei.state << 4) | idx) & 0xFF;
237 
238  if(qei.state == 0x42 || qei.state == 0xD4 || qei.state == 0x2B || qei.state == 0xBD) {
239  qei.data.position--;
240  if(qei.vel_timeout == 0 || qei.dir == QEI_DirCW) {
241  qei.dir = QEI_DirCCW;
242  qei.event.position_changed = On;
243  task_add_immediate(qei_post_event, NULL);
244  }
245  } else if(qei.state == 0x81 || qei.state == 0x17 || qei.state == 0xE8 || qei.state == 0x7E) {
246  qei.data.position++;
247  if(qei.vel_timeout == 0 || qei.dir == QEI_DirCCW) {
248  qei.dir = QEI_DirCW;
249  qei.event.position_changed = On;
250  task_add_immediate(qei_post_event, NULL);
251  }
252  }
253  }
254 }
255 
256 static void qei_select_irq (uint8_t port, bool high)
257 {
258  if(high)
259  return;
260 
261  if(!qei.dbl_click_timeout) {
262  qei.dbl_click_timeout = qei.settings.dbl_click_window;
263  } else if(qei.dbl_click_timeout < qei.settings.dbl_click_window) {
264  qei.dbl_click_timeout = 0;
265  task_delete(qei_dblclk_event, NULL);
266  task_add_immediate(qei_dblclk_event, NULL);
267  }
268 }
269 
270 static bool qei_claim (encoder_on_event_ptr event_handler, void *context)
271 {
272  if(event_handler == NULL || qei.on_event)
273  return false;
274 
275  qei.context = context;
276  qei.on_event = event_handler;
277  qei.encoder.reset = qei_reset;
278  qei.encoder.get_data = qei_get_data;
279  qei.encoder.configure = qei_configure;
280 
281  if(qei.port_b != IOPORT_UNASSIGNED) {
282  ioport_enable_irq(qei.port_a, IRQ_Mode_Change, qei_ab_irq);
283  ioport_enable_irq(qei.port_b, IRQ_Mode_Change, qei_ab_irq);
284  }
285 
286  if(qei.port_select != IOPORT_UNASSIGNED)
287  ioport_enable_irq(qei.port_select, IRQ_Mode_Change, qei_select_irq);
288 
289  task_add_systick(qei_poll, NULL);
290 
291  return true;
292 }
293 
294 static inline void encoder_pin_claimed (uint8_t port, xbar_t *pin)
295 {
296  switch(pin->function) {
297 
298  case Input_QEI_A:
299  qei.port_a = port;
300  break;
301 
302  case Input_QEI_B:
303  qei.port_b = port;
304  qei.encoder.claim = qei_claim;
305  if(qei.port_a != IOPORT_UNASSIGNED)
306  encoder_register(&qei.encoder);
307  break;
308 
309  case Input_QEI_Select:
310  qei.port_select = port;
311  qei.encoder.caps.select = On;
312  break;
313 
314  default: break;
315  }
316 }
317 
318 #endif
@ Input_QEI_A
Definition: crossbar.h:265
@ Input_QEI_Select
Definition: crossbar.h:267
@ Input_QEI_B
Definition: crossbar.h:266
@ IRQ_Mode_Change
0b00100 (0x04)
Definition: crossbar.h:578
bool(* encoder_configure_ptr)(encoder_t *encoder, encoder_cfg_t *settings)
Pointer to function for configuring an encoder.
Definition: encoders.h:105
encoder_data_t *(* encoder_get_data_ptr)(encoder_t *encoder)
Pointer to function for getting encoder data.
Definition: encoders.h:92
bool(* encoder_enumerate_callback_ptr)(encoder_t *encoder, void *data)
Pointer to the callbak function to be called by encoders_enumerate().
Definition: encoders.h:98
uint8_t encoders_get_num(void)
Definition: encoders.c:76
void encoder_register(encoder_t *encoder)
Definition: encoders.c:44
bool encoders_enumerate(encoder_enumerate_callback_ptr callback, void *data)
Definition: encoders.c:64
void(* encoder_reset_ptr)(encoder_t *encoder)
Pointer to function for resetting encoder data.
Definition: encoders.h:79
bool(* encoder_claim_ptr)(encoder_on_event_ptr event_handler, void *context)
Pointer to function for claiming an encoder.
Definition: encoders.h:86
void(* encoder_on_event_ptr)(encoder_t *encoder, encoder_event_t *events, void *context)
Pointer to callback function to receive encoder events.
Definition: encoders.h:74
ISR_CODE void task_delete(foreground_task_ptr fn, void *data)
Definition: grbllib.c:640
ISR_CODE bool ISR_FUNC() task_add_immediate(foreground_task_ptr fn, void *data)
Enqueue a function to be called once by the foreground process.
Definition: grbllib.c:714
DCRAM grbl_hal_t hal
Global HAL struct.
Definition: grbllib.c:91
ISR_CODE bool ISR_FUNC() task_add_systick(foreground_task_ptr fn, void *data)
Definition: grbllib.c:661
FLASHMEM bool ioport_enable_irq(uint8_t port, pin_irq_mode_t irq_mode, ioport_interrupt_callback_ptr handler)
Definition: ioports.c:632
#define IOPORT_UNASSIGNED
Definition: ioports.h:26
#define On
Definition: nuts_bolts.h:36
settings_t settings
Definition: settings.c:47
Definition: encoders.h:56
uint32_t dbl_click_window
ms.
Definition: encoders.h:58
uint32_t vel_timeout
Definition: encoders.h:57
Definition: encoders.h:61
int32_t position
Definition: encoders.h:62
uint32_t velocity
Definition: encoders.h:63
Definition: encoders.h:111
encoder_reset_ptr reset
Definition: encoders.h:114
encoder_configure_ptr configure
Definition: encoders.h:116
encoder_caps_t caps
Definition: encoders.h:112
encoder_claim_ptr claim
Definition: encoders.h:113
encoder_get_data_ptr get_data
Definition: encoders.h:115
uint32_t(* get_elapsed_ticks)(void)
Optional handler for getting number of elapsed 1 ms tics since startup. Rolls over every 49....
Definition: hal.h:663
Definition: crossbar.h:763
pin_function_t function
Pin function.
Definition: crossbar.h:766
Definition: encoders.h:43
uint8_t value
Definition: encoders.h:44
uint8_t spindle_rpm
Definition: encoders.h:50
uint8_t mask
Definition: encoders.h:45
uint8_t select
Definition: encoders.h:48
uint8_t index
Definition: encoders.h:49
uint8_t bidirectional
Definition: encoders.h:47
uint8_t spindle_pos
Definition: encoders.h:51
uint8_t unused
Definition: encoders.h:52
Definition: encoders.h:29
uint8_t direction_changed
Definition: encoders.h:34
uint8_t index_pulse
Definition: encoders.h:38
uint8_t value
Definition: encoders.h:30
uint8_t click
Definition: encoders.h:35
uint8_t long_click
Definition: encoders.h:37
uint8_t position_changed
Definition: encoders.h:33
uint8_t events
Definition: encoders.h:31
uint8_t dbl_click
Definition: encoders.h:36
uint8_t unused
Definition: encoders.h:39