; -------------------------------------------------- ; 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; %}