Add example to read quadrature encoder in python with the PIO module

pull/81/head
Samuel 2024-11-17 15:14:53 +01:00
rodzic 1dc8d73a08
commit 24aaca1777
1 zmienionych plików z 148 dodań i 0 usunięć

148
pio/pio_qei.py 100644
Wyświetl plik

@ -0,0 +1,148 @@
# Example using PIO to read a quadrature encoder
#
# Demonstrates:
# - PIO reading 2 pin
# How to force a program to start at 0 by putting nop()
# instructions at the end of the programm
import time
import rp2
from machine import Pin
from rp2 import PIO
@rp2.asm_pio(in_shiftdir=PIO.SHIFT_LEFT)
def QEI_prog():
#
# Copyright (c) 2021 pmarques-dev @ github
#
# SPDX-License-Identifier: BSD-3-Clause
#
#.program quadrature_encoder
# this code must be loaded into address 0, but at 29 instructions, it probably
# wouldn't be able to share space with other programs anyway
#.origin 0
# the code works by running a loop that continuously shifts the 2 phase pins into
# ISR and looks at the lower 4 bits to do a computed jump to an instruction that
# does the proper "do nothing" | "increment" | "decrement" action for that pin
# state change (or no change)
# ISR holds the last state of the 2 pins during most of the code. The Y register
# keeps the current encoder count and is incremented / decremented according to
# the steps sampled
# writing any non zero value to the TX FIFO makes the state machine push the
# current count to RX FIFO between 6 to 18 clocks afterwards. The worst case
# sampling loop takes 14 cycles, so this program is able to read step rates up
# to sysclk / 14 (e.g., sysclk 125MHz, max step rate = 8.9 Msteps/sec)
# 00 state
jmp("update") # read 00
jmp("decrement") # read 01
jmp("increment") # read 10
jmp("update") # read 11
# 01 state
jmp("increment") # read 00
jmp("update") # read 01
jmp("update") # read 10
jmp("decrement") # read 11
# 10 state
jmp("decrement") # read 00
jmp("update") # read 01
jmp("update") # read 10
jmp("increment") # read 11
# to reduce code size, the last 2 states are implemented in place and become the
# target for the other jumps
# 11 state
jmp("update") # read 00
jmp("increment") # read 01
label("decrement")
# note: the target of this instruction must be the next address, so that
# the effect of the instruction does not depend on the value of Y. The
# same is true for the "JMP X--" below. Basically "JMP Y--, <next addr>"
# is just a pure "decrement Y" instruction, with no other side effects
jmp(y_dec, "update") # read 10
# this is where the main loop starts
wrap_target()
label("update")
# we start by checking the TX FIFO to see if the main code is asking for
# the current count after the PULL noblock, OSR will have either 0 if
# there was nothing or the value that was there
set(x, 0)
pull(noblock)
# since there are not many free registers, and PULL is done into OSR, we
# have to do some juggling to avoid losing the state information and
# still place the values where we need them
mov(x, osr)
mov(osr, isr)
# the main code did not ask for the count, so just go to "sample_pins"
jmp(not_x, "sample_pins")
# if it did ask for the count, then we push it
mov(isr, y) # we trash ISR, but we already have a copy in OSR
push()
label("sample_pins")
# we shift into ISR the last state of the 2 input pins (now in OSR) and
# the new state of the 2 pins, thus producing the 4 bit target for the
# computed jump into the correct action for this state
mov(isr, null)
in_(osr, 2)
in_(pins, 2)
mov(pc, isr)
# the PIO does not have a increment instruction, so to do that we do a
# negate, decrement, negate sequence
label("increment")
mov(x, invert(y))
jmp(x_dec, "increment_cont")
label("increment_cont")
mov(y, invert(x))
wrap() # the .wrap here avoids one jump instruction and saves a cycle too
# Without these 3 instructions, the code wasn't working.
# did python put the start of the program somewhere else than at 0 ?
# With these 3 nop(), there is no choice, it should use the entire 32 bytes of memory
nop()
nop()
nop()
class PIOQEI:
def __init__(self, sm_id, pin):
Pin(pin, Pin.IN)
Pin(pin+1, Pin.IN)
self.sm = rp2.StateMachine(sm_id, prog=QEI_prog, in_base=pin, in_shiftdir=PIO.SHIFT_LEFT)
self.sm.active(1)
def get(self):
# Minimum value is -1 (completely turn off), 0 actually still produces narrow pulse
self.sm.put(1)
return self.sm.get()
# Create the StateMachine with the QEI program, reading on Pin(2) & Pin(3).
qei1 = PIOQEI(1, 2)
qei2 = PIOQEI(0, 11)
while True:
print("count1:" + str(qei1.get()))
print("count2:" + str(qei2.get()))
time.sleep_ms(100)