Skip to content

Commit

Permalink
Finished off big motor examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ZodiusInfuser committed Nov 3, 2023
1 parent 5e395cf commit 7d33120
Show file tree
Hide file tree
Showing 12 changed files with 845 additions and 81 deletions.
57 changes: 0 additions & 57 deletions docs/audio_amp.md

This file was deleted.

89 changes: 89 additions & 0 deletions examples/modules/big_motor/all_motors_no_encoders.py
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()
99 changes: 99 additions & 0 deletions examples/modules/big_motor/multiple_motors.py
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()
124 changes: 124 additions & 0 deletions examples/modules/big_motor/position_control.py
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()
Loading

0 comments on commit 7d33120

Please sign in to comment.