Story "GoPiGo3 C++ API and the Pickle" Continued

Three years ago (June 2021) the GoPiGo3 team was in a pickle. New GoPiGo3 motors arrived with 16 encoder ticks per revolution where the previous motors only had 6 encoder ticks per revolution.

The solution - a “Pickle File” containing the serial numbers of all the GoPiGo3 kits that included the new motors - AND modify every GoPiGo3 function that used ENCODER_TICKS including the graphical tool to tune Wheel Diameter, along with modifying the install code. It was a big pickle but the “team” (@cleoqc) managed to implement the solution with impressive quality on the first release.

Eighteen months later (Dec 2022) I noticed the GoPiGo3 C++ API was not paying attention to that “Pickle File” and so put my hand in the pickle barrel for the GoPiGo C++ API. I created a C++ “drive GoPiGo3 from key presses” to test my changes.

Now March 2024, I decided to mate a Raspberry Pi5 to my GoPiGo3 robot Dave. The RPi 5 made a big change from the former versions in the handling of the GPIO pins, which totally breaks the Python API due to a dependance on the unmaintained pigpiod interface daemon and the deprecated for Pi5 RPI.GPIO package. Rather than try to solve the Python API for the RPi 5, I guessed that the C++ API might just work on the Pi5.

Indeed, once I ported the Python gopigo3_power service to the new GPIO interface for the Pi5, the GoPiGo3 C++ interface appeared to be fully working for sensors, servos, and motors.

I started to enhance the “drive with keys” program and suddenly found that my test program was driving Dave very slowly. My original test program had masked a couple more places in the GoPiGo3 C++ API affected by the pickle that need attention.

I am very rusty in C++ and will have to review the Python API to find where ENCODER_TICKS need to be applied, then relearn enough C++ to make the changes. Eventually, I will submit a new pull request that gets the GoPiGo3 C++ API out of this pickle.

Example: set_motor_dps()

  • In Python:
def set_motor_dps(self, port, dps):
        """
        Set the motor target speed in degrees per second

        Keyword arguments:
        port -- The motor port(s). MOTOR_LEFT and/or MOTOR_RIGHT.
        dps -- The target speed in degrees per second
        """
        dps = int(dps * self.MOTOR_TICKS_PER_DEGREE)             <<---- OH MY, DPS is no longer DPS
        outArray = [self.SPI_Address, self.SPI_MESSAGE_TYPE.SET_MOTOR_DPS, int(port),\
                    ((dps >> 8) & 0xFF), (dps & 0xFF)]
        self.spi_transfer_array(outArray)

NOTE that the “dps” sent in the SET_MOTOR_DPS message to the red board is actually DPS times MOTOR_TICKS_PER_DEGREE - Lesson: don’t read the words, read the code.

  • The C++ API (sends DPS without conversion for Ticks):
int GoPiGo3::set_motor_dps(uint8_t port, int16_t dps){
  spi_array_out[0] = Address;
  spi_array_out[1] = GPGSPI_MESSAGE_SET_MOTOR_DPS;
  spi_array_out[2] = port;
  spi_array_out[3] = ((dps >> 8) & 0xFF);             <<-- here the real DPS is sent in the message
  spi_array_out[4] = (dps & 0xFF);
  return spi_transfer_array(5, spi_array_out, spi_array_in);
}

That’s on the commanding side “set_motor_dps”; all the get_ functions that deal with encoders have to be made aware of the pickle.

p.s. My original test program used NO_LIMIT_SPEED which is 1000 DPS. The GoPiGo3 won’t do 1000 DPS but passing 1000 on a “6-Tick Motor” still gets 500 DPS and on a “16-Tick Motor” gets 186 DPS which is a good test speed. When I tried commanding Dave for 150 DPS in C++, he only goes forward at 28 DPS which is slower than an Alaskan glacier.

2 Likes

Wow, it’s awesome that the C++ version still works as is with a Pi5.
I’m looking forward to your PR on this - although I think most of the users who got the 16 ticks motors would use C++ :wink:

2 Likes

Very interesting that no changes to the C++ API were needed to run on the Pi5 - even servos and Grove Ultrasonic Range sensor…

It came together pretty quickly. Speed and Encoder tests confirm the new GoPiGo3 C++ API honors the ENCODER_TICKS_PER_ROTATION (and the resultant MOTOR_TICKS_PER_DEGREE).

I also created two new examples:

  • servos.cpp centers any properly attached servos (connectors are not polarized)
  • vbatt.cpp retrieves the battery and 5v supply voltages (separated out from info.cpp)
2 Likes

If anyone wants to use the GoPiGo3 C++ API on their GoPiGo3 (Raspberry Pi3 or 4)

I should have put a README file in the PR - sorry.

The GoPiGo3 C++ API

/home/pi/Dexter/GoPiGo3/Software/C/ contains the current official GoPiGo3 C++ API in files GoPiGo3.h and GoPiGo3.cpp (which end up as the .h and libgopigo3.so for linking)

Setup:

  • sudo apt install cmake
  • make a local copy of the GoPiGo3 C++ API
mkdir gpg_cpp
cd gpg_cpp
cp -r /home/pi/Dexter/GoPiGo3/Software/C/*  .
cd gpg_cpp
cmake CMakeLists.txt
make

Other Useful Commands:

  • make clean will remove all executables
  • make will rebuild any changed project

To run Examples (after make):

  • info - reads GoPiGo3 red board information and reports battery voltage
  • vbatt - reports battery voltage reading and VCC (5v) voltage
    Actual battery voltage is 0.8v higher than reported
    due to reverse polarity protection diode drop.
  • leds - cycles center “WiFi LED” through colors
  • servos - centers properly attached servos
    (Servo1: brown wire closest to front of robot)
    (Servo2: brown wire closest to back of robot)
  • motors - Gently rotate robot’s left wheel in a forward direction
    Robot’s right wheel will rotate to match left wheel encoder value
  • sensors - Reports Grove Ultrasonic Ranger (connected to AD1) range in millimeters
    Reports Infrared Remote Control (connected to AD2) code
  • i2c - Toggles P0 output of PCA9570 I2C output expander connected to AD1 port ???
  • grove_led - Varies Grove LED brightness (connected to AD1)
  • drive - Allows driving GoPiGo3 with key presses
    spacebar: stop
    w: forward at 150 DPS
    s: spin
    x: backward at 150 DPS
    a: pivot ccw around left wheel
    d: pivot cw around right wheel
    q: exit (only q, cntrl-c does not exit)
2 Likes

:astonished: <= Gobsmacked at what you’ve accomplished.

C++ reminds me of the hieroglyphics I saw when Svetlana and I visited Egypt a few years ago.  :man_facepalming:

What’s even worse is that I’m probably going to have to go that route to port this stuff to the Nano!  (double facepalm!!)

2 Likes

Relearning the C++ language is rewarding, but the pleasure is tempered by all these things I have to do at the platform level to get back to working on my robot being aware of its environment and able to navigate safely.

The self-docking thing is also back on the TODO list. At present I have to place Dave on his dock manually, so automation will probably look like Dave asking to be placed on his dock when his battery is low or it is time for Dave to nap through the night.

While I have implemented enough GoPiGo C++ to begin re-writing my ROS 2 gopigo3_node in C++, I decided to continue my C++ refresher by implementing the motion and encoder methods from the Python EasyGoPiGo.py in C++. The ones I am most interested in are the drive_cm(+/- float) and ```turn_degrees(+/- float) - I didn’t implement these in the Python GoPiGo3 ROS node, but the Create3 had them as built-in services and I was amazed not to find these two seemingly basic functions in ROS. ROS assumes the programmer will do everything and it seems all the programmers are interested in is to have the navigation package drive for them.

1 Like