Added a reactive encoder example

pull/352/head
ZodiusInfuser 2022-04-21 21:14:41 +01:00
rodzic 70d1368a2b
commit 6b3ba659a2
6 zmienionych plików z 257 dodań i 168 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()