forked from pimoroni/inventorhatmini-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathposition_on_velocity_control.py
More file actions
139 lines (103 loc) · 5.42 KB
/
position_on_velocity_control.py
File metadata and controls
139 lines (103 loc) · 5.42 KB
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
import time
import math
import random
from inventorhatmini import InventorHATMini, MOTOR_A
from ioexpander.common import PID, NORMAL_DIR # , REVERSED_DIR
"""
An example of how to move a motor smoothly between random positions,
with velocity limits, with the help of it's attached encoder and PID control.
Press "User" to exit the program.
"""
# Constants
GEAR_RATIO = 50 # The gear ratio of the motor
DIRECTION = NORMAL_DIR # The direction to spin the motor in. NORMAL_DIR (0), REVERSED_DIR (1)
SPEED_SCALE = 5.4 # The scaling to apply to the motor's speed to match its real-world speed
UPDATES = 100 # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATES
TIME_FOR_EACH_MOVE = 1 # The time to travel between each random value, in seconds
UPDATES_PER_MOVE = TIME_FOR_EACH_MOVE * UPDATES
PRINT_DIVIDER = 6 # How many of the updates should be printed (i.e. 2 would be every other update)
# Multipliers for the different printed values, so they appear nicely on the Thonny plotter
ACC_PRINT_SCALE = 2 # Acceleration multiplier
SPD_PRINT_SCALE = 40 # Driving Speed multiplier
POSITION_EXTENT = 180 # How far from zero to move the motor, in degrees
MAX_SPEED = 1.0 # The maximum speed to move the motor at, in revolutions per second
INTERP_MODE = 0 # The interpolating mode between setpoints. STEP (0), LINEAR (1), COSINE (2)
# PID values
POS_KP = 0.03 # Position proportional (P) gain
POS_KI = 0.0 # Position integral (I) gain
POS_KD = 0.0 # Position derivative (D) gain
VEL_KP = 30.0 # Velocity proportional (P) gain
VEL_KI = 0.0 # Velocity integral (I) gain
VEL_KD = 0.4 # Velocity derivative (D) gain
# Create a new InventorHATMini and get a motor and encoder from it
board = InventorHATMini(motor_gear_ratio=GEAR_RATIO, init_leds=False)
m = board.motors[MOTOR_A]
enc = board.encoders[MOTOR_A]
# Set the motor's speed scale
m.speed_scale(SPEED_SCALE)
# Set the motor and encoder's direction
m.direction(DIRECTION)
enc.direction(DIRECTION)
# Create PID objects for both position and velocity control
pos_pid = PID(POS_KP, POS_KI, POS_KD, UPDATE_RATE)
vel_pid = PID(VEL_KP, VEL_KI, VEL_KD, UPDATE_RATE)
# Enable the motor to get started
m.enable()
update = 0
print_count = 0
# Set the initial value and create a random end value between the extents
start_value = 0.0
end_value = random.uniform(-POSITION_EXTENT, POSITION_EXTENT)
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = end_time - time.monotonic()
if time_to_sleep > 0.0:
time.sleep(time_to_sleep)
# Continually move the motor until the user button is pressed
while not board.switch_pressed():
# Record the start time of this loop
start_time = time.monotonic()
# Capture the state of the encoder
capture = enc.capture()
# Calculate how far along this movement to be
percent_along = min(update / UPDATES_PER_MOVE, 1.0)
if INTERP_MODE == 0:
# Move the motor instantly to the end value
pos_pid.setpoint = end_value
elif INTERP_MODE == 2:
# Move the motor between values using cosine
pos_pid.setpoint = (((-math.cos(percent_along * math.pi) + 1.0) / 2.0) * (end_value - start_value)) + start_value
else:
# Move the motor linearly between values
pos_pid.setpoint = (percent_along * (end_value - start_value)) + start_value
# Calculate the velocity to move the motor closer to the position setpoint
vel = pos_pid.calculate(capture.degrees, capture.degrees_per_second)
# Limit the velocity between user defined limits, and set it as the new setpoint of the velocity PID
vel_pid.setpoint = max(min(vel, MAX_SPEED), -MAX_SPEED)
# Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
accel = vel_pid.calculate(capture.revolutions_per_second)
# Accelerate or decelerate the motor
m.speed(m.speed() + (accel * UPDATE_RATE))
# Print out the current motor values and their setpoints, but only on every multiple
if print_count == 0:
print("Pos =", capture.degrees, end=", ")
print("Pos SP =", pos_pid.setpoint, end=", ")
print("Vel =", capture.revolutions_per_second * SPD_PRINT_SCALE, end=", ")
print("Vel SP =", vel_pid.setpoint * SPD_PRINT_SCALE, end=", ")
print("Accel =", accel * ACC_PRINT_SCALE, end=", ")
print("Speed =", m.speed() * SPD_PRINT_SCALE)
# Increment the print count, and wrap it
print_count = (print_count + 1) % PRINT_DIVIDER
update += 1 # Move along in time
# Have we reached the end of this movement?
if update >= UPDATES_PER_MOVE:
update = 0 # Reset the counter
# Set the start as the last end and create a new random end value
start_value = end_value
end_value = random.uniform(-POSITION_EXTENT, POSITION_EXTENT)
# Sleep until the next update, accounting for how long the above operations took to perform
sleep_until(start_time + UPDATE_RATE)
# Disable the motor
m.disable()