-
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
5d4b613
commit 5e395cf
Showing
3 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
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,57 @@ | ||
import math | ||
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 single motor from a Big Motor + Encoder Module connected to Slot1. | ||
A wave pattern will be played on the attached motor. | ||
""" | ||
|
||
# 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 | ||
|
||
# Variables | ||
yukon = Yukon() # Create a new Yukon object | ||
module = BigMotorModule() # Create a BigMotorModule object | ||
phase_offset = 0 # The offset used to animate the motors | ||
|
||
# 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.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 the motor a new speed | ||
phase = phase_offset * math.pi * 2 | ||
speed = math.sin(phase) * SPEED_EXTENT | ||
module.motor.speed(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,105 @@ | ||
from pimoroni import NORMAL_DIR # , REVERSED_DIR | ||
from pimoroni_yukon import Yukon | ||
from pimoroni_yukon import SLOT1 as SLOT | ||
from pimoroni_yukon.modules import BigMotorModule | ||
|
||
""" | ||
A program that profiles the speed of a motor across its PWM duty cycle range using the attached encoder for feedback. | ||
Note that the returned readings will only be valid for a single input voltage. | ||
""" | ||
|
||
# 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 | ||
|
||
DIRECTION = NORMAL_DIR # The direction to spin the motor in. NORMAL_DIR (0), REVERSED_DIR (1) | ||
SPEED_SCALE = 3.4 # The scaling to apply to the motor's speed. Set this to the maximum measured speed | ||
ZERO_POINT = 0.0 # The duty cycle that corresponds with zero speed when plotting the motor's speed as a straight line | ||
DEAD_ZONE = 0.0 # The duty cycle below which the motor's friction prevents it from moving | ||
|
||
DUTY_STEPS = 100 # How many duty cycle steps to sample the speed of | ||
SETTLE_TIME = 0.1 # How long to wait after changing motor duty cycle | ||
CAPTURE_TIME = 0.2 # How long to capture the motor's speed at each step | ||
|
||
# Variables | ||
yukon = Yukon() # Create a new Yukon object | ||
module = BigMotorModule(counts_per_rev=MOTOR_CPR) # Create a BigMotorModule object | ||
|
||
# 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 | ||
|
||
# Set the motor's speed scale, zeropoint, and deadzone | ||
module.motor.speed_scale(SPEED_SCALE) | ||
module.motor.zeropoint(ZERO_POINT) | ||
module.motor.deadzone(DEAD_ZONE) | ||
|
||
# Set the motor and encoder's direction | ||
module.motor.direction(DIRECTION) | ||
module.encoder.direction(DIRECTION) | ||
|
||
# Function that performs a single profiling step | ||
def profile_at_duty(motor, encoder, duty): | ||
# Set the motor to a new duty cycle and wait for it to settle | ||
if DIRECTION == 1: | ||
motor.duty(0.0 - duty) | ||
else: | ||
motor.duty(duty) | ||
yukon.monitored_sleep(SETTLE_TIME) | ||
|
||
# Perform a dummy capture to clear the encoder | ||
encoder.capture() | ||
|
||
# Wait for the capture time to pass | ||
yukon.monitored_sleep(CAPTURE_TIME) | ||
|
||
# Perform a capture and read the measured speed | ||
capture = encoder.capture() | ||
measured_speed = capture.revolutions_per_second | ||
|
||
# These are some alternate speed measurements from the encoder | ||
# measured_speed = capture.revolutions_per_minute | ||
# measured_speed = capture.degrees_per_second | ||
# measured_speed = capture.radians_per_second | ||
|
||
# Print out the expected and measured speeds, as well as their difference | ||
print("Duty =", motor.duty(), end=", ") | ||
print("Expected =", motor.speed(), end=", ") | ||
print("Measured =", measured_speed, end=", ") | ||
print("Diff =", motor.speed() - measured_speed) | ||
|
||
module.enable() # Enable the motor driver on the BigMotorModule | ||
module.motor.enable() # Enable the motor to get started | ||
|
||
print("Profiler Starting...") | ||
|
||
# Profile from 0% up to one step below 100% | ||
for i in range(DUTY_STEPS): | ||
profile_at_duty(module.motor, module.encoder, i / DUTY_STEPS) | ||
|
||
# Profile from 100% down to one step above 0% | ||
for i in range(DUTY_STEPS): | ||
profile_at_duty(module.motor, module.encoder, (DUTY_STEPS - i) / DUTY_STEPS) | ||
|
||
# Profile from 0% down to one step above -100% | ||
for i in range(DUTY_STEPS): | ||
profile_at_duty(module.motor, module.encoder, -i / DUTY_STEPS) | ||
|
||
# Profile from -100% up to one step below 0% | ||
for i in range(DUTY_STEPS): | ||
profile_at_duty(module.motor, module.encoder, -(DUTY_STEPS - i) / DUTY_STEPS) | ||
|
||
# Profile 0% again | ||
profile_at_duty(module.motor, module.encoder, 0) | ||
|
||
print("Profiler Finished...") | ||
|
||
# Disable the motor now the profiler has finished | ||
module.motor.disable() | ||
|
||
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,109 @@ | ||
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 | ||
|
||
""" | ||
A program to aid in the discovery and tuning of motor PID values for position control. | ||
It does this by commanding the motor to move repeatedly between two setpoint angles and | ||
plots the measured response. | ||
""" | ||
|
||
# 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 | ||
|
||
DIRECTION = NORMAL_DIR # The direction to spin the motor 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 | ||
PRINT_WINDOW = 0.25 # The time (in seconds) after a new setpoint, to display print out motor values | ||
MOVEMENT_WINDOW = 2.0 # The time (in seconds) between each new setpoint being set | ||
PRINT_DIVIDER = 1 # 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 = 10 # Driving Speed multipler | ||
|
||
POSITION_EXTENT = 90 # How far from zero to move the motor, in degrees | ||
|
||
# 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 | ||
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(DIRECTION) | ||
module.encoder.direction(DIRECTION) | ||
|
||
# Create PID object for position control | ||
pos_pid = PID(POS_KP, POS_KI, POS_KD, UPDATE_RATE) | ||
|
||
module.enable() # Enable the motor driver on the BigMotorModule | ||
module.motor.enable() # Enable the motor to get started | ||
|
||
pos_pid.setpoint = POSITION_EXTENT # Set the initial setpoint position | ||
|
||
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 the state of the encoder | ||
capture = module.encoder.capture() | ||
|
||
# Calculate the velocity to move the motor closer to the position setpoint | ||
vel = pos_pid.calculate(capture.degrees, capture.degrees_per_second) | ||
|
||
# Set the new motor driving speed | ||
module.motor.speed(vel) | ||
|
||
# Print out the current motor values and their setpoints, | ||
# but only for the first few updates and only every multiple | ||
if update < (PRINT_WINDOW * UPDATES) and 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 time window? | ||
if update >= (MOVEMENT_WINDOW * UPDATES): | ||
update = 0 # Reset the counter | ||
|
||
# Set the new position setpoint to be the inverse of the current setpoint | ||
pos_pid.setpoint = 0.0 - pos_pid.setpoint | ||
|
||
# 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) | ||
|
||
# Disable the motor | ||
module.motor.disable() | ||
|
||
finally: | ||
# Put the board back into a safe state, regardless of how the program may have ended | ||
yukon.reset() |