Skip to content

IncrementalEncoder: factor out the quadrature state machine #4580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 5 additions & 60 deletions ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include "common-hal/rotaryio/IncrementalEncoder.h"
#include "shared-module/rotaryio/IncrementalEncoder.h"

#include "atmel_start_pins.h"

Expand Down Expand Up @@ -68,11 +69,9 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode
self->position = 0;
self->quarter_count = 0;

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

claim_pin(pin_a);
claim_pin(pin_b);
Expand Down Expand Up @@ -106,66 +105,12 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o
self->pin_b = NO_PIN;
}

mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t* self) {
return self->position;
}

void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t* self,
mp_int_t new_position) {
self->position = new_position;
}

void incrementalencoder_interrupt_handler(uint8_t channel) {
rotaryio_incrementalencoder_obj_t* self = get_eic_channel_data(channel);

// This table also works for detent both at 11 and 00
// For 11 at detent:
// Turning cw: 11->01->00->10->11
// Turning ccw: 11->10->00->01->11
// For 00 at detent:
// Turning cw: 00->10->11->10->00
// Turning ccw: 00->01->11->10->00

// index table by state <oldA><oldB><newA><newB>
#define BAD 7
static const int8_t transitions[16] = {
0, // 00 -> 00 no movement
-1, // 00 -> 01 3/4 ccw (11 detent) or 1/4 ccw (00 at detent)
+1, // 00 -> 10 3/4 cw or 1/4 cw
BAD, // 00 -> 11 non-Gray-code transition
+1, // 01 -> 00 2/4 or 4/4 cw
0, // 01 -> 01 no movement
BAD, // 01 -> 10 non-Gray-code transition
-1, // 01 -> 11 4/4 or 2/4 ccw
-1, // 10 -> 00 2/4 or 4/4 ccw
BAD, // 10 -> 01 non-Gray-code transition
0, // 10 -> 10 no movement
+1, // 10 -> 11 4/4 or 2/4 cw
BAD, // 11 -> 00 non-Gray-code transition
+1, // 11 -> 01 1/4 or 3/4 cw
-1, // 11 -> 10 1/4 or 3/4 ccw
0, // 11 -> 11 no movement
};

// Shift the old AB bits to the "old" position, and set the new AB bits.
// TODO(tannewt): If we need more speed then read the pin directly. gpio_get_pin_level has
// smarts to compensate for pin direction we don't need.
self->last_state = (self->last_state & 0x3) << 2 |
uint8_t new_state =
((uint8_t) gpio_get_pin_level(self->pin_a) << 1) |
(uint8_t) gpio_get_pin_level(self->pin_b);

int8_t quarter_incr = transitions[self->last_state];
if (quarter_incr == BAD) {
// Missed a transition. We don't know which way we're going, so do nothing.
return;
}

self->quarter_count += quarter_incr;
if (self->quarter_count >= 4) {
self->position += 1;
self->quarter_count = 0;
} else if (self->quarter_count <= -4) {
self->position -= 1;
self->quarter_count = 0;
}
shared_module_softencoder_state_update(self, new_state);
}
8 changes: 4 additions & 4 deletions ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ typedef struct {
mp_obj_base_t base;
uint8_t pin_a;
uint8_t pin_b;
uint8_t eic_channel_a:4;
uint8_t eic_channel_b:4;
uint8_t last_state:4; // <old A><old B><new A><new B>
int8_t quarter_count:4; // count intermediate transitions between detents
uint8_t eic_channel_a;
uint8_t eic_channel_b;
uint8_t state; // <old A><old B>
int8_t quarter_count; // count intermediate transitions between detents
mp_int_t position;
} rotaryio_incrementalencoder_obj_t;

Expand Down
2 changes: 2 additions & 0 deletions ports/atmel-samd/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ USB_SERIAL_NUMBER_LENGTH = 32
# Number of USB endpoint pairs.
USB_NUM_EP = 8

CIRCUITPY_ROTARYIO_SOFTENCODER = 1

######################################################################
# Put samd21-only choices here.

Expand Down
36 changes: 5 additions & 31 deletions ports/nrf/common-hal/rotaryio/IncrementalEncoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include "common-hal/rotaryio/IncrementalEncoder.h"
#include "shared-module/rotaryio/IncrementalEncoder.h"
#include "nrfx_gpiote.h"

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

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

uint8_t change = (new_state - self->state) & 0x03;
if (change == 1) {
self->quarter++;
} else if (change == 3) {
self->quarter--;
}
// ignore other state transitions

self->state = new_state;

// logic from the atmel-samd port: provides some damping and scales movement
// down by 4:1.
if (self->quarter >= 4) {
self->position++;
self->quarter = 0;
} else if (self->quarter <= -4) {
self->position--;
self->quarter = 0;
}
shared_module_softencoder_state_update(self, new_state);
}

void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self,
Expand Down Expand Up @@ -110,12 +93,3 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o
self->pin_a = NO_PIN;
self->pin_b = NO_PIN;
}

mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t *self) {
return self->position;
}

void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t *self,
mp_int_t new_position) {
self->position = new_position;
}
4 changes: 2 additions & 2 deletions ports/nrf/common-hal/rotaryio/IncrementalEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ typedef struct {
mp_obj_base_t base;
uint8_t pin_a;
uint8_t pin_b;
uint8_t state;
int8_t quarter;
uint8_t state; // <old A><old B>
int8_t quarter_count; // count intermediate transitions between detents
mp_int_t position;
} rotaryio_incrementalencoder_obj_t;

Expand Down
1 change: 1 addition & 0 deletions ports/nrf/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ CIRCUITPY_RTC ?= 1
CIRCUITPY_FREQUENCYIO = 0

CIRCUITPY_RGBMATRIX ?= 1
CIRCUITPY_ROTARYIO_SOFTENCODER = 1
CIRCUITPY_FRAMEBUFFERIO ?= 1

CIRCUITPY_COUNTIO = 0
Expand Down
75 changes: 15 additions & 60 deletions ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <hardware/regs/pio.h>
#include "common-hal/rotaryio/IncrementalEncoder.h"
#include "shared-module/rotaryio/IncrementalEncoder.h"
#include "bindings/rp2pio/__init__.h"
#include "bindings/rp2pio/StateMachine.h"

Expand Down Expand Up @@ -60,9 +61,12 @@ STATIC void incrementalencoder_interrupt_handler(void *self_in);
void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self,
const mcu_pin_obj_t *pin_a, const mcu_pin_obj_t *pin_b) {
mp_obj_t pins[] = {MP_OBJ_FROM_PTR(pin_a), MP_OBJ_FROM_PTR(pin_b)};
// Start out with swapped to match behavior with other ports.
self->swapped = true;
if (!common_hal_rp2pio_pins_are_sequential(2, pins)) {
pins[0] = MP_OBJ_FROM_PTR(pin_b);
pins[1] = MP_OBJ_FROM_PTR(pin_a);
self->swapped = false;
if (!common_hal_rp2pio_pins_are_sequential(2, pins)) {
mp_raise_RuntimeError(translate("Pins must be sequential"));
}
Expand All @@ -88,12 +92,10 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode
common_hal_rp2pio_statemachine_run(&self->state_machine, encoder_init, MP_ARRAY_SIZE(encoder_init));

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

shared_module_softencoder_state_init(self, quiescent_state & 3);
common_hal_rp2pio_statemachine_set_interrupt_handler(&self->state_machine, incrementalencoder_interrupt_handler, self, PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS);
}

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

mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t *self) {
return self->position;
}

void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t *self,
mp_int_t new_position) {
self->position = new_position;
}

STATIC void incrementalencoder_interrupt_handler(void *self_in) {
rotaryio_incrementalencoder_obj_t *self = self_in;
// This table also works for detent both at 11 and 00
// For 11 at detent:
// Turning cw: 11->01->00->10->11
// Turning ccw: 11->10->00->01->11
// For 00 at detent:
// Turning cw: 00->10->11->10->00
// Turning ccw: 00->01->11->10->00

// index table by state <oldA><oldB><newA><newB>
#define BAD 7
static const int8_t transitions[16] = {
0, // 00 -> 00 no movement
-1, // 00 -> 01 3/4 ccw (11 detent) or 1/4 ccw (00 at detent)
+1, // 00 -> 10 3/4 cw or 1/4 cw
BAD, // 00 -> 11 non-Gray-code transition
+1, // 01 -> 00 2/4 or 4/4 cw
0, // 01 -> 01 no movement
BAD, // 01 -> 10 non-Gray-code transition
-1, // 01 -> 11 4/4 or 2/4 ccw
-1, // 10 -> 00 2/4 or 4/4 ccw
BAD, // 10 -> 01 non-Gray-code transition
0, // 10 -> 10 no movement
+1, // 10 -> 11 4/4 or 2/4 cw
BAD, // 11 -> 00 non-Gray-code transition
+1, // 11 -> 01 1/4 or 3/4 cw
-1, // 11 -> 10 1/4 or 3/4 ccw
0, // 11 -> 11 no movement
};

while (common_hal_rp2pio_statemachine_get_in_waiting(&self->state_machine)) {
// Bypass all the logic of StateMachine.c:_transfer, we need something
// very simple and fast for an interrupt!
uint8_t new = self->state_machine.pio->rxf[self->state_machine.state_machine];

// Shift the old AB bits to the "old" position, and set the new AB bits.
self->last_state = (self->last_state & 0x3) << 2 | (new & 0x3);

int8_t quarter_incr = transitions[self->last_state];
if (quarter_incr == BAD) {
// Missed a transition. We don't know which way we're going, so do nothing.
return;
}

self->quarter_count += quarter_incr;
if (self->quarter_count >= 4) {
self->position += 1;
self->quarter_count = 0;
} else if (self->quarter_count <= -4) {
self->position -= 1;
self->quarter_count = 0;
uint8_t new_state = self->state_machine.pio->rxf[self->state_machine.state_machine];
if (self->swapped) {
if (new_state == 0x1) {
new_state = 0x2;
} else if (new_state == 0x2) {
new_state = 0x1;
}
}
shared_module_softencoder_state_update(self, new_state);
}
}
5 changes: 3 additions & 2 deletions ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
typedef struct {
mp_obj_base_t base;
rp2pio_statemachine_obj_t state_machine;
uint8_t last_state : 4; // <old A><old B><new A><new B>
int8_t quarter_count : 4; // count intermediate transitions between detents
uint8_t state; // <old A><old B>
int8_t quarter_count; // count intermediate transitions between detents
bool swapped; // Did the pins need to be swapped to be sequential?
mp_int_t position;
} rotaryio_incrementalencoder_obj_t;
1 change: 1 addition & 0 deletions ports/raspberrypi/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CIRCUITPY_BITOPS ?= 1
CIRCUITPY_PWMIO ?= 1
CIRCUITPY_RGBMATRIX ?= 1
CIRCUITPY_ROTARYIO ?= 1
CIRCUITPY_ROTARYIO_SOFTENCODER = 1

# Things that need to be implemented.
# Use PWM interally
Expand Down
1 change: 1 addition & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ SRC_SHARED_MODULE_ALL = \
random/__init__.c \
rgbmatrix/RGBMatrix.c \
rgbmatrix/__init__.c \
rotaryio/IncrementalEncoder.c \
sharpdisplay/SharpMemoryFramebuffer.c \
sharpdisplay/__init__.c \
socket/__init__.c \
Expand Down
3 changes: 3 additions & 0 deletions py/circuitpy_mpconfig.mk
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ CFLAGS += -DCIRCUITPY_RGBMATRIX=$(CIRCUITPY_RGBMATRIX)
CIRCUITPY_ROTARYIO ?= 1
CFLAGS += -DCIRCUITPY_ROTARYIO=$(CIRCUITPY_ROTARYIO)

CIRCUITPY_ROTARYIO_SOFTENCODER ?= 0
CFLAGS += -DCIRCUITPY_ROTARYIO_SOFTENCODER=$(CIRCUITPY_ROTARYIO_SOFTENCODER)

CIRCUITPY_RTC ?= 1
CFLAGS += -DCIRCUITPY_RTC=$(CIRCUITPY_RTC)

Expand Down
Loading