pimoroni-pico/drivers/encoder/encoder.pio

100 lines
3.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 enc_a_was_high
mov isr, null
jmp read_enc_b
enc_a_was_high:
set y, 1
mov isr, y
read_enc_b:
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 {
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;
%}