-
Notifications
You must be signed in to change notification settings - Fork 233
/
Copy pathpio_qei.py
148 lines (115 loc) · 4.6 KB
/
pio_qei.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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)