pimoroni-pico/drivers/encoder-pio/encoder.pio

119 lines
4.0 KiB
Plaintext

; --------------------------------------------------
; Quadrature Encoder reader using PIO
; by Christopher (@ZodiusInfuser) Parrott
; --------------------------------------------------
;
; Watches any two pins (i.e. do not need to be consecutive) for
; when their state changes, and pushes that new state along with
; the old state, and time since the last change.
;
; - X is used for storing the last state
; - Y is used as a general scratch register and for storing the current state
; - OSR is used for storing the state-change timer
;
; After data is pushed into the system, a long delay takes place
; as a form of switch debounce to deal with rotary encoder dials.
; This is currently set to 500 cycles, but can be changed using the
; debounce constants below, as well as adjusting the frequency the PIO
; state machine runs at. E.g. a freq_divider of 250 gives a 1ms debounce.
; Debounce Constants
; --------------------------------------------------
.define SET_CYCLES 10
.define ITERATIONS 30
.define JMP_CYCLES 16
.define public ENC_DEBOUNCE_CYCLES (SET_CYCLES + (JMP_CYCLES * ITERATIONS))
; Ensure that ENC_DEBOUNCE_CYCLES is a multiple of the number of cycles the
; wrap takes, which is currently 10 cycles, otherwise timing may be inaccurate
; Encoder Program
; --------------------------------------------------
.program encoder
.wrap_target
loop:
; Copy the state-change timer from OSR, decrement it, and save it back
mov y, osr
jmp y-- osr_dec
osr_dec:
mov osr, y
; takes 3 cycles
; Read the state of both encoder pins and check if they are different from the last state
jmp pin encA_was_high
mov isr, null
jmp read_encB
encA_was_high:
set y, 1
mov isr, y
read_encB:
in pins, 1
mov y, isr
jmp x!=y state_changed [1]
; takes 7 cycles on both paths
.wrap
state_changed:
; Put the last state and the timer value into ISR alongside the current state,
; and push that state to the system. Then override the last state with the current state
in x, 2
mov x, ~osr ; invert the timer value to give a sensible value to the system
in x, 28
push noblock ; this also clears isr
mov x, y
; Perform a delay to debounce switch inputs
set y, (ITERATIONS - 1) [SET_CYCLES - 1]
debounce_loop:
jmp y-- debounce_loop [JMP_CYCLES - 1]
; Initialise the timer, as an inverse, and decrement it to account for the time this setup takes
mov y, ~null
jmp y-- y_dec
y_dec:
mov osr, y
jmp loop [1]
;takes 10 cycles, not counting whatever the debounce adds
; Initialisation Code
; --------------------------------------------------
% c-sdk {
#include "hardware/clocks.h"
static const uint8_t ENC_LOOP_CYCLES = encoder_wrap - encoder_wrap_target;
//The time that the debounce takes, as the number of wrap loops that the debounce is equivalent to
static const uint8_t ENC_DEBOUNCE_TIME = ENC_DEBOUNCE_CYCLES / ENC_LOOP_CYCLES;
static inline void encoder_program_init(PIO pio, uint sm, uint offset, uint pinA, uint pinB, uint16_t divider) {
pio_sm_config c = encoder_program_get_default_config(offset);
sm_config_set_jmp_pin(&c, pinA);
sm_config_set_in_pins(&c, pinB);
sm_config_set_in_shift(&c, false, false, 1);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_gpio_init(pio, pinA);
pio_gpio_init(pio, pinB);
gpio_pull_up(pinA);
gpio_pull_up(pinB);
pio_sm_set_consecutive_pindirs(pio, sm, pinA, 1, 0);
pio_sm_set_consecutive_pindirs(pio, sm, pinB, 1, 0);
sm_config_set_clkdiv_int_frac(&c, divider, 0);
pio_sm_init(pio, sm, offset, &c);
}
static inline void encoder_program_start(PIO pio, uint sm, bool stateA, bool stateB) {
pio_sm_exec(pio, sm, pio_encode_set(pio_x, (uint)stateA << 1 | (uint)stateB));
pio_sm_set_enabled(pio, sm, true);
}
static inline void encoder_program_release(PIO pio, uint sm) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_unclaim(pio, sm);
}
%}