Skip to content

Commit 7ec7b78

Browse files
committed
Got Encoder PIO working
1 parent bd0ef8a commit 7ec7b78

File tree

5 files changed

+144
-87
lines changed

5 files changed

+144
-87
lines changed

Examples/xrp_test.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from XRPLib.defaults import *
22
from machine import Pin
33
import time
4+
from XRPLib.dryw_encoder import *
45

56
imu.reset_pitch()
67
imu.reset_yaw()
@@ -68,4 +69,20 @@ def test_rangefinder():
6869
print(f"{rangefinder.distance()}")
6970
time.sleep(0.25)
7071

71-
test_rangefinder()
72+
def encoder_benchmarking():
73+
print("start benchmark")
74+
N = 100000
75+
a = time.time()
76+
for i in range(N):
77+
drivetrain.left_motor._encoder._isr()
78+
b = time.time()
79+
for i in range(N):
80+
mot1.encInt(4)
81+
c = time.time()
82+
# Print benchmark
83+
print(f"Time for {N} Old Encoder calls: {b-a}s")
84+
print(f"Time per Old Encoder call: {(b-a)/N}s") # ~0.06 ms per call
85+
print(f"Time for {N} New Encoder calls: {c-b}s")
86+
print(f"Time per New Encoder call: {(c-b)/N}s")
87+
88+
encoder_benchmarking()

XRPLib/defaults.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
Run "from XRPLib.defaults import *" to use
1515
"""
1616

17-
left_motor = EncodedMotor.get_default_left_motor()
18-
right_motor = EncodedMotor.get_default_right_motor()
17+
left_motor = EncodedMotor.get_default_encoded_motor(0)
18+
right_motor = EncodedMotor.get_default_encoded_motor(1)
1919
imu = IMU.get_default_imu()
2020
drivetrain = DifferentialDrive.get_default_differential_drive()
2121
rangefinder = Rangefinder.get_default_rangefinder()

XRPLib/differential_drive.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ def straight(self, distance: float, speed: float = 0.5, timeout: float = None, m
114114
# Secondary controller to keep encoder values in sync
115115
if secondary_controller is None:
116116
secondary_controller = PID(
117-
kp = 0.01,
118-
ki = 0.02
117+
kp = 0.02,
119118
)
120119

121120
rotationsToDo = distance / (self.wheel_diam * math.pi)
@@ -150,9 +149,7 @@ def straight(self, distance: float, speed: float = 0.5, timeout: float = None, m
150149

151150
headingCorrection = secondary_controller.tick(current_heading - initial_heading)
152151

153-
self.set_effort(effort + headingCorrection, effort - headingCorrection)
154-
155-
#print(self.imu.get_yaw() - heading, effort + headingCorrection, effort - headingCorrection, headingCorrection)
152+
self.set_effort(effort - headingCorrection, effort + headingCorrection)
156153

157154
time.sleep(0.01)
158155

XRPLib/encoded_motor.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,52 @@ class EncodedMotor:
88

99
_DEFAULT_LEFT_MOTOR_INSTANCE = None
1010
_DEFAULT_RIGHT_MOTOR_INSTANCE = None
11+
_DEFAULT_MOTOR_THREE_INSTANCE = None
12+
_DEFAULT_MOTOR_FOUR_INSTANCE = None
1113

1214
@classmethod
13-
def get_default_left_motor(cls):
14-
"""
15-
Get the default XRP v2 left motor instance. This is a singleton, so only one instance of the drivetrain will ever exist.
16-
Motor pins set to 6 and 7 and the encoder pins set to 4 and 5
17-
"""
18-
19-
if cls._DEFAULT_LEFT_MOTOR_INSTANCE is None:
20-
cls._DEFAULT_LEFT_MOTOR_INSTANCE = cls(
21-
Motor(6, 7, flip_dir=True),
22-
Encoder(4, 5)
23-
)
24-
25-
return cls._DEFAULT_LEFT_MOTOR_INSTANCE
15+
def get_default_encoded_motor(cls, index:int):
16+
"""
17+
Get one of the default XRP v2 motor instances. These are singletons, so only one instance of each of these will ever exist.
18+
"""
19+
if index == 0: # Left Motor
20+
if cls._DEFAULT_LEFT_MOTOR_INSTANCE is None:
21+
cls._DEFAULT_LEFT_MOTOR_INSTANCE = cls(
22+
Motor(6, 7, flip_dir=True),
23+
Encoder(0, 4, 5)
24+
)
25+
motor = cls._DEFAULT_LEFT_MOTOR_INSTANCE
26+
elif index == 1:
27+
if cls._DEFAULT_RIGHT_MOTOR_INSTANCE is None:
28+
cls._DEFAULT_RIGHT_MOTOR_INSTANCE = cls(
29+
Motor(14, 15),
30+
Encoder(1, 12, 13)
31+
)
32+
motor = cls._DEFAULT_RIGHT_MOTOR_INSTANCE
33+
elif index == 2:
34+
if cls._DEFAULT_MOTOR_THREE_INSTANCE is None:
35+
cls._DEFAULT_MOTOR_THREE_INSTANCE = cls(
36+
Motor(2, 3),
37+
Encoder(2, 0, 1)
38+
)
39+
motor = cls._DEFAULT_MOTOR_THREE_INSTANCE
40+
elif index == 3:
41+
if cls._DEFAULT_MOTOR_FOUR_INSTANCE is None:
42+
cls._DEFAULT_MOTOR_FOUR_INSTANCE = cls(
43+
Motor(10, 11, flip_dir=True),
44+
Encoder(3, 8, 9)
45+
)
46+
motor = cls._DEFAULT_MOTOR_FOUR_INSTANCE
47+
return motor
2648

2749
@classmethod
28-
def get_default_right_motor(cls):
50+
def get_default_encoded_motor(cls):
2951
"""
3052
Get the default XRP v2 right motor instance. This is a singleton, so only one instance of the drivetrain will ever exist.
3153
Motor pins set to 14 and 15 and the encoder pins set to 12 and 13
3254
"""
3355

34-
if cls._DEFAULT_RIGHT_MOTOR_INSTANCE is None:
35-
cls._DEFAULT_RIGHT_MOTOR_INSTANCE = cls(
36-
Motor(14, 15),
37-
Encoder(12, 13)
38-
)
56+
3957

4058
return cls._DEFAULT_RIGHT_MOTOR_INSTANCE
4159

XRPLib/encoder.py

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,91 @@
1-
from machine import Pin
2-
from micropython import const
1+
# Modified from https://forum.micropython.org/viewtopic.php?t=12277&p=66659
32

4-
class Encoder:
5-
def __init__(self, aPin:int, bPin:int, ticks_per_revolution:int = 146.25):
6-
# TODO: Look into PIO implementation as to not take CPU time
7-
self.currentPosition = 0
8-
self.ticks_per_rev = ticks_per_revolution
9-
# Set pins as inputs
10-
self._aPin = Pin(aPin, Pin.IN)
11-
self._bPin = Pin(bPin, Pin.IN)
12-
# Set up both pins with an interrupt to update the encoder count on any change
13-
self._aPin.irq(trigger=Pin.IRQ_RISING, handler=lambda pin:self._isr())
14-
#self._bPin.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=lambda pin:self.isr("b"))
15-
16-
#self.prevAState = 0
17-
#self.prevBState = 0
18-
# Use a lookup table based on the pin transitions to figure out if we are running forwards or backwards
19-
#self._ENCODER_LOOKUP_TABLE = [ 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0 ]
3+
import machine
4+
import rp2
5+
import time
206

21-
22-
def get_position(self):
23-
"""
24-
: return: The position of the encoded motor, in revolutions, relative to the last time reset was called.
25-
: rtype: float
26-
"""
27-
return self.currentPosition/self.ticks_per_rev
7+
class Encoder:
8+
gearRatio = (30/14) * (28/16) * (36/9) * (26/8) # 48.75
9+
ticksPerMotorRotation = 12
10+
ticksPerShaftRotation = ticksPerMotorRotation * gearRatio # 585
11+
12+
def __init__(self, index, encAPin, encBPin):
13+
if(abs(encAPin - encBPin) != 1):
14+
raise Exception("Encoder pins must be successive!")
15+
basePin = machine.Pin(min(encAPin, encBPin))
16+
self.sm = rp2.StateMachine(index, self._encoder, in_base=basePin)
17+
self.reset_encoder_position()
18+
self.sm.active(1)
2819

29-
# return position in ticks
30-
def get_position_ticks(self):
31-
"""
32-
: return: The position of the encoder, in ticks, relative to the last time reset was called.
33-
: rtype: int
34-
"""
35-
return self.currentPosition
36-
3720
def reset_encoder_position(self):
38-
"""
39-
Resets the encoder position back to zero.
40-
"""
41-
self.currentPosition = 0
42-
43-
def _isr(self):
44-
# aState = self._aPin.value()
45-
bState = self._bPin.value()
46-
47-
# At rising edge of A. If B is low, we are going forwards, if B is high, we are going backwards
48-
if bState == 0:
49-
self.currentPosition -= 1
50-
else:
51-
self.currentPosition += 1
52-
53-
#print(self.currentPosition)
21+
# It's possible for this to cause issues if in the middle of inrementing
22+
# or decrementing, but the result should only be off by 1. If that's a
23+
# problem, an alternative solution is to stop the state machine, then
24+
# reset both x and the program counter. But that's excessive.
25+
self.sm.exec("set(x, 0)")
26+
27+
def get_position_ticks(self):
28+
ticks = self.sm.get()
29+
if(ticks > 2**31):
30+
ticks -= 2**32
31+
return ticks
32+
33+
def get_position(self):
34+
return self.getTicks() / self.ticksPerShaftRotation
5435

36+
@rp2.asm_pio(in_shiftdir=rp2.PIO.SHIFT_LEFT, out_shiftdir=rp2.PIO.SHIFT_RIGHT)
37+
def _encoder():
38+
# Register descriptions:
39+
# X - Encoder count, as a 32-bit number
40+
# OSR - Previous pin values, only last 2 bits are used
41+
# ISR - Push encoder count, and combine pin states together
5542

56-
# index = self.prevAState*8 + self.prevBState*4 + aState*2 + bState
57-
58-
# print(text, aState, bState, index)
59-
60-
# self.currentPosition += self._ENCODER_LOOKUP_TABLE[index]
61-
62-
# # DEBUG ONLY
63-
# #print(f"{index}, {self._ENCODER_LOOKUP_TABLE[index]}")
64-
65-
# self.prevAState = aState
66-
# self.prevBState = bState
43+
# Jump table
44+
# The program counter is moved to memory address 0000 - 1111, based
45+
# on the previous (left 2 bits) and current (right bits) pin states
46+
jmp("read") # 00 -> 00 No change, continue
47+
jmp("decr") # 00 -> 01 Reverse, decrement count
48+
jmp("incr") # 00 -> 10 Forward, increment count
49+
jmp("read") # 00 -> 11 Impossible, continue
50+
51+
jmp("incr") # 01 -> 00 Forward, increment count
52+
jmp("read") # 01 -> 01 No change, continue
53+
jmp("read") # 01 -> 10 Impossible, continue
54+
jmp("decr") # 01 -> 11 Reverse, decrement count
55+
56+
jmp("decr") # 10 -> 00 Reverse, decrement count
57+
jmp("read") # 10 -> 01 Impossible, continue
58+
jmp("read") # 10 -> 10 No change, continue
59+
jmp("incr") # 10 -> 11 Forward, increment count
60+
61+
jmp("read") # 11 -> 00 Impossible, continue
62+
jmp("incr") # 11 -> 01 Forward, increment count
63+
jmp("decr") # 11 -> 10 Reverse, decrement count
64+
jmp("read") # 11 -> 11 No change, continue
65+
66+
label("read")
67+
mov(osr, isr) # Store previous pin states in OSR
68+
mov(isr, x) # Copy encoder count to ISR
69+
push(noblock) # Push count to RX buffer, and reset ISR to 0
70+
out(isr, 2) # Shift previous pin states into ISR
71+
in_(pins, 2) # Shift current pin states into ISR
72+
mov(pc, isr) # Move PC to jump table to determine what to do next
73+
74+
label("decr") # There is no explicite increment intruction, but X can be
75+
jmp(x_dec, "decr_nop") # decremented in the jump instruction. So we use that and jump
76+
label("decr_nop") # to the next instruction, which is equivalent to just decrementing
77+
jmp("read")
78+
79+
label("incr") # There is no explicite increment intruction, but X can be
80+
mov(x, invert(x)) # decremented in the jump instruction. So we invert X, decrement,
81+
jmp(x_dec, "incr_nop") # then invert again - this is equivalent to incrementing.
82+
label("incr_nop")
83+
mov(x, invert(x))
84+
jmp("read")
85+
86+
# Fill remaining instruction memory with jumps to ensure nothing bad happens
87+
# For some reason, weird behavior happens if the instruction memory isn't full
88+
jmp("read")
89+
jmp("read")
90+
jmp("read")
91+
jmp("read")

0 commit comments

Comments
 (0)