pimoroni / pimoroni-pico

Libraries and examples to support Pimoroni Pico add-ons in C++ and MicroPython.
https://shop.pimoroni.com/collections/pico
MIT License
1.23k stars 474 forks source link

Inventor 2040 W, some examples don't work correctly? #922

Closed alphanumeric007 closed 3 months ago

alphanumeric007 commented 3 months ago

I have an Inventor 2040 W with two micro metal gear motors plus encoders. Both are connected to the motor connectors with the required 6 wire cables.

read_encodes.py runs fine, and shows the correct rotation as I turn the wheels by hand.

reactive_encoder.py fails. If I turn the wheel clockwise it immediately starts turning on its own and won't stop. Going counter clockwise works until I go past detent 11. Then it starts spinning counterclockwise and won't stop.

driving_sequence.py turns both motors on and they just continuously run at 2.1 with no change in speed or direction.

LEFT = -2.212428, RIGHT = -2.211421, 
LEFT = -2.203851, RIGHT = -2.210657, 
LEFT = -2.20833, RIGHT = -2.197961, 
LEFT = -2.220342, RIGHT = -2.190321, 

The LED's continue to cycle colors until I click stop.

If you'd like me to run another example just let me know. Something is broken with my setup? I don't know if its hardware or software, but something is messed up somewhere. I get the same results with Pimoroni v1.21.0 and v1.22.2.

ZodiusInfuser commented 3 months ago

What gear ratio motors are you using? Some motors will have an additional gear sprocket and so need the encoder's direction reversing (with respect to the motors direction) to count in the same direction as the motor shaft. I know this is the case for the 110:1's, for example.

ZodiusInfuser commented 3 months ago

Here's a minimal example:

import time
from pimoroni import NORMAL_DIR, REVERSED_DIR
from inventor import Inventor2040W, MOTOR_A

GEAR_RATIO = 110                         # The gear ratio of the motor

board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
m = board.motors[MOTOR_A]
enc = board.encoders[MOTOR_A]

# Set the motor and encoder's direction
m.direction(NORMAL_DIR)
enc.direction(REVERSED_DIR)
alphanumeric007 commented 3 months ago

I've been playing around with similar code here: https://forums.pimoroni.com/t/how-to-read-encoders-on-inventor-2040w/24507/12

enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]
A = board.motors[MOTOR_A]
B = board.motors[MOTOR_B]
A.speed_scale(SPEED_SCALE)
B.speed_scale(SPEED_SCALE)
A.direction(DIRECTION)
enc_A.direction(DIRECTION)
ZodiusInfuser commented 3 months ago

I've been playing around with similar code here: https://forums.pimoroni.com/t/how-to-read-encoders-on-inventor-2040w/24507/12

Hel has pointed me to that forum thread now I am back from my easter holiday, which I was in the process of replying to.

In fact, whilst we're here, I believe the issue in that case is your inconsistent calling of the .capture() function. See a .capture() calculates its speed values by taking the difference in the number of encoder counts detected between the last and current capture (with some PIO magic for higher accuracy). Therefore it is beneficial to have equal timing between each capture.

Taking an extract from the code you posted:

A.enable()
B.enable()
time.sleep(1)

# Drive at full positive
A.speed(1.0)
B.speed(1.0)
time.sleep(1)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
#print (capture_A)
print("A Speed =", A.speed())
print("B Speed =", B.speed())
time.sleep(5)# Access the motor from Inventor and enable it

# Stop moving
A.stop()
B.stop()
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("B Speed =", B.speed())
time.sleep(2)# Access the motor from Inventor and enable it

A.speed(0.5)
B.speed(0.5)
time.sleep(1)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("B Speed =", B.speed())
...

What is actually happening is:

Checking the documentation for Encoder I see that this behaviour is not properly described. I'll add a note about this. Also, I have whipped up this speed reading example just now that I will also get merged. I have tested and gives reliable readings for 50:1 gear ratio motors.

import time
from inventor import Inventor2040W, NUM_MOTORS  # , MOTOR_A, MOTOR_B
# from pimoroni import REVERSED_DIR

"""
Demonstrates how to read the speeds of Inventor 2040 W's two encoders.

Press "User" to exit the program.
"""

# Wheel friendly names
NAMES = ["LEFT", "RIGHT"]

# Constants
GEAR_RATIO = 50                         # The gear ratio of the motor
SPEED = 1.0                             # The speed to drive the motors at
SLEEP = 0.1                             # The time to sleep between each capture

# Create a new Inventor2040W
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)

# Uncomment the below lines (and the top imports) to
# reverse the counting direction of an encoder
# encoders[MOTOR_A].direction(REVERSED_DIR)
# encoders[MOTOR_B].direction(REVERSED_DIR)

# Set both motors driving
for motor in board.motors:
    motor.speed(SPEED)

# Variables for storing encoder captures
captures = [None] * NUM_MOTORS

# Read the encoders until the user button is pressed
while not board.switch_pressed():

    # Capture the state of all the encoders since the last capture, SLEEP seconds ago
    for i in range(NUM_MOTORS):
        captures[i] = board.encoders[i].capture()

    # Print out the speeds from each encoder
    for i in range(NUM_MOTORS):
        print(NAMES[i], "=", captures[i].revolutions_per_second, end=", ")
    print()

    time.sleep(SLEEP)

As for this github issue...

enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]
A = board.motors[MOTOR_A]
B = board.motors[MOTOR_B]
A.speed_scale(SPEED_SCALE)
B.speed_scale(SPEED_SCALE)
A.direction(DIRECTION)
enc_A.direction(DIRECTION)

assuming you are using 110:1 motors as that forum thread indicates, then you need to give your enc_A and enc_B opposite directions to your A and B. What you are observing from reactive_encoder.py and driving_sequence.py is the code entering a feedback loop, where it attempts to get the motor to drive in one direction but the encoder reads the reverse, so it increases the motors speed in an attempt to correct it, making matters worse. So this is what needs to be done:

A.direction(NORMAL_DIR)
enc_A.direction(REVERSED_DIR)
alphanumeric007 commented 3 months ago
import time
from inventor import Inventor2040W, NUM_MOTORS  # , MOTOR_A, MOTOR_B
# from pimoroni import REVERSED_DIR

"""
Demonstrates how to read the speeds of Inventor 2040 W's two encoders.

Press "User" to exit the program.
"""

# Wheel friendly names
NAMES = ["LEFT", "RIGHT"]

# Constants
GEAR_RATIO = 110                         # The gear ratio of the motor
SPEED = 1.0                             # The speed to drive the motors at
SLEEP = 0.1                             # The time to sleep between each capture

# Create a new Inventor2040W
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)

# Uncomment the below lines (and the top imports) to
# reverse the counting direction of an encoder
# encoders[MOTOR_A].direction(REVERSED_DIR)
# encoders[MOTOR_B].direction(REVERSED_DIR)

# Set both motors driving
for motor in board.motors:
    motor.speed(SPEED)

# Variables for storing encoder captures
captures = [None] * NUM_MOTORS

# Read the encoders until the user button is pressed
while not board.switch_pressed():

    # Capture the state of all the encoders since the last capture, SLEEP seconds ago
    for i in range(NUM_MOTORS):
        captures[i] = board.encoders[i].capture()

    # Print out the speeds from each encoder
    for i in range(NUM_MOTORS):
        print(NAMES[i], "=", captures[i].revolutions_per_second, end=", ")
    print()

    time.sleep(SLEEP)

gets me

LEFT = -2.191163, RIGHT = -2.184329, 
LEFT = -2.189903, RIGHT = -2.18535, 
LEFT = -2.190561, RIGHT = -2.187061, 
LEFT = -2.190505, RIGHT = -2.188617, 

I'll try the examples again with the suggested modifications.

ZodiusInfuser commented 3 months ago

That matches the readings I would expect from 110:1 motors, though the below lines should be uncommented to make the values positive.

# encoders[MOTOR_A].direction(REVERSED_DIR)
# encoders[MOTOR_B].direction(REVERSED_DIR)
alphanumeric007 commented 3 months ago
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)

enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]
A = board.motors[MOTOR_A]
B = board.motors[MOTOR_B]
A.speed_scale(SPEED_SCALE)
B.speed_scale(SPEED_SCALE)
A.direction(REVERSED_DIR)
enc_A.direction(NORMAL_DIR)
B.direction(NORMAL_DIR)
enc_B.direction(REVERSED_DIR)

Appears to get the driving_sequence.py to work. The wheels are going the way I want and the readings match the direction and speed. Near as i can tell anyway. Thankyou for replying to this issue.

ZodiusInfuser commented 3 months ago

Excellent!

Btw, a trick to get sensible readings from the code in your forum post:

# Drive at full positive
A.speed(1.0)
B.speed(1.0)
time.sleep(1)    # Sleep to give time for motors to settle

# Perform a dummy capture to clear any previous readings
enc_A.capture()
enc_B.capture()

time.sleep(0.01)    # Sleep to give time to detect the motor speed

capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", capture_A.revolutions_per_second)
print("B Speed =", capture_B.revolutions_per_second)
ZodiusInfuser commented 3 months ago

Related to the above, I have just remembered this example exists: https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/inventor2040w/motors/tuning/motor_profiler.py

alphanumeric007 commented 3 months ago

the readings for driving_sequence.py top out at ~2, is that right?

 LEFT = 2.196403, RIGHT = -2.234383, 
LEFT = 2.192, RIGHT = -2.230969, 
LEFT = 2.201186, RIGHT = -2.232923, 
alphanumeric007 commented 3 months ago

And

import time
from pimoroni import PID, REVERSED_DIR, NORMAL_DIR
from inventor import Inventor2040W, MOTOR_A, MOTOR_B, NUM_MOTORS, NUM_LEDS

# Constants
GEAR_RATIO = 110                         # The gear ratio of the motors
SPEED_SCALE = 1.0                       # The scaling to apply to each motor's speed to match its real-world speed

board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)

enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]
A = board.motors[MOTOR_A]
B = board.motors[MOTOR_B]
A.speed_scale(SPEED_SCALE)
B.speed_scale(SPEED_SCALE)
A.direction(REVERSED_DIR)
enc_A.direction(NORMAL_DIR)
B.direction(NORMAL_DIR)
enc_B.direction(REVERSED_DIR)

# Drive at full positive
A.speed(1.0)
B.speed(1.0)
time.sleep(1)    # Sleep to give time for motors to settle

# Perform a dummy capture to clear any previous readings
enc_A.capture()
enc_B.capture()

time.sleep(0.01)    # Sleep to give time to detect the motor speed

capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", capture_A.revolutions_per_second)
print("B Speed =", capture_B.revolutions_per_second)

Gets me

>>> %Run -c $EDITOR_CONTENT
MPY: soft reboot
A Speed = 2.20926
B Speed = 2.1596
>>> 
ZodiusInfuser commented 3 months ago

Yes, 2.2 looks to be correct for a 110:1 motor. I get 5.0 for a 50:1 motor

alphanumeric007 commented 3 months ago

What Speed Scale are you using? I set mine to 1.0. My wheels are turning faster than 2.2 RPM. And the encoder is turning a lot faster than that.

Ops , revolutions per second. Missed that some how. Time to do some math, lol.

ZodiusInfuser commented 3 months ago

For 50:1 motors I'm using 5.4. This matches the top speed I got out of the motors when running motor_profiler.py. In your case you can set your speed scale to 2.2. Or if you want your speed to be in RPM, then divide this by 60 to give 0.0366rpm.

alphanumeric007 commented 3 months ago

Many thanks. I feel like I'm making progress now, and not just banging my head against the wall. Need to take a break for a bit now though. I have chronic pian issues, back mostly, makes sitting for any prolonged time a literal Pain In The Ass.

ZodiusInfuser commented 3 months ago

You're welcome, and apologies for the frustrations. If anything else comes up to do with Inventor and its encoders, feel free to tag me in that forum thread.

alphanumeric007 commented 3 months ago

The profiler keyed in on 2.2 as you predicted. Entered that into the driving_sequence.py and it looks good. Thanks again for the help.