How to read encoders on Inventor 2040W

I’m struggling to read the speed value of the motor encoders on the Inventor 2040W. I just don’t understand what syntax / command to run?
I’d like to read the speed and or RPM. If I do a
print (cature_A)
I get all the info, I just don’t get how to pick out individual info.
I’m going by this.
pimoroni-pico/micropython/modules/motor at main · pimoroni/pimoroni-pico · GitHub

Here is my test code for running the motors.

import time
import math
from inventor import Inventor2040W, MOTOR_A, MOTOR_B, NUM_LEDS 
from pimoroni import PID, REVERSED_DIR
"""
Demonstrates how to control a motor on Inventor 2040 W.
"""
GEAR_RATIO = 110

DIRECTION = REVERSED_DIR
# Create a new Inventor2040W
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]

#board = Inventor2040W()
A = board.motors[MOTOR_A]
B = board.motors[MOTOR_B]

A.direction(DIRECTION)
enc_A.direction(DIRECTION)

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())
time.sleep(5)

# 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())
time.sleep(5)

# 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


# Drive at full negative
A.speed(-1.0)
B.speed(-1.0)
A.full_negative()
B.full_negative()
time.sleep(1)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("B Speed =", B.speed())
time.sleep(5)



# Coast to a gradual stop
A.coast()
B.coast()
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("B Speed =", B.speed())
time.sleep(5)# Access the motor from Inventor and enable it

time.sleep(5)

# Disable the motor
A.disable()
B.disable()

Please post the output of the print-statement, maybe it gives some insight. Thanks!

MPY: soft reboot
(count=-2828, delta=-2828, frequency=-1412.459, revolutions=-2.142424, degrees=-771.2728, radians=-13.46125, revolutions_delta=-2.142424, degrees_delta=-771.2728, radians_delta=-13.46125, revolutions_per_second=-1.070045, revolutions_per_minute=-64.20267, degrees_per_second=-385.2161, radians_per_second=-6.723289)
A Speed = 1.0

Tried this but the numbers don’t make any sense? Most likely because I’m not sampling them in the correct way. Full speed gets me ~30, half ~60, and stoped reads ~130. Something like that, I didn’t write them down. I’ll run it again and take notes. I’ll have to have a good look at the examples and try and pick out the code I need. Problem is they are over complicated doing fancy start stop stuff that makes figuring out what going on harder. At least for me it does.
I’ll likely have to switch tactics and look at the velocity instead of RPM.

Running this

import time
import math
from inventor import Inventor2040W, MOTOR_A, MOTOR_B, NUM_LEDS 
from pimoroni import PID, REVERSED_DIR
"""
Demonstrates how to control a motor on Inventor 2040 W.
"""
GEAR_RATIO = 110

DIRECTION = REVERSED_DIR
SPEED_SCALE = 6.0
# Create a new Inventor2040W
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]

#board = Inventor2040W()
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)

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

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

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

A.speed(3.0)
B.speed(3.0)
time.sleep(5)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
time.sleep(2)

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

A.speed(-3.0)
B.speed(-3.0)
time.sleep(5)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
time.sleep(2)

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

# Drive at full negative
A.speed(-6.0)
B.speed(-6.0)
time.sleep(5)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
time.sleep(2)

#print (capture_A)

# Coast to a gradual stop
A.coast()
B.coast()
time.sleep(5)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
time.sleep(2)# Access the motor from Inventor and enable it

# Disable the motor
A.disable()
B.disable()

Gets me this

MPY: soft reboot
A Speed = 6.0
A RPM = -109.9839
B Speed = 6.0
B RPM = -110.1759
A Speed = 0.0
A RPM = -129.4337
B Speed = 0.0
B RPM = -128.683
A Speed = 3.0
A RPM = -27.20043
B Speed = 3.0
B RPM = -27.02252
A Speed = 0.0
A RPM = -63.25087
B Speed = 0.0
B RPM = -62.94611
A Speed = -3.0
A RPM = 26.93824
B Speed = -3.0
B RPM = 27.19337
A Speed = 0.0
A RPM = 63.44396
B Speed = 0.0
B RPM = 64.50197
A Speed = -6.0
A RPM = 55.00327
B Speed = -6.0
B RPM = 56.5227
A Speed = 0.0
A RPM = 119.7155
B Speed = 0.0
B RPM = 123.5738
>>> 

Running a slightly modified read_encoders.py gets me what look like OK numbers when I turn the wheels.

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

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

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

# Wheel friendly names
NAMES = ["LEFT", "RIGHT"]
SPEED_SCALE = 6.0
# Constants
GEAR_RATIO = 50                         # The gear ratio of the motor
DIRECTION = REVERSED_DIR
# Create a new Inventor2040W
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)

enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]

#board = Inventor2040W()
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)


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


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

    # Print out the angle of each encoder
    for i in range(NUM_MOTORS):
        print(NAMES[i], "=", board.encoders[i].degrees(), end=", ")
    print()

    time.sleep(0.1)

gets me this,

>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = 0.0, 
LEFT = 0.0, RIGHT = -8.4, 
LEFT = 0.0, RIGHT = -42.0, 
LEFT = 0.0, RIGHT = -63.6, 
LEFT = 0.0, RIGHT = -86.4, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 0.0, RIGHT = -97.8, 
LEFT = 1.8, RIGHT = -97.8, 
LEFT = 28.2, RIGHT = -97.8, 
LEFT = 57.0, RIGHT = -97.8, 
LEFT = 60.6, RIGHT = -97.8, 
LEFT = 60.6, RIGHT = -97.8, 
LEFT = 60.6, RIGHT = -97.8, 
LEFT = 60.6, RIGHT = -97.8, 
LEFT = 57.0, RIGHT = -97.8, 
LEFT = 27.0, RIGHT = -97.8, 
LEFT = -1.2, RIGHT = -97.8, 
LEFT = -4.2, RIGHT = -97.8, 
LEFT = -4.2, RIGHT = -97.8, 
LEFT = -4.2, RIGHT = -97.8, 
LEFT = -4.2, RIGHT = -102.6, 
LEFT = -4.2, RIGHT = -136.8, 
LEFT = -4.2, RIGHT = -159.6, 
LEFT = -4.2, RIGHT = -159.6, 
LEFT = -4.2, RIGHT = -129.0, 
LEFT = -4.2, RIGHT = -96.60001, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 
LEFT = -4.2, RIGHT = -93.6, 

There is some kind of trickery going on here, near as I can tell anyway?
ENCODER_A_PINS = (19, 18)
ENCODER_B_PINS = (17, 16)
Are not ADC’s, which is what you would normally use right?
@hel @gadgetoid

Have you found the encoder module documentation here: pimoroni-pico/micropython/modules/encoder at main · pimoroni/pimoroni-pico · GitHub - looks like it contains a bunch of info about how to read encoder data and it explains a bit about the different fields in that .capture() function too.

This example looks like it’s doing some extra calculations to feed a ‘counts per revolution’ value to the encoder object when you set it up, which might be what needs to happen here?

I saw that but have been having trouble transferring that code?
I was thinking it had to do with MMME_CPR
I have two motors / two encoders in use.

from inventor import Inventor2040W, MOTOR_A, MOTOR_B, NUM_LEDS 
from pimoroni import PID, REVERSED_DIR
from encoder import Encoder
from encoder import MMME_CPR


board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
enc_A = board.encoders[MOTOR_A]
enc_B = board.encoders[MOTOR_B]

enc_A = board.encoders[MOTOR_A],counts_per_rev=COUNTS_PER_REV, count_microsteps=True
gets me
TypeError: 'bool' object isn't iterable

enc_A = Encoder(0, 0, ENCODER_PINS, direction=DIRECTION, counts_per_rev=COUNTS_PER_REV, count_microsteps=True)
gets me a
NameError: name 'ENCODER_PINS' isn't defined

ah - I missed that you were using an Inventor, sorry!

Looks like the Inventor module should set that stuff up for you: pimoroni-pico/micropython/modules_py/inventor.py at main · pimoroni/pimoroni-pico · GitHub

Yes, but there is no example that shows how to read the RPM? Not without a bunch of other stuff going on at the same time.

I’m missing some code somewhere as you can see from what I posted above. My capture.revolutions_per_second isn’t working as intended. I’ll just have to plug alone and try to sort it out. Thanks for the help so far.

This:

import time
import math
from inventor import Inventor2040W, MOTOR_A, MOTOR_B, NUM_LEDS
from pimoroni import PID, REVERSED_DIR 
from encoder import Encoder, MMME_CPR 
from encoder import MMME_CPR

GEAR_RATIO = 110

DIRECTION = REVERSED_DIR
SPEED_SCALE = 6.0

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, in seconds
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
ACC_PRINT_SCALE = 0.05
SPD_PRINT_SCALE = 40 

# Acceleration multiplier

POSITION_EXTENT = 180                   # How far from zero to move the motor, in degrees
MAX_SPEED = 6.0                         # The maximum speed to move the motor at, in revolutions per second
INTERP_MODE = 0  

# PID values

POS_KP = 0.025                          # Position proportional (P) gain
POS_KI = 0.0                            # Position integral (I) gain
POS_KD = 0.0                            # Position derivative (D) gain

VEL_KP = 30.0                           # Velocity proportional (P) gain
VEL_KI = 0.0                            # Velocity integral (I) gain
VEL_KD = 0.4                            # Velocity derivative (D) gain

pos_pid = PID(POS_KP, POS_KI, POS_KD, UPDATE_RATE)
vel_pid = PID(VEL_KP, VEL_KI, VEL_KD, UPDATE_RATE)

board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
A = board.motors[MOTOR_A]
A.speed_scale(SPEED_SCALE)
A.direction(DIRECTION)

enc_A = board.encoders[MOTOR_A]
enc_A.direction(DIRECTION)

B = board.motors[MOTOR_B]
B.speed_scale(SPEED_SCALE)

enc_B = board.encoders[MOTOR_B]

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

# Drive at full positive
A.speed(6.0)
B.speed(6.0)


board.leds.clear()
for L in range(A.speed()):
    board.leds.set_rgb(L, 0, 255, 0)
for R in range(6, B.speed() + 6):
    board.leds.set_rgb(R, 0, 255, 0)


time.sleep(2)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
accel = vel_pid.calculate(capture_A.revolutions_per_second)
print("Vel =", capture_A.revolutions_per_second, end=", ")
print("Vel SP =", vel_pid.setpoint, end=", ")
print("Accel =", accel * ACC_PRINT_SCALE, end=", ")
print("Speed =", A.speed())
time.sleep(2)# Access the motor from Inventor and enable it

board.leds.clear()

# Stop moving
A.speed(0)
B.speed(0)

board.leds.clear()
for L in range(A.speed()):
    board.leds.set_rgb(L, 0, 255, 0)
for R in range(6, B.speed() + 6):
    board.leds.set_rgb(R, 0, 255, 0)
    
time.sleep(2)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
board.leds.clear()
print("Vel =", capture_A.revolutions_per_second, end=", ")
print("Vel SP =", vel_pid.setpoint, end=", ")
print("Accel =", accel * ACC_PRINT_SCALE, end=", ")
print("Speed =", A.speed())
time.sleep(2)# Access the motor from Inventor and enable it

board.leds.clear()

A.speed(3.0)
B.speed(3.0)

board.leds.clear()
for L in range(A.speed()):
    board.leds.set_rgb(L, 0, 255, 0)
for R in range(6, B.speed() + 6):
    board.leds.set_rgb(R, 0, 255, 0)

time.sleep(2)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
time.sleep(2)
board.leds.clear()


# Stop moving
A.stop()
B.stop()


for L in range(A.speed()):
    board.leds.set_rgb(L, 0, 255, 0)
for R in range(6, B.speed() + 6):
    board.leds.set_rgb(R, 0, 255, 0)

time.sleep(5)
capture_A = enc_A.capture()
capture_B = enc_B.capture()
print("A Speed =", A.speed())
print("A RPM =", capture_A .revolutions_per_minute)
print("B Speed =", B.speed())
print("B RPM =", capture_B .revolutions_per_minute)
print("Vel =", capture_A.revolutions_per_second, end=", ")
print("Vel SP =", vel_pid.setpoint, end=", ")
print("Accel =", accel * ACC_PRINT_SCALE, end=", ")
print("Speed =", A.speed())
time.sleep(2)# Access the motor from Inventor and enable it

Gets me this:

MPY: soft reboot
A Speed = 6.0
A RPM = -44.58585
B Speed = 6.0
B RPM = -44.47534
Vel = -0.7430975, Vel SP = 0, Accel = 2.600841, Speed = 6.0
A Speed = 0.0
A RPM = -127.9083
B Speed = 0.0
B RPM = -127.7043
Vel = -2.131804, Vel SP = 0, Accel = 2.600841, Speed = 0.0
A Speed = 3.0
A RPM = -21.02662
B Speed = 3.0
B RPM = -21.05077
A Speed = 0.0
A RPM = -62.31686
B Speed = 0.0
B RPM = -62.56263
Vel = -1.038614, Vel SP = 0, Accel = 2.600841, Speed = 0.0

Getting no where fast? read_encoders.py works OK. The angle shows correctly from 0 to 360.
driving_sequence.py just ends up in an endless drive forward? No error message on screen?
How hard can it be to just read the RPM from the motor encoder?