Skip to content

Commit 1b60c9d

Browse files
authored
Merge pull request #4580 from jepler/incrementalencoder-refactor
IncrementalEncoder: factor out the quadrature state machine
2 parents 35d618b + 61e33a5 commit 1b60c9d

File tree

13 files changed

+162
-159
lines changed

13 files changed

+162
-159
lines changed

ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c

Lines changed: 5 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626

2727
#include "common-hal/rotaryio/IncrementalEncoder.h"
28+
#include "shared-module/rotaryio/IncrementalEncoder.h"
2829

2930
#include "atmel_start_pins.h"
3031

@@ -68,11 +69,9 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode
6869
self->position = 0;
6970
self->quarter_count = 0;
7071

71-
// Top two bits of self->last_state don't matter, because they'll be gone as soon as
72-
// interrupt handler is called.
73-
self->last_state =
72+
shared_module_softencoder_state_init(self,
7473
((uint8_t) gpio_get_pin_level(self->pin_a) << 1) |
75-
(uint8_t) gpio_get_pin_level(self->pin_b);
74+
(uint8_t) gpio_get_pin_level(self->pin_b));
7675

7776
claim_pin(pin_a);
7877
claim_pin(pin_b);
@@ -106,66 +105,12 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o
106105
self->pin_b = NO_PIN;
107106
}
108107

109-
mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t* self) {
110-
return self->position;
111-
}
112-
113-
void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t* self,
114-
mp_int_t new_position) {
115-
self->position = new_position;
116-
}
117-
118108
void incrementalencoder_interrupt_handler(uint8_t channel) {
119109
rotaryio_incrementalencoder_obj_t* self = get_eic_channel_data(channel);
120110

121-
// This table also works for detent both at 11 and 00
122-
// For 11 at detent:
123-
// Turning cw: 11->01->00->10->11
124-
// Turning ccw: 11->10->00->01->11
125-
// For 00 at detent:
126-
// Turning cw: 00->10->11->10->00
127-
// Turning ccw: 00->01->11->10->00
128-
129-
// index table by state <oldA><oldB><newA><newB>
130-
#define BAD 7
131-
static const int8_t transitions[16] = {
132-
0, // 00 -> 00 no movement
133-
-1, // 00 -> 01 3/4 ccw (11 detent) or 1/4 ccw (00 at detent)
134-
+1, // 00 -> 10 3/4 cw or 1/4 cw
135-
BAD, // 00 -> 11 non-Gray-code transition
136-
+1, // 01 -> 00 2/4 or 4/4 cw
137-
0, // 01 -> 01 no movement
138-
BAD, // 01 -> 10 non-Gray-code transition
139-
-1, // 01 -> 11 4/4 or 2/4 ccw
140-
-1, // 10 -> 00 2/4 or 4/4 ccw
141-
BAD, // 10 -> 01 non-Gray-code transition
142-
0, // 10 -> 10 no movement
143-
+1, // 10 -> 11 4/4 or 2/4 cw
144-
BAD, // 11 -> 00 non-Gray-code transition
145-
+1, // 11 -> 01 1/4 or 3/4 cw
146-
-1, // 11 -> 10 1/4 or 3/4 ccw
147-
0, // 11 -> 11 no movement
148-
};
149-
150-
// Shift the old AB bits to the "old" position, and set the new AB bits.
151-
// TODO(tannewt): If we need more speed then read the pin directly. gpio_get_pin_level has
152-
// smarts to compensate for pin direction we don't need.
153-
self->last_state = (self->last_state & 0x3) << 2 |
111+
uint8_t new_state =
154112
((uint8_t) gpio_get_pin_level(self->pin_a) << 1) |
155113
(uint8_t) gpio_get_pin_level(self->pin_b);
156114

157-
int8_t quarter_incr = transitions[self->last_state];
158-
if (quarter_incr == BAD) {
159-
// Missed a transition. We don't know which way we're going, so do nothing.
160-
return;
161-
}
162-
163-
self->quarter_count += quarter_incr;
164-
if (self->quarter_count >= 4) {
165-
self->position += 1;
166-
self->quarter_count = 0;
167-
} else if (self->quarter_count <= -4) {
168-
self->position -= 1;
169-
self->quarter_count = 0;
170-
}
115+
shared_module_softencoder_state_update(self, new_state);
171116
}

ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ typedef struct {
3535
mp_obj_base_t base;
3636
uint8_t pin_a;
3737
uint8_t pin_b;
38-
uint8_t eic_channel_a:4;
39-
uint8_t eic_channel_b:4;
40-
uint8_t last_state:4; // <old A><old B><new A><new B>
41-
int8_t quarter_count:4; // count intermediate transitions between detents
38+
uint8_t eic_channel_a;
39+
uint8_t eic_channel_b;
40+
uint8_t state; // <old A><old B>
41+
int8_t quarter_count; // count intermediate transitions between detents
4242
mp_int_t position;
4343
} rotaryio_incrementalencoder_obj_t;
4444

ports/atmel-samd/mpconfigport.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ USB_SERIAL_NUMBER_LENGTH = 32
2525
# Number of USB endpoint pairs.
2626
USB_NUM_EP = 8
2727

28+
CIRCUITPY_ROTARYIO_SOFTENCODER = 1
29+
2830
######################################################################
2931
# Put samd21-only choices here.
3032

ports/nrf/common-hal/rotaryio/IncrementalEncoder.c

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626

2727
#include "common-hal/rotaryio/IncrementalEncoder.h"
28+
#include "shared-module/rotaryio/IncrementalEncoder.h"
2829
#include "nrfx_gpiote.h"
2930

3031
#include "py/runtime.h"
@@ -40,29 +41,11 @@ static void _intr_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
4041
return;
4142
}
4243

43-
// reads a state 0 .. 3 *in order*.
44-
uint8_t new_state = nrf_gpio_pin_read(self->pin_a);
45-
new_state = (new_state << 1) + (new_state ^ nrf_gpio_pin_read(self->pin_b));
44+
uint8_t new_state =
45+
((uint8_t) nrf_gpio_pin_read(self->pin_a) << 1) |
46+
(uint8_t) nrf_gpio_pin_read(self->pin_b);
4647

47-
uint8_t change = (new_state - self->state) & 0x03;
48-
if (change == 1) {
49-
self->quarter++;
50-
} else if (change == 3) {
51-
self->quarter--;
52-
}
53-
// ignore other state transitions
54-
55-
self->state = new_state;
56-
57-
// logic from the atmel-samd port: provides some damping and scales movement
58-
// down by 4:1.
59-
if (self->quarter >= 4) {
60-
self->position++;
61-
self->quarter = 0;
62-
} else if (self->quarter <= -4) {
63-
self->position--;
64-
self->quarter = 0;
65-
}
48+
shared_module_softencoder_state_update(self, new_state);
6649
}
6750

6851
void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self,
@@ -110,12 +93,3 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o
11093
self->pin_a = NO_PIN;
11194
self->pin_b = NO_PIN;
11295
}
113-
114-
mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t *self) {
115-
return self->position;
116-
}
117-
118-
void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t *self,
119-
mp_int_t new_position) {
120-
self->position = new_position;
121-
}

ports/nrf/common-hal/rotaryio/IncrementalEncoder.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ typedef struct {
3535
mp_obj_base_t base;
3636
uint8_t pin_a;
3737
uint8_t pin_b;
38-
uint8_t state;
39-
int8_t quarter;
38+
uint8_t state; // <old A><old B>
39+
int8_t quarter_count; // count intermediate transitions between detents
4040
mp_int_t position;
4141
} rotaryio_incrementalencoder_obj_t;
4242

ports/nrf/mpconfigport.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ CIRCUITPY_RTC ?= 1
4040
CIRCUITPY_FREQUENCYIO = 0
4141

4242
CIRCUITPY_RGBMATRIX ?= 1
43+
CIRCUITPY_ROTARYIO_SOFTENCODER = 1
4344
CIRCUITPY_FRAMEBUFFERIO ?= 1
4445

4546
CIRCUITPY_COUNTIO = 0

ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c

Lines changed: 15 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include <hardware/regs/pio.h>
3030
#include "common-hal/rotaryio/IncrementalEncoder.h"
31+
#include "shared-module/rotaryio/IncrementalEncoder.h"
3132
#include "bindings/rp2pio/__init__.h"
3233
#include "bindings/rp2pio/StateMachine.h"
3334

@@ -60,9 +61,12 @@ STATIC void incrementalencoder_interrupt_handler(void *self_in);
6061
void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self,
6162
const mcu_pin_obj_t *pin_a, const mcu_pin_obj_t *pin_b) {
6263
mp_obj_t pins[] = {MP_OBJ_FROM_PTR(pin_a), MP_OBJ_FROM_PTR(pin_b)};
64+
// Start out with swapped to match behavior with other ports.
65+
self->swapped = true;
6366
if (!common_hal_rp2pio_pins_are_sequential(2, pins)) {
6467
pins[0] = MP_OBJ_FROM_PTR(pin_b);
6568
pins[1] = MP_OBJ_FROM_PTR(pin_a);
69+
self->swapped = false;
6670
if (!common_hal_rp2pio_pins_are_sequential(2, pins)) {
6771
mp_raise_RuntimeError(translate("Pins must be sequential"));
6872
}
@@ -88,12 +92,10 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode
8892
common_hal_rp2pio_statemachine_run(&self->state_machine, encoder_init, MP_ARRAY_SIZE(encoder_init));
8993

9094
// We're guaranteed by the init code that some output will be available promptly
91-
uint8_t state;
92-
common_hal_rp2pio_statemachine_readinto(&self->state_machine, &state, 1, 1);
93-
// Top two bits of self->last_state don't matter, because they'll be gone as soon as
94-
// interrupt handler is called.
95-
self->last_state = state & 3;
95+
uint8_t quiescent_state;
96+
common_hal_rp2pio_statemachine_readinto(&self->state_machine, &quiescent_state, 1, 1);
9697

98+
shared_module_softencoder_state_init(self, quiescent_state & 3);
9799
common_hal_rp2pio_statemachine_set_interrupt_handler(&self->state_machine, incrementalencoder_interrupt_handler, self, PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS);
98100
}
99101

@@ -109,67 +111,20 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o
109111
common_hal_rp2pio_statemachine_deinit(&self->state_machine);
110112
}
111113

112-
mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t *self) {
113-
return self->position;
114-
}
115-
116-
void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t *self,
117-
mp_int_t new_position) {
118-
self->position = new_position;
119-
}
120-
121114
STATIC void incrementalencoder_interrupt_handler(void *self_in) {
122115
rotaryio_incrementalencoder_obj_t *self = self_in;
123-
// This table also works for detent both at 11 and 00
124-
// For 11 at detent:
125-
// Turning cw: 11->01->00->10->11
126-
// Turning ccw: 11->10->00->01->11
127-
// For 00 at detent:
128-
// Turning cw: 00->10->11->10->00
129-
// Turning ccw: 00->01->11->10->00
130-
131-
// index table by state <oldA><oldB><newA><newB>
132-
#define BAD 7
133-
static const int8_t transitions[16] = {
134-
0, // 00 -> 00 no movement
135-
-1, // 00 -> 01 3/4 ccw (11 detent) or 1/4 ccw (00 at detent)
136-
+1, // 00 -> 10 3/4 cw or 1/4 cw
137-
BAD, // 00 -> 11 non-Gray-code transition
138-
+1, // 01 -> 00 2/4 or 4/4 cw
139-
0, // 01 -> 01 no movement
140-
BAD, // 01 -> 10 non-Gray-code transition
141-
-1, // 01 -> 11 4/4 or 2/4 ccw
142-
-1, // 10 -> 00 2/4 or 4/4 ccw
143-
BAD, // 10 -> 01 non-Gray-code transition
144-
0, // 10 -> 10 no movement
145-
+1, // 10 -> 11 4/4 or 2/4 cw
146-
BAD, // 11 -> 00 non-Gray-code transition
147-
+1, // 11 -> 01 1/4 or 3/4 cw
148-
-1, // 11 -> 10 1/4 or 3/4 ccw
149-
0, // 11 -> 11 no movement
150-
};
151116

152117
while (common_hal_rp2pio_statemachine_get_in_waiting(&self->state_machine)) {
153118
// Bypass all the logic of StateMachine.c:_transfer, we need something
154119
// very simple and fast for an interrupt!
155-
uint8_t new = self->state_machine.pio->rxf[self->state_machine.state_machine];
156-
157-
// Shift the old AB bits to the "old" position, and set the new AB bits.
158-
self->last_state = (self->last_state & 0x3) << 2 | (new & 0x3);
159-
160-
int8_t quarter_incr = transitions[self->last_state];
161-
if (quarter_incr == BAD) {
162-
// Missed a transition. We don't know which way we're going, so do nothing.
163-
return;
164-
}
165-
166-
self->quarter_count += quarter_incr;
167-
if (self->quarter_count >= 4) {
168-
self->position += 1;
169-
self->quarter_count = 0;
170-
} else if (self->quarter_count <= -4) {
171-
self->position -= 1;
172-
self->quarter_count = 0;
120+
uint8_t new_state = self->state_machine.pio->rxf[self->state_machine.state_machine];
121+
if (self->swapped) {
122+
if (new_state == 0x1) {
123+
new_state = 0x2;
124+
} else if (new_state == 0x2) {
125+
new_state = 0x1;
126+
}
173127
}
128+
shared_module_softencoder_state_update(self, new_state);
174129
}
175130
}

ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
typedef struct {
3535
mp_obj_base_t base;
3636
rp2pio_statemachine_obj_t state_machine;
37-
uint8_t last_state : 4; // <old A><old B><new A><new B>
38-
int8_t quarter_count : 4; // count intermediate transitions between detents
37+
uint8_t state; // <old A><old B>
38+
int8_t quarter_count; // count intermediate transitions between detents
39+
bool swapped; // Did the pins need to be swapped to be sequential?
3940
mp_int_t position;
4041
} rotaryio_incrementalencoder_obj_t;

ports/raspberrypi/mpconfigport.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ CIRCUITPY_BITOPS ?= 1
2626
CIRCUITPY_PWMIO ?= 1
2727
CIRCUITPY_RGBMATRIX ?= 1
2828
CIRCUITPY_ROTARYIO ?= 1
29+
CIRCUITPY_ROTARYIO_SOFTENCODER = 1
2930

3031
# Things that need to be implemented.
3132
# Use PWM interally

py/circuitpy_defns.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ SRC_SHARED_MODULE_ALL = \
518518
random/__init__.c \
519519
rgbmatrix/RGBMatrix.c \
520520
rgbmatrix/__init__.c \
521+
rotaryio/IncrementalEncoder.c \
521522
sharpdisplay/SharpMemoryFramebuffer.c \
522523
sharpdisplay/__init__.c \
523524
socket/__init__.c \

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ CFLAGS += -DCIRCUITPY_RGBMATRIX=$(CIRCUITPY_RGBMATRIX)
271271
CIRCUITPY_ROTARYIO ?= 1
272272
CFLAGS += -DCIRCUITPY_ROTARYIO=$(CIRCUITPY_ROTARYIO)
273273

274+
CIRCUITPY_ROTARYIO_SOFTENCODER ?= 0
275+
CFLAGS += -DCIRCUITPY_ROTARYIO_SOFTENCODER=$(CIRCUITPY_ROTARYIO_SOFTENCODER)
276+
274277
CIRCUITPY_RTC ?= 1
275278
CFLAGS += -DCIRCUITPY_RTC=$(CIRCUITPY_RTC)
276279

0 commit comments

Comments
 (0)