kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Added a reactive encoder example
rodzic
70d1368a2b
commit
6b3ba659a2
|
@ -4,15 +4,26 @@
|
|||
- [Single Motor](#single-motor)
|
||||
- [Multiple Motors](#multiple-motors)
|
||||
- [Motor Cluster](#motor-cluster)
|
||||
- [Simple Easing](#simple-easing)
|
||||
- [Motor Wave](#motor-wave)
|
||||
- [Calibration](#calibration)
|
||||
- [Turn Off Motors](#turn-off-motors)
|
||||
- [Encoder Examples](#encoder-examples)
|
||||
- [Motor Angles](#motor-angles)
|
||||
- [Motor Profiler](#motor-profiler)
|
||||
- [Function Examples](#function-examples)
|
||||
- [Read Sensors](#read-sensors)
|
||||
- [Sensor Feedback](#sensor-feedback)
|
||||
- [Current Meter](#current-meter)
|
||||
- [LED Rainbow](#led-rainbow)
|
||||
- [Turn Off LEDs](#turn-off-leds)
|
||||
- [Control Examples](#control-examples)
|
||||
- [Position Control](#position-control)
|
||||
- [Velocity Control](#velocity-control)
|
||||
- [Position on Velocity Control](#position-on-velocity-control)
|
||||
- [Reactive Encoder](#reactive-encoder)
|
||||
- [Tuning Examples](#tuning-examples)
|
||||
- [Position Tuning](#position-tuning)
|
||||
- [Velocity Tuning](#velocity-tuning)
|
||||
- [Position on Velocity Tuning](#position-on-velocity-tuning)
|
||||
|
||||
|
||||
## Motor Examples
|
||||
|
@ -35,41 +46,50 @@ Demonstrates how to create multiple Motor objects and control them together.
|
|||
Demonstrates how to create a MotorCluster object to control multiple motors at once.
|
||||
|
||||
|
||||
### Simple Easing
|
||||
[simple_easing.py](simple_easing.py)
|
||||
|
||||
An example of how to move a motor smoothly between random positions.
|
||||
|
||||
|
||||
### Motor Wave
|
||||
[motor_wave.py](motor_wave.py)
|
||||
|
||||
An example of applying a wave pattern to a group of motors and the LEDs.
|
||||
|
||||
|
||||
### Calibration
|
||||
[calibration.py](calibration.py)
|
||||
### Turn Off Motors
|
||||
[turn_off_motors.py](turn_off_motors.py)
|
||||
|
||||
Shows how to create motors with different common calibrations, modify a motor's existing calibration, and create a motor with a custom calibration.
|
||||
A simple program that turns off the motors.
|
||||
|
||||
|
||||
## Encoder Examples
|
||||
|
||||
### Motor Angles
|
||||
[motor_angles.py](motor_angles.py)
|
||||
|
||||
Demonstrates how to read the angles of Motor 2040's four encoders.
|
||||
|
||||
|
||||
### Motor Profiler
|
||||
[motor_profiler.py](motor_profiler.py)
|
||||
|
||||
A program that profiles the speed of a motor across its PWM
|
||||
duty cycle range using the attached encoder for feedback.
|
||||
|
||||
|
||||
## Function Examples
|
||||
|
||||
### Read Sensors
|
||||
[read_sensors.py](read_sensors.py)
|
||||
|
||||
TODO
|
||||
Shows how to initialise and read the 6 external and 2 internal sensors of Motor 2040.
|
||||
|
||||
|
||||
### Sensor Feedback
|
||||
[sensor_feedback.py](sensor_feedback.py)
|
||||
|
||||
TODO
|
||||
Show how to read the 6 external sensors and display their values on the neighbouring LEDs.
|
||||
|
||||
|
||||
### Current Meter
|
||||
[current_meter.py](current_meter.py)
|
||||
|
||||
TODO
|
||||
An example of how to use Motor 2040's current measuring ability and display the value on the onboard LED bar.
|
||||
|
||||
|
||||
|
@ -83,3 +103,49 @@ Displays a rotating rainbow pattern on the Motor 2040's onboard LED.
|
|||
[turn_off_led.py](turn_off_led.py)
|
||||
|
||||
A simple program that turns off the onboard LED.
|
||||
|
||||
|
||||
## Control Examples
|
||||
|
||||
### Position Control
|
||||
[position_control.py](position_control.py)
|
||||
|
||||
An example of how to move a motor smoothly between random positions, with the help of it's attached encoder and PID control.
|
||||
|
||||
|
||||
### Velocity Control
|
||||
[velocity_control.py](velocity_control.py)
|
||||
|
||||
An example of how to drive a motor smoothly between random speeds, with the help of it's attached encoder and PID control.
|
||||
|
||||
|
||||
### Position on Velocity Control
|
||||
[position_on_velocity_control.py](position_on_velocity_control.py)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Reactive Encoder
|
||||
[reactive_encoder.py](reactive_encoder.py)
|
||||
|
||||
A demonstration of how a motor with an encoder can be used as a programmable rotary encoder for user input, with force-feedback for arbitrary detents and end stops.
|
||||
|
||||
|
||||
## Tuning Examples
|
||||
|
||||
### Position Tuning
|
||||
[position_tuning.py](position_tuning.py)
|
||||
|
||||
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 target angles and plots the measured response.
|
||||
|
||||
|
||||
### Velocity Tuning
|
||||
[velocity_tuning.py](velocity_tuning.py)
|
||||
|
||||
A program to aid in the discovery and tuning of motor PID values for velocity control. It does this by commanding the motor to drive repeatedly between two target speeds and plots the measured response.
|
||||
|
||||
|
||||
### Position on Velocity Tuning
|
||||
[position_on_velocity_tuning.py](position_on_velocity_tuning.py)
|
||||
|
||||
A program to aid in the discovery and tuning of motor PID values for position on velocity control. It does this by commanding the motor to move repeatedly between two target angles and plots the measured response.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import gc
|
||||
import time
|
||||
from pimoroni import Button
|
||||
from motor import motor2040
|
||||
from encoder import Encoder, MMME_CPR
|
||||
# from encoder import REVERSED
|
||||
|
||||
"""
|
||||
Demonstrates how to read the angles of Motor 2040's four encoders.
|
||||
|
||||
Press "Boot" to exit the program.
|
||||
"""
|
||||
|
||||
GEAR_RATIO = 50 # The gear ratio of the motor
|
||||
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO # The counts per revolution of the motor's output shaft
|
||||
|
||||
|
||||
# Free up hardware resources ahead of creating a new Encoder
|
||||
gc.collect()
|
||||
|
||||
# Create a list of encoders
|
||||
ENCODER_PINS = [motor2040.ENCODER_A, motor2040.ENCODER_B, motor2040.ENCODER_C, motor2040.ENCODER_D]
|
||||
ENCODER_NAMES = ["A", "B", "C", "D"]
|
||||
NUM_ENCODERS = len(ENCODER_PINS)
|
||||
encoders = [Encoder(0, i, ENCODER_PINS[i], counts_per_rev=COUNTS_PER_REV, count_microsteps=True) for i in range(NUM_ENCODERS)]
|
||||
|
||||
# Uncomment the below lines (and the top import) to
|
||||
# reverse the counting direction of an encoder
|
||||
# encoders[0].direction(REVERSED)
|
||||
# encoders[1].direction(REVERSED)
|
||||
# encoders[2].direction(REVERSED)
|
||||
# encoders[3].direction(REVERSED)
|
||||
|
||||
# Create the user button
|
||||
user_sw = Button(motor2040.USER_SW)
|
||||
|
||||
# Read the encoders until the user button is pressed
|
||||
while user_sw.raw() is not True:
|
||||
|
||||
# Print out the angle of each encoder
|
||||
for i in range(NUM_ENCODERS):
|
||||
print(ENCODER_NAMES[i], "=", encoders[i].degrees(), end=", ")
|
||||
print()
|
||||
|
||||
time.sleep(0.1)
|
|
@ -1,40 +0,0 @@
|
|||
import gc
|
||||
import time
|
||||
from pimoroni import Button
|
||||
from encoder import Encoder
|
||||
# from encoder import REVERSED
|
||||
from motor import motor2040
|
||||
|
||||
"""
|
||||
Demonstrates how to read the counts of multiple motor encoders.
|
||||
|
||||
Press "Boot" to exit the program.
|
||||
"""
|
||||
|
||||
# Free up hardware resources ahead of creating a new Encoder
|
||||
gc.collect()
|
||||
|
||||
# Create an encoder on the 3 ADC pins, using PIO 0 and State Machine 0
|
||||
enc_a = Encoder(0, 0, motor2040.ENCODER_A, count_microsteps=True)
|
||||
enc_b = Encoder(0, 1, motor2040.ENCODER_B, count_microsteps=True)
|
||||
enc_c = Encoder(0, 2, motor2040.ENCODER_C, count_microsteps=True)
|
||||
enc_d = Encoder(0, 3, motor2040.ENCODER_D, count_microsteps=True)
|
||||
|
||||
# Uncomment the below line (and the top import) to reverse the counting direction
|
||||
# enc_a.direction(REVERSED)
|
||||
# enc_b.direction(REVERSED)
|
||||
# enc_c.direction(REVERSED)
|
||||
# enc_d.direction(REVERSED)
|
||||
|
||||
# Create the user button
|
||||
user_sw = Button(motor2040.USER_SW)
|
||||
|
||||
# Read the encoders until the user button is pressed
|
||||
while user_sw.raw() is not True:
|
||||
print("A =", enc_a.count(), end=", ")
|
||||
print("B =", enc_b.count(), end=", ")
|
||||
print("C =", enc_c.count(), end=", ")
|
||||
print("D =", enc_d.count())
|
||||
|
||||
time.sleep(0.1)
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import gc
|
||||
import time
|
||||
from pimoroni import Button
|
||||
from encoder import Encoder, MMME_CPR
|
||||
# from encoder import REVERSED
|
||||
from motor import motor2040
|
||||
|
||||
"""
|
||||
Demonstrates how to read the counts of multiple motor encoders.
|
||||
|
||||
Press "Boot" to exit the program.
|
||||
"""
|
||||
|
||||
# Free up hardware resources ahead of creating a new Encoder
|
||||
gc.collect()
|
||||
|
||||
# The gear ratio of the motor the encoder is attached to.
|
||||
GEAR_RATIO = 50
|
||||
CPR = MMME_CPR * GEAR_RATIO
|
||||
|
||||
|
||||
# Create a list of motors
|
||||
ENCODER_PINS = [ motor2040.ENCODER_A, motor2040.ENCODER_B, motor2040.ENCODER_C, motor2040.ENCODER_D ]
|
||||
ENCODER_NAMES = [ "A", "B", "C", "D" ]
|
||||
NUM_ENCODERS = len(ENCODER_PINS)
|
||||
encoders = [Encoder(0, i, ENCODER_PINS[i], counts_per_rev=CPR, count_microsteps=True) for i in range(NUM_ENCODERS)]
|
||||
|
||||
# Uncomment the below line (and the top import) to reverse the counting direction
|
||||
# encoders[0].direction(REVERSED)
|
||||
# encoders[1].direction(REVERSED)
|
||||
# encoders[2].direction(REVERSED)
|
||||
# encoders[3].direction(REVERSED)
|
||||
|
||||
# Create the user button
|
||||
user_sw = Button(motor2040.USER_SW)
|
||||
|
||||
captures = [None] * NUM_ENCODERS
|
||||
|
||||
# Read the encoders until the user button is pressed
|
||||
while user_sw.raw() is not True:
|
||||
# Capture the state of all encoders, at as close to the same moment as possible
|
||||
for i in range(NUM_ENCODERS):
|
||||
captures[i] = encoders[i].take_snapshot()
|
||||
|
||||
# Print out the angle and speed of each encoder
|
||||
for i in range(NUM_ENCODERS):
|
||||
print(ENCODER_NAMES[i], "=", captures[i].degrees(), "|", captures[i].degrees_per_second(), end=", ")
|
||||
|
||||
print()
|
||||
time.sleep(0.1)
|
|
@ -0,0 +1,132 @@
|
|||
import gc
|
||||
import time
|
||||
from pimoroni import Button, PID
|
||||
from plasma import WS2812
|
||||
from motor import Motor, motor2040
|
||||
from encoder import Encoder, MMME_CPR
|
||||
|
||||
"""
|
||||
A demonstration of how a motor with an encoder can be used
|
||||
as a programmable rotary encoder for user input, with
|
||||
force-feedback for arbitrary detents and end stops.
|
||||
|
||||
Press "Boot" to exit the program.
|
||||
|
||||
NOTE: Plasma WS2812 uses the RP2040's PIO system, and as
|
||||
such may have problems when running code multiple times.
|
||||
If you encounter issues, try resetting your board.
|
||||
"""
|
||||
|
||||
MOTOR_PINS = motor2040.MOTOR_A # The pins of the motor being profiled
|
||||
ENCODER_PINS = motor2040.ENCODER_A # The pins of the encoder attached to the profiled motor
|
||||
GEAR_RATIO = 50 # The gear ratio of the motor
|
||||
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO # The counts per revolution of the motor's output shaft
|
||||
|
||||
DIRECTION = 0 # The direction to spin the motor in. NORMAL (0), REVERSED (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
|
||||
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
|
||||
|
||||
DETENT_SIZE = 20 # The size (in degrees) of each detent region
|
||||
MIN_DETENT = -12 # The minimum detent that can be counted to
|
||||
MAX_DETENT = +12 # The maximum detent that can be counted to
|
||||
MAX_DRIVE_PERCENT = 0.5 # The maximum drive force (as a percent) to apply when crossing detents
|
||||
|
||||
# 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
|
||||
|
||||
BRIGHTNESS = 0.4 # The brightness of the LEDs
|
||||
|
||||
|
||||
# Free up hardware resources ahead of creating a new Encoder
|
||||
gc.collect()
|
||||
|
||||
# Create a motor and set its speed scale
|
||||
m = Motor(MOTOR_PINS, direction=DIRECTION, speed_scale=SPEED_SCALE, deadzone=0.05)
|
||||
|
||||
# Create an encoder, using PIO 0 and State Machine 0
|
||||
enc = Encoder(0, 0, ENCODER_PINS, direction=DIRECTION, counts_per_rev=COUNTS_PER_REV, count_microsteps=True)
|
||||
|
||||
# Create the LED, using PIO 0 and State Machine 1
|
||||
led = WS2812(motor2040.NUM_LEDS, 0, 1, motor2040.LED_DATA)
|
||||
|
||||
# Create the user button
|
||||
user_sw = Button(motor2040.USER_SW)
|
||||
|
||||
# Create PID object for position control
|
||||
pos_pid = PID(POS_KP, POS_KI, POS_KD, UPDATE_RATE)
|
||||
|
||||
# Start updating the LED
|
||||
led.start()
|
||||
|
||||
# Enable the motor to get started
|
||||
m.enable()
|
||||
|
||||
|
||||
current_detent = 0
|
||||
|
||||
|
||||
# Function to deal with a detent change
|
||||
def detent_change(change):
|
||||
global current_detent
|
||||
|
||||
# Update the current detent and pid target
|
||||
current_detent += change
|
||||
pos_pid.target = (current_detent * DETENT_SIZE) # Update the motor position target
|
||||
print("Detent =", current_detent)
|
||||
|
||||
# Convert the current detent to a hue and set the onboard led to it
|
||||
hue = (current_detent - MIN_DETENT) / (MAX_DETENT - MIN_DETENT)
|
||||
led.set_hsv(0, hue, 1.0, BRIGHTNESS)
|
||||
|
||||
|
||||
# Call the function once to set the target and print the value
|
||||
detent_change(0)
|
||||
|
||||
|
||||
# Continually move the motor until the user button is pressed
|
||||
while user_sw.raw() is not True:
|
||||
|
||||
# Capture the state of the encoder
|
||||
capture = enc.capture()
|
||||
|
||||
# Get the current detent's centre angle
|
||||
detent_angle = (current_detent * DETENT_SIZE)
|
||||
|
||||
# Is the current angle above the region of this detent?
|
||||
if capture.degrees > detent_angle + (DETENT_SIZE / 2):
|
||||
# Is there another detent we can move to?
|
||||
if current_detent < MAX_DETENT:
|
||||
detent_change(1) # Increment to the next detent
|
||||
|
||||
# Is the current angle below the region of this detent?
|
||||
elif capture.degrees < detent_angle - (DETENT_SIZE / 2):
|
||||
# Is there another detent we can move to?
|
||||
if current_detent > MIN_DETENT:
|
||||
detent_change(-1) # Decrement to the next detent
|
||||
|
||||
# Calculate the velocity to move the motor closer to the position target
|
||||
vel = pos_pid.calculate(capture.degrees, capture.degrees_per_second)
|
||||
|
||||
# If the current angle is within the detent range, limit the max vel
|
||||
# (aka feedback force) that the user will feel when turning the motor between detents
|
||||
if (capture.degrees >= MIN_DETENT * DETENT_SIZE) and (capture.degrees <= MAX_DETENT * DETENT_SIZE):
|
||||
vel = max(min(vel, MAX_DRIVE_PERCENT), -MAX_DRIVE_PERCENT)
|
||||
|
||||
# Set the new motor driving speed
|
||||
m.speed(vel)
|
||||
|
||||
time.sleep(UPDATE_RATE)
|
||||
|
||||
# Disable the motor
|
||||
m.disable()
|
||||
|
||||
# Turn off the LED
|
||||
led.clear()
|
|
@ -1,64 +0,0 @@
|
|||
import time
|
||||
import math
|
||||
import random
|
||||
from pimoroni import Button
|
||||
from servo import Servo, servo2040
|
||||
|
||||
"""
|
||||
An example of how to move a servo smoothly between random positions.
|
||||
|
||||
Press "Boot" to exit the program.
|
||||
"""
|
||||
|
||||
UPDATES = 50 # How many times to update Servos per second
|
||||
TIME_FOR_EACH_MOVE = 2 # The time to travel between each random value
|
||||
UPDATES_PER_MOVE = TIME_FOR_EACH_MOVE * UPDATES
|
||||
|
||||
SERVO_EXTENT = 80 # How far from zero to move the servo
|
||||
USE_COSINE = True # Whether or not to use a cosine path between values
|
||||
|
||||
# Create a servo on pin 0
|
||||
s = Servo(servo2040.SERVO_1)
|
||||
|
||||
# Get the initial value and create a random end value between the extents
|
||||
start_value = s.mid_value()
|
||||
end_value = random.uniform(-SERVO_EXTENT, SERVO_EXTENT)
|
||||
|
||||
# Create the user button
|
||||
user_sw = Button(servo2040.USER_SW)
|
||||
|
||||
|
||||
update = 0
|
||||
|
||||
# Continually move the servo until the user button is pressed
|
||||
while user_sw.raw() is not True:
|
||||
|
||||
# Calculate how far along this movement to be
|
||||
percent_along = update / UPDATES_PER_MOVE
|
||||
|
||||
if USE_COSINE:
|
||||
# Move the servo between values using cosine
|
||||
s.to_percent(math.cos(percent_along * math.pi), 1.0, -1.0, start_value, end_value)
|
||||
else:
|
||||
# Move the servo linearly between values
|
||||
s.to_percent(percent_along, 0.0, 1.0, start_value, end_value)
|
||||
|
||||
# Print out the value the servo is now at
|
||||
print("Value = ", round(s.value(), 3), sep="")
|
||||
|
||||
# Move along in time
|
||||
update += 1
|
||||
|
||||
# Have we reached the end of this movement?
|
||||
if update >= UPDATES_PER_MOVE:
|
||||
# Reset the counter
|
||||
update = 0
|
||||
|
||||
# Set the start as the last end and create a new random end value
|
||||
start_value = end_value
|
||||
end_value = random.uniform(-SERVO_EXTENT, SERVO_EXTENT)
|
||||
|
||||
time.sleep(1.0 / UPDATES)
|
||||
|
||||
# Disable the servo
|
||||
s.disable()
|
Ładowanie…
Reference in New Issue