Skip to content
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

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
148 changes: 148 additions & 0 deletions pio/pio_qei.py
Original file line number Diff line number Diff line change
@@ -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)