grblHAL core  20260318
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 
87 typedef bool (*encoder_claim_ptr)(encoder_t *encoder, encoder_on_event_ptr event_handler, void *context);
88 
93 typedef encoder_data_t *(*encoder_get_data_ptr)(encoder_t *encoder);
94 
99 typedef bool (*encoder_enumerate_callback_ptr)(encoder_t *encoder, void *data);
100 
107 
109 bool encoders_enumerate (encoder_enumerate_callback_ptr callback, void *data);
110 uint8_t encoders_get_count (void);
111 
112 struct encoder {
113  void *hw;
119 };
120 
121 #endif // _ENCODERS_H_
122 
123 // Interrupt driven Quadrature Encoder Interface - static code for driver/plugin use
124 
125 #if QEI_ENABLE && defined(QEI_A_PIN) && defined(QEI_B_PIN)
126 
127 typedef enum {
128  QEI_DirUnknown = 0,
129  QEI_DirCW,
130  QEI_DirCCW
131 } qei_dir_t;
132 
133 typedef union {
134  uint_fast8_t pins;
135  struct {
136  uint_fast8_t a :1,
137  b :1;
138  };
139 } qei_state_t;
140 
141 typedef struct {
143  encoder_data_t data;
144  encoder_event_t event;
145  void *context;
146  int32_t vel_count;
147  uint_fast16_t state;
148  qei_dir_t dir;
149  uint8_t port_a, port_b, port_select;
150  volatile uint32_t dbl_click_timeout;
151  volatile uint32_t vel_timeout;
152  uint32_t vel_timestamp;
153  encoder_on_event_ptr on_event;
155 } iqei_t;
156 
157 static iqei_t iqei = {
158  .port_a = IOPORT_UNASSIGNED,
159  .port_b = IOPORT_UNASSIGNED,
160  .port_select = IOPORT_UNASSIGNED,
161  .settings.dbl_click_window = 500,
162  .encoder.caps.bidirectional = On
163 };
164 
165 static void iqei_select_irq (uint8_t port, bool high);
166 
167 static void iqei_post_event (void *data)
168 {
169  iqei.event.events |= ((encoder_event_t *)data)->events;
170 
171  iqei.on_event(&iqei.encoder, &iqei.event, iqei.context);
172 }
173 
174 static void iqei_reset (encoder_t *encoder)
175 {
176  iqei.vel_timeout = 0;
177  iqei.dir = QEI_DirUnknown;
178  iqei.data.position = iqei.vel_count = 0;
179  iqei.vel_timestamp = hal.get_elapsed_ticks();
180  iqei.vel_timeout = iqei.settings.vel_timeout;
181 }
182 
183 static bool iqei_configure (encoder_t *encoder, encoder_cfg_t *settings)
184 {
185  if(iqei.vel_timeout != settings->vel_timeout)
186  iqei.vel_timestamp = hal.get_elapsed_ticks();
187 
188  memcpy(&iqei.settings, settings, sizeof(encoder_cfg_t));
189 
190  return true;
191 }
192 
193 static encoder_data_t *iqei_get_data (encoder_t *encoder)
194 {
195  return &iqei.data;
196 }
197 
198 static void iqei_poll (void *data)
199 {
200  if(iqei.vel_timeout && !(--iqei.vel_timeout)) {
201 
202  uint32_t time = hal.get_elapsed_ticks();
203 
204  iqei.data.velocity = abs(iqei.data.position - iqei.vel_count) * 1000 / (time - iqei.vel_timestamp);
205  iqei.vel_timestamp = time;
206  iqei.vel_timeout = iqei.settings.vel_timeout;
207  if((iqei.event.position_changed = !iqei.dbl_click_timeout || iqei.data.velocity == 0))
208  iqei.on_event(&iqei.encoder, &iqei.event, iqei.context);
209  iqei.vel_count = iqei.data.position;
210  }
211 
212  if(iqei.dbl_click_timeout && !(--iqei.dbl_click_timeout)) {
213  iqei.event.click = On;
214  iqei.on_event(&iqei.encoder, &iqei.event, iqei.context);
215  }
216 }
217 
218 static void iqei_ab_irq (uint8_t port, bool high)
219 {
220  PROGMEM static const uint8_t encoder_valid_state[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
221  PROGMEM static const encoder_event_t dir_changed = { .direction_changed = On, .position_changed = On };
222 
223  static qei_state_t state = {0};
224 
225  if(port == iqei.port_a)
226  state.a = high;
227  else
228  state.b = high;
229 
230  uint_fast8_t idx = (((iqei.state << 2) & 0x0F) | state.pins);
231 
232  if(encoder_valid_state[idx] ) {
233 
234 // int32_t count = iqei.count;
235 
236  iqei.state = ((iqei.state << 4) | idx) & 0xFF;
237 
238  if(iqei.state == 0x42 || iqei.state == 0xD4 || iqei.state == 0x2B || iqei.state == 0xBD) {
239  iqei.data.position--;
240  if(iqei.vel_timeout == 0 || iqei.dir == QEI_DirCW) {
241  iqei.dir = QEI_DirCCW;
242  task_add_immediate(iqei_post_event, &dir_changed);
243  }
244  } else if(iqei.state == 0x81 || iqei.state == 0x17 || iqei.state == 0xE8 || iqei.state == 0x7E) {
245  iqei.data.position++;
246  if(iqei.vel_timeout == 0 || iqei.dir == QEI_DirCCW) {
247  iqei.dir = QEI_DirCW;
248  task_add_immediate(iqei_post_event, &dir_changed);
249  }
250  }
251  }
252 }
253 
254 static void iqei_select (void *data)
255 {
256  static uint8_t clicks = 0;
257 
258  if(!iqei.dbl_click_timeout) {
259  clicks = 1;
260  iqei.dbl_click_timeout = iqei.settings.dbl_click_window;
261  } else if(iqei.dbl_click_timeout < iqei.settings.dbl_click_window && ++clicks == 2) {
262  iqei.dbl_click_timeout = 0;
263  iqei.event.dbl_click = On;
264  iqei.on_event(&iqei.encoder, &iqei.event, iqei.context);
265  }
266 }
267 
268 static void iqei_select_irq (uint8_t port, bool high)
269 {
270  static bool lock = false;
271 
272  if(high || lock)
273  return;
274 
275 // lock = true;
276 
277  task_add_immediate(iqei_select, NULL);
278 
279 // lock = false;
280 }
281 
282 static bool iqei_claim (encoder_t *encoder, encoder_on_event_ptr event_handler, void *context)
283 {
284  if(event_handler == NULL || iqei.on_event)
285  return false;
286 
287  iqei.context = context;
288  iqei.on_event = event_handler;
289  iqei.encoder.reset = iqei_reset;
290  iqei.encoder.get_data = iqei_get_data;
291  iqei.encoder.configure = iqei_configure;
292 
293  if(iqei.port_b != IOPORT_UNASSIGNED) {
294  ioport_enable_irq(iqei.port_a, IRQ_Mode_Change, iqei_ab_irq);
295  ioport_enable_irq(iqei.port_b, IRQ_Mode_Change, iqei_ab_irq);
296  }
297 
298  if(iqei.port_select != IOPORT_UNASSIGNED)
299  ioport_enable_irq(iqei.port_select, IRQ_Mode_Change, iqei_select_irq);
300 
301  task_add_systick(iqei_poll, NULL);
302 
303  return true;
304 }
305 
306 static inline void _encoder_pin_claimed (uint8_t port, xbar_t *pin)
307 {
308  switch(pin->function) {
309 
310  case Input_QEI_A:
311  iqei.port_a = port;
312  break;
313 
314  case Input_QEI_B:
315  iqei.port_b = port;
316  iqei.encoder.claim = iqei_claim;
317  if(iqei.port_a != IOPORT_UNASSIGNED)
318  encoder_register(&iqei.encoder);
319  break;
320 
321  case Input_QEI_Select:
322  iqei.port_select = port;
323  iqei.encoder.caps.select = On;
324  if(pin->config) {
325  gpio_in_config_t config = {
326  .debounce = On,
327  .pull_mode = PullMode_Up
328  };
329  pin->config(pin, &config, false);
330  }
331  break;
332 
333  default: break;
334  }
335 }
336 
337 #endif
@ PullMode_Up
0b01 (0x01)
Definition: crossbar.h:717
@ Input_QEI_A
Definition: crossbar.h:337
@ Input_QEI_Select
Definition: crossbar.h:339
@ Input_QEI_B
Definition: crossbar.h:338
@ IRQ_Mode_Change
0b00100 (0x04)
Definition: crossbar.h:694
bool(* encoder_configure_ptr)(encoder_t *encoder, encoder_cfg_t *settings)
Pointer to function for configuring an encoder.
Definition: encoders.h:106
encoder_data_t *(* encoder_get_data_ptr)(encoder_t *encoder)
Pointer to function for getting encoder data.
Definition: encoders.h:93
bool(* encoder_enumerate_callback_ptr)(encoder_t *encoder, void *data)
Pointer to the callbak function to be called by encoders_enumerate().
Definition: encoders.h:99
void encoder_register(encoder_t *encoder)
Definition: encoders.c:35
uint8_t encoders_get_count(void)
Definition: encoders.c:67
bool encoders_enumerate(encoder_enumerate_callback_ptr callback, void *data)
Definition: encoders.c:55
bool(* encoder_claim_ptr)(encoder_t *encoder, encoder_on_event_ptr event_handler, void *context)
Pointer to function for claiming an encoder.
Definition: encoders.h:87
void(* encoder_reset_ptr)(encoder_t *encoder)
Pointer to function for resetting encoder data.
Definition: encoders.h:79
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 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:750
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:697
FLASHMEM bool ioport_enable_irq(uint8_t port, pin_irq_mode_t irq_mode, ioport_interrupt_callback_ptr handler)
Definition: ioports.c:643
#define IOPORT_UNASSIGNED
Definition: ioports.h:26
#define On
Definition: nuts_bolts.h:36
settings_t settings
Definition: settings.c:48
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:112
encoder_reset_ptr reset
Definition: encoders.h:116
encoder_configure_ptr configure
Definition: encoders.h:118
encoder_caps_t caps
Definition: encoders.h:114
void * hw
Definition: encoders.h:113
encoder_claim_ptr claim
Definition: encoders.h:115
encoder_get_data_ptr get_data
Definition: encoders.h:117
/a cfg_data argument to /a xbar_config_ptr for gpio input pins
Definition: crossbar.h:816
bool debounce
Definition: crossbar.h:818
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:667
Definition: crossbar.h:879
xbar_config_ptr config
Optional pointer to function for configuring the port.
Definition: crossbar.h:889
pin_function_t function
Pin function.
Definition: crossbar.h:882
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