-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5e395cf
commit 7d33120
Showing
12 changed files
with
845 additions
and
81 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import math | ||
from pimoroni_yukon import Yukon | ||
from pimoroni_yukon.modules import BigMotorModule | ||
from pimoroni_yukon.timing import ticks_ms, ticks_add | ||
from motor import MotorCluster | ||
|
||
""" | ||
How to drive up to 6 motors from a set of Big Motor + Encoder Modules connected to Slots, using a MotorCluster. | ||
A wave pattern will be played on the attached motors. | ||
The MotorCluster controls the whole set of motors using PIO. | ||
Note: Is not possible to use more than 4 encoders whilst using a motor cluster, hence encoders being omitted from this example. | ||
Attempting to do so causes a hard lock. This is likely a PIO program space limitation, though this needs to be investigated | ||
""" | ||
|
||
# Constants | ||
SPEED = 0.005 # How much to advance the motor phase offset by each update | ||
UPDATES = 50 # How many times to update the motors per second | ||
SPEED_EXTENT = 1.0 # How far from zero to drive the motors | ||
CURRENT_LIMIT = 0.5 # The maximum current (in amps) the motors will be driven with | ||
WAVE_SCALE = 1.0 # A scale to apply to the phase calculation to expand or contract the wave | ||
CLUSTER_PIO = 0 # The PIO system to use (0 or 1) to drive the motor cluster | ||
CLUSTER_SM = 0 # The State Machines (SM) to use to drive the motor cluster | ||
|
||
# Variables | ||
yukon = Yukon() # Create a new Yukon object | ||
modules = [] # A list to store QuadServo module objects created later | ||
phase_offset = 0 # The offset used to animate the motors | ||
|
||
|
||
# Function to get a motor speed from its index | ||
def speed_from_index(index, offset=0.0): | ||
phase = (((index * WAVE_SCALE) / BigMotorModule.NUM_MOTORS) + offset) * math.pi * 2 | ||
speed = math.sin(phase) * SPEED_EXTENT | ||
return speed | ||
|
||
# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt) | ||
try: | ||
# Find out which slots of Yukon have BigMotorModule attached | ||
for slot in yukon.find_slots_with(BigMotorModule): | ||
module = BigMotorModule(init_motor=False, # Create a BigMotorModule object | ||
init_encoder=False) | ||
yukon.register_with_slot(module, slot) # Register the BigMotorModule object with the slot | ||
modules.append(module) # Add the object to the module list | ||
|
||
# Record the number of motors that will be driven | ||
NUM_MOTORS = len(modules) * BigMotorModule.NUM_MOTORS | ||
print(f"Up to {NUM_MOTORS} motors available") | ||
|
||
yukon.verify_and_initialise() # Verify that BigMotorModules are attached to Yukon, and initialise them | ||
|
||
# Create a MotorCluster object, with a list of motor pin pairs to control. | ||
# The pin list is created using list comprehension | ||
motors = MotorCluster(CLUSTER_PIO, CLUSTER_SM, | ||
pins=[module.motor_pins for module in modules]) | ||
|
||
yukon.enable_main_output() # Turn on power to the module slots | ||
|
||
for module in modules: | ||
module.enable() # Enable the motor driver on the BigMotorModule | ||
|
||
current_time = ticks_ms() # Record the start time of the program loop | ||
|
||
# Loop until the BOOT/USER button is pressed | ||
while not yukon.is_boot_pressed(): | ||
|
||
# Give all the motors new speeds | ||
for current_motor in range(motors.count()): | ||
speed = speed_from_index(current_motor, phase_offset) | ||
motors.speed(current_motor, speed) | ||
|
||
# Advance the phase offset, wrapping if it exceeds 1.0 | ||
phase_offset += SPEED | ||
if phase_offset >= 1.0: | ||
phase_offset -= 1.0 | ||
|
||
print(f"Phase = {phase_offset}") | ||
|
||
# Advance the current time by a number of seconds | ||
current_time = ticks_add(current_time, int(1000 / UPDATES)) | ||
|
||
# Monitor sensors until the current time is reached, recording the min, max, and average for each | ||
# This approach accounts for the updating of the rainbows taking a non-zero amount of time to complete | ||
yukon.monitor_until_ms(current_time) | ||
|
||
finally: | ||
# Put the board back into a safe state, regardless of how the program may have ended | ||
yukon.reset() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import math | ||
from pimoroni_yukon import Yukon | ||
from pimoroni_yukon.modules import BigMotorModule | ||
from pimoroni_yukon.timing import ticks_ms, ticks_add | ||
|
||
""" | ||
How to drive up to 4 motors from a set of Big Motor + Encoder Modules connected to Slots. | ||
A wave pattern will be played on the attached motors, and their speeds printed out. | ||
To use more motors, look at the all_motors_no_encoders.py example. | ||
""" | ||
|
||
# Constants | ||
SPEED = 0.005 # How much to advance the motor phase offset by each update | ||
UPDATES = 50 # How many times to update the motors per second | ||
SPEED_EXTENT = 1.0 # How far from zero to drive the motors | ||
WAVE_SCALE = 1.0 # A scale to apply to the phase calculation to expand or contract the wave | ||
|
||
# Variables | ||
yukon = Yukon() # Create a new Yukon object | ||
modules = [] # A list to store BigMotorModule objects created later | ||
phase_offset = 0 # The offset used to animate the motors | ||
|
||
|
||
# Function to get a motor speed from its index | ||
def speed_from_index(index, offset=0.0): | ||
phase = (((index * WAVE_SCALE) / BigMotorModule.NUM_MOTORS) + offset) * math.pi * 2 | ||
speed = math.sin(phase) * SPEED_EXTENT | ||
return speed | ||
|
||
|
||
# Generator to get the next PIO and State Machine numbers | ||
def pio_and_sm_generator(): | ||
pio = 0 | ||
sm = 0 | ||
while True: | ||
yield (pio, sm) # Return the next pair of PIO and SM values | ||
|
||
sm += 1 # Advance by one SM | ||
|
||
# Wrap the SM and increment the PIO | ||
if sm > 3: | ||
sm -= 4 | ||
pio += 1 | ||
|
||
|
||
pio_and_sm = pio_and_sm_generator() # An instance of the generator | ||
|
||
# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt) | ||
try: | ||
# Find out which slots of Yukon have BigMotorModule attached | ||
for slot in yukon.find_slots_with(BigMotorModule): | ||
pio, sm = next(pio_and_sm) # Get the next PIO and State Machine numbers | ||
module = BigMotorModule(encoder_pio=pio, # Create a BigMotorModule object, with a specific PIO and SM for each encoder | ||
encoder_sm=sm) | ||
yukon.register_with_slot(module, slot) # Register the BigMotorModule object with the slot | ||
modules.append(module) # Add the object to the module list | ||
|
||
# Record the number of motors that will be driven | ||
NUM_MOTORS = len(modules) * BigMotorModule.NUM_MOTORS | ||
print(f"Up to {NUM_MOTORS} motors available") | ||
|
||
yukon.verify_and_initialise() # Verify that BigMotorModules are attached to Yukon, and initialise them | ||
yukon.enable_main_output() # Turn on power to the module slots | ||
|
||
for module in modules: | ||
module.enable() # Enable the motor driver on the BigMotorModule | ||
|
||
current_time = ticks_ms() # Record the start time of the program loop | ||
|
||
# Loop until the BOOT/USER button is pressed | ||
while not yukon.is_boot_pressed(): | ||
|
||
# Read all the encoders and give all the motors new speeds | ||
current_motor = 0 | ||
for module in modules: | ||
capture = module.encoder.capture() # Capture the state of the encoder | ||
print(f"RPS{current_motor} = {capture.revolutions_per_second}", end=", ") # Print out the measured speed of the motor | ||
|
||
speed = speed_from_index(current_motor, phase_offset) | ||
module.motor.speed(speed) | ||
current_motor += 1 | ||
print() | ||
|
||
# Advance the phase offset, wrapping if it exceeds 1.0 | ||
phase_offset += SPEED | ||
if phase_offset >= 1.0: | ||
phase_offset -= 1.0 | ||
|
||
# Advance the current time by a number of seconds | ||
current_time = ticks_add(current_time, int(1000 / UPDATES)) | ||
|
||
# Monitor sensors until the current time is reached, recording the min, max, and average for each | ||
# This approach accounts for the updating of the rainbows taking a non-zero amount of time to complete | ||
yukon.monitor_until_ms(current_time) | ||
|
||
finally: | ||
# Put the board back into a safe state, regardless of how the program may have ended | ||
yukon.reset() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import math | ||
import random | ||
from pimoroni import PID, NORMAL_DIR # , REVERSED_DIR | ||
from pimoroni_yukon import Yukon | ||
from pimoroni_yukon import SLOT1 as SLOT | ||
from pimoroni_yukon.modules import BigMotorModule | ||
from pimoroni_yukon.timing import ticks_ms, ticks_add | ||
|
||
""" | ||
How to drive a motor smoothly between random positions, with the help of it's attached encoder and PID control. | ||
This uses a Big Motor + Encoder Module connected to Slot1. | ||
Press "Boot/User" to exit the program. | ||
""" | ||
|
||
# Constants | ||
GEAR_RATIO = 30 # The gear ratio of the motor | ||
ENCODER_CPR = 12 # The number of counts a single encoder shaft revolution will produce | ||
MOTOR_CPR = GEAR_RATIO * ENCODER_CPR # The number of counts a single motor shaft revolution will produce | ||
|
||
MOTOR_DIRECTION = NORMAL_DIR # The direction to spin the motor in. NORMAL_DIR (0), REVERSED_DIR (1) | ||
ENCODER_DIRECTION = NORMAL_DIR # The direction the encoder counts positive in. NORMAL_DIR (0), REVERSED_DIR (1) | ||
SPEED_SCALE = 3.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 | ||
UPDATES_PER_MOVE = TIME_FOR_EACH_MOVE * UPDATES | ||
PRINT_DIVIDER = 4 # 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 | ||
SPD_PRINT_SCALE = 20 # Driving Speed multipler | ||
|
||
POSITION_EXTENT = 180 # How far from zero to move the motor, in degrees | ||
INTERP_MODE = 2 # The interpolating mode between setpoints. STEP (0), LINEAR (1), COSINE (2) | ||
|
||
# PID values | ||
POS_KP = 0.14 # Position proportional (P) gain | ||
POS_KI = 0.0 # Position integral (I) gain | ||
POS_KD = 0.0022 # Position derivative (D) gain | ||
|
||
|
||
# Variables | ||
yukon = Yukon() # Create a new Yukon object | ||
module = BigMotorModule(counts_per_rev=MOTOR_CPR) # Create a BigMotorModule object | ||
pos_pid = PID(POS_KP, POS_KI, POS_KD, UPDATE_RATE) # Create a PID object for position control | ||
update = 0 | ||
print_count = 0 | ||
|
||
# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt) | ||
try: | ||
yukon.register_with_slot(module, SLOT) # Register the BigMotorModule object with the slot | ||
yukon.verify_and_initialise() # Verify that a BigMotorModule is attached to Yukon, and initialise it | ||
yukon.enable_main_output() # Turn on power to the module slots | ||
|
||
module.motor.speed_scale(SPEED_SCALE) # Set the motor's speed scale | ||
|
||
# Set the motor and encoder's direction | ||
module.motor.direction(MOTOR_DIRECTION) | ||
module.encoder.direction(ENCODER_DIRECTION) | ||
|
||
module.enable() # Enable the motor driver on the BigMotorModule | ||
module.motor.enable() # Enable the motor to get started | ||
|
||
# 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) | ||
|
||
current_time = ticks_ms() # Record the start time of the program loop | ||
|
||
# Loop until the BOOT/USER button is pressed | ||
while not yukon.is_boot_pressed(): | ||
|
||
capture = module.encoder.capture() # Capture the state of the encoder | ||
|
||
# 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) | ||
|
||
module.motor.speed(vel) # Set the new motor driving speed | ||
|
||
# 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("Speed = ", module.motor.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) | ||
|
||
# Advance the current time by a number of seconds | ||
current_time = ticks_add(current_time, int(1000 * UPDATE_RATE)) | ||
|
||
# Monitor sensors until the current time is reached, recording the min, max, and average for each | ||
# This approach accounts for the updating of the rainbows taking a non-zero amount of time to complete | ||
yukon.monitor_until_ms(current_time) | ||
|
||
module.motor.disable() # Disable the motor | ||
|
||
finally: | ||
# Put the board back into a safe state, regardless of how the program may have ended | ||
yukon.reset() |
Oops, something went wrong.