ROS: Revisiting GoPiGo3 Encoder Precision

TL:DR;) Old or New Calibrated GoPiGo3 will both demonstrate (at best) /odom accuracy of +/- 0.29mm (0.00029 meters) for straight line travel.

YMMV due to surface, speed, starting, stopping and prior direction of wheel rotation.

I realized I had a discussion with @Matt (DI engineer till 2019) about this topic a few years ago and came to understand that encoder stuff (for a classic GoPiGo3) happens at half degree precision but the encoder reading APIs operate in whole degree precision which results in +/- half degree accuracy.

GoPiGo3.get_motor_status() returns

encoder_reading = int(encoder / self.MOTOR_TICKS_PER_DEGREE).

So for 360 degree revolution of a wheel:

  • Classic encoder of 2.0 ticks per degree:
    encoder_reading = int(720 / 2) or int(721 / 2) = 360
  • New encoder of 5.3333 ticks per degree:
    encoder_reading = int( 1920/5.33333) to int( 1925/5.3333) = 360

So if I am interpreting this correctly, encoder operations in the new GoPiGo3 operate at roughly 1/5th degree precision, but the encoder reading API is still whole degree precision for +/- half degree accuracy.

Since ROS does not use methods that perform internal encoder calculations (like offset_motor_encoder() which drives till encoder value exceeds the requested encoder reading), the ROS odometry uses the +/- half degree accuracy and outputs distances with roughly +/- 0.29 mm accuracy for a calibrated GoPiGo3 (gpg3_config.json has wheel dia and wheel base to make encoder readings closely match actual distance traveled and actual angle turned).

1 Like

I could live with that. You’re clearly talking about degrees of wheel rotation here (since the motor is geared down).

I need to do a re-calibration since I’m using an image I downloaded and haven’t done anything to calibrate it to my robot in particular.
/K

2 Likes

Well it is greatly misleading number. Back when I was doing my “non-ROS” occupancy grid project, I came up with something being 2.8% of distance traveled using the encoders.

2 Likes

I spent part of today on calibration. Rather than use the control panel I wrote a couple of scripts using easygopigo3.py to create the gpg3_config.json file (which didn’t exist as part of the image I had downloaded) and then figure out the proper values for WHEEL_DIAMETER and WHEEL_BASE_WIDTH.

However in further testing, it doesn’t look like gopigo3 actually reads from the json file. Although it’s a bit ugly, it seems like the most straight forward workaround is to just hard code the values in the gopigo3_driver.py file in the mygopigo package. It’s not like they’re going to change a lot.

@cyclicalobsessive - how have you been handling this?

/K

1 Like
  1. First - I reworked Carl’s wheel test programs for Dave:

and determined my Wheel dia: 66.77mm and Wheel Base: 106.14mm

  1. Then I updated /Dexter and the gopigo3 egg to the latest (version 1.3) which writes the json file if it does not exist, and reads it if it does.
    NOTE: I first updated from the install-on-Ubuntu branch, but after hours of wondering why it wasn’t creating or reading the gpg_config.json file, I updated from the master branch and got version 1.3…

  2. Then I manually edited the numbers from the wheelDia and wheelBase tests into the file.

This step also could have been done with

FILE: write_gpg_config_json.py

from easygopigo3 import EasyGoPiGo3

egpg = EasyGoPiGo3()

egpg.WHEEL_BASE_WIDTH = xxx
egpg.WHEEL_DIAMETER = xxx
egpg.save_robot_constants()

print("Wrote ~/Dexter/gpg_config.json")

  1. Then I commented out the hard-coded values (from the GoPiGo3 class definition) in gopigo3_node:
    # WIDTH = gopigo3.GoPiGo3.WHEEL_BASE_WIDTH * 1e-3
    # CIRCUMFERENCE = gopigo3.GoPiGo3.WHEEL_CIRCUMFERENCE * 1e-3

and added reading it from the initialized GoPiGo3 instance to the node init:

def __init__(self):

...
       # GoPiGo3 and ROS setup
        self.g = gopigo3.GoPiGo3()

        # Short Constants in meters
        self.WIDTH = self.g.WHEEL_BASE_WIDTH * 1e-3
        self.CIRCUMFERENCE = self.g.WHEEL_CIRCUMFERENCE * 1e-3

        print("==================================")
        print("GoPiGo3 info:")
...
2 Likes

The other approach is to use ROS parameters with default values. Once I get everything working with my ROS2 gopigo3_node, I am contemplating setting up somethings as parameters.

But I’m conflicted by the mix of gopigo3 configuration with ROS configuration. ROS doesn’t realize that the latest gopigo3 code is setting things that my “16 tick” bot must have configured correctly or all the ROS speeds and distances will be wrong.

For the older 6 tick bots, after the GoPiGo3 object is instantiated:

These GoPiGo object values have to be set if the load_robot_constants() is not called (older gopgio3.py version 1.2):

       self.g.WHEEL_DIAMETER = wheel_diameter
        self.g.WHEEL_CIRCUMFERENCE = self.WHEEL_DIAMETER * math.pi
        self.g.WHEEL_BASE_WIDTH = wheel_base_width
        self.g.WHEEL_BASE_CIRCUMFERENCE = self.WHEEL_BASE_WIDTH * math.pi
2 Likes

Hadn’t thought to do that.

That would be the “Ros-onic” (as opposed to pythonic) way of doing it. But I was really trying to minimize changing existing code.
/K

2 Likes

Always a good idea.

This is why I suggested overloading the base class if at all possible.

What I do is make a copy of the relevant parts, give them new names, and only then do I modify them.

Since I don’t know what else depends on that particular library function, renaming it seems to me to be the best way to avoid bizarre failures.

1 Like