[SOLVED] How fix GoPiGo3 I2C after error

I was running my version of the ObstacleAvoidanceRobot and then in another term window, I fired up my status.py program which instantiates a gopigo3.GoPiGo3 object and a di_sensors.easy_distance_sensor EasyDistanceSensor object.

Shortly both programs started complaining about I2C errors. I stopped both programs but the I2C appeared to be hosed up, complaining of no Distance_Sensor.

I2C_or_SPI%20stuck

My status.py does a gpg.reset_all() before exiting - could that hose up the works?

Calling reset_all() will essentially put the GPG3 back into it’s bootup state, with all sensor ports un-configured (it will un-configure the port’s I2C setup).

1 Like

Also note that I2C comms is not threadsafe, or multiprocess safe.

If you want to run two programs that controls the GoPiGo3, you will need to use the easygopigo3.py library and use use_mutex=True when instantiating (in both programs!)

mygopigo = easygopigo3.Easygopigo3(use_mutex=True)

All methods from easygopigo3 will be thread safe, but none of the ones in gopigo3.py

1 Like

I’m a little confused by the docs for EasyGoPiGo3 class, and the init_distance_sensor(). There is an EasyGoPiGo3.init_distance_sensor() method that says it is passed the mutex parm from the EasyGoPiGo3() instantiation, and it returns an EasyDistanceSensor object.

So the sequence is:

file: status.py


# only need to import the one Easy class
import easygopigo3


def status():
    global egpg, ds
    print("distance: %.1f inches" % ds.read_inches())                   # This is now thread-safe
    print("Supply Voltage: %.2 volts" % egpg.get_voltage_5v())  # NOT  thread-safe !!!
    print("Battery Voltage: %.2 volts" % egpg.volt()                      # This is now thread-safe  

main():
    global egpg, ds

    egpg=EasyGoPiGo3(use_mutex=True)
    ds=egpg.init_distance_sensor()


    egpg.drive_inches(12)
    while epgp.get_speed() > 0:
        if ds.read_inches() < MIN_DISTANCE:
            egpg.stop()
        time.sleep(0.2)

Multiple separate python programs reading the distance sensor and voltages play better, but eventually still get the dreaded “GoPiGo not detected or Distance Sensor not present” error .

Q) So verifying: .volt() is thread-save but egpg.get_voltage_battery() is not?

Q) Verifying: egpg.get_voltage_5v() is not thread-safe?

Q) The init_servo() does not have a use_mutex parm. Are the servo methods thread-safe?

Q) If I want to use the underlying GoPiGo3 methods, do I have to create a MyThreadSafeEasyGoPiGo3(EasyGoPiGo3) class and overload the desired GoPiGo3 class methods? Will that work?

Q) I never have to call any kind of egpg.exit() and ds.exit() before ending a program?

Q) When I see the I2C issue, do I have to shutdown and restart, or is there a sequence of calls or console commands that I can issue to clean-start the GoPiGo3 board? (Rebooting RPi is not enough.)

Hi there @cyclicalobsessive,

Q) So verifying: .volt() is thread-save but egpg.get_voltage_battery() is not?

EasyGoPiGo3.volt method is thread-safe when the constructor of the EasyGoPiGo3 class is passed the use_mutex = True parameter. Every other method that’s not explicitly defined in the EasyGoPiGo3 class is not thread-safe - so that means all the inherited methods from the GoPiGo3 class cannot be set to be thread-safe.

Therefore, since get_voltage_battery is an inherited method from the GoPiGo3 class, it cannot be set to be thread-safe - not without external intervention such as creating another class to account for all inherited methods.

Q) Verifying: egpg.get_voltage_5v() is not thread-safe?

It’s the same story as the one for get_voltage_battery method.

Q) The init_servo() does not have a use_mutex parm. Are the servo methods thread-safe?

When instantiating an EasyGoPiGo3 class and you pass the use_mutex parameter, you are already setting the thread-safe-profile for all callable methods belonging to the EasyGoPiGo3 - excluding the inherited methods. That includes the EasyGoPiGo3.init_servo method.

Q) If I want to use the underlying GoPiGo3 methods, do I have to create a MyThreadSafeEasyGoPiGo3(EasyGoPiGo3) class and overload the desired GoPiGo3 class methods? Will that work?

Pretty much yeah. I would suggest you read a sort of a 101 crash course on object programming in Python that can introduce you to this in just a few hours.

Q) I never have to call any kind of egpg.exit() and ds.exit() before ending a program?

Generally, I make sure the robot stops by calling the EasyGoPiGo3.stop method and then I exit the program. More often than not, you are only looking after what the robot actually does, in physical terms.

Q) When I see the I2C issue, do I have to shutdown and restart, or is there a sequence of calls or console commands that I can issue to clean-start the GoPiGo3 board? (Rebooting RPi is not enough.)

Use a try/except/finally construct to avoid problems. Say you place this on your distance sensor. When it fails, you just re-instantiate the sensor and move on with the execution of your script. Though the distance sensor or any other sensor shouldn’t fail that easily. You can always experiment with the "AD1" and "AD2" ports of the GoPiGo3. They are generally more stable than its counterparts - that’s because the GoPiGo3 implements its own I2C protocol which is more stable than the Raspberry Pi’s one.

If there are any more questions, please feel free to leave a message here or just start another thread.

Thank you!

And to add to what @RobertLucian said,

  • every method in gopigo3.py is not thread safe if called directly
  • most methods in gopigo3.py have a thread-safe wrapper found in easygopigo3.py
  • should you want to call a method from gopigo3.py in a threadsafe manner, and you have already instantiated an easygopigo3 object, you can wrap it with the mutex methods:
_ifMutexAcquire(self.use_mutex)
val = self.gpg.get_grove_value(self.get_port_ID())
_ifMutexRelease(self.use_mutex)

You will find the definitions of the _ifMutex* methods in easysensors.py

1 Like

OUCH! I asked about the wrong comm channel!

My question should have been:

“How do I fix comm to the GoPiGo3 board after SPI gets messed up?”

I’m guessing that two separate processes calling the EasyGoPiGo3 method volt(), (which calls the GoPiGo3 method get_voltage_battery(), which calls read_spi_16() to send the GET_VOLTAGE_VCC command to the board), is mucking up the SPI channel in a partial transfer or something.

Is there a software way to fix the SPI channel between the RPi and the GoPiGo3 board, without shutting down, power down, power up?

When are you seeing an SPI error?

Although they don’t have mutex wrappers to make them “thread safe”, most of the GPG3 methods are indeed thread safe by design. If a method only uses one SPI transaction, it shouldn’t need mutex protection (at least not as far as the communication is concerned). If you try to conduct multiple SPI transactions at the same time, they will each take their turn, so they are thread safe. Things like setting motor power, reading battery voltage, etc. are all thread safe without added mutexes.

Although most (if not all) of the GPG3 methods are thread safe, there might be applications where you would need to use mutexes to protect certain resources (SPI communication not being one of them).

The communication that does require mutexes to be thread-safe, would be I2C transactions where the drivers write the register and then read the result. Since it’s two transactions, you need to ensure another thread won’t squeeze a conflicting message between the write and the read. Below are examples of conflicting and non-conflicting messages squeezed between the write and the read.

An I2C read looks something like this:

  • write the register to read from
  • read data

To read two bytes from register 0x12 from slave 0x06, it would look like this:

  • write 0x12 to 0x06
  • read two bytes from 0x06

Now, if you have two threads, where one is trying to read 2 bytes from reg 0x12 from slave 0x06, and the other is trying to read 1 byte from reg 0x10 from slave 0x06, it must be done like this:

  • (thread 1) write 0x12 to 0x06
  • (thread 1) read two bytes from 0x06
  • (thread 2) write 0x10 to 0x06
  • (thread 2) read one byte from 0x06

In this example, you need mutexes to prevent something like this:

  • (thread 1) write 0x12 to 0x06
  • (thread 2) write 0x10 to 0x06
  • (thread 1) read two bytes from 0x06. The first byte will be the value at reg 0x10, and the second byte will be unknown.
  • (thread 2) read one byte from 0x06. The byte will be unknown, since the previous transaction read the value sitting in the buffer.

If you don’t have multiple threads interacting with the same I2C slave, then you probably don’t need to protect the transactions. If you have two threads, where one is trying to read 2 bytes from reg 0x12 from slave 0x06, and the other is trying to read 1 byte from reg 0x10 from slave 0x08, it’s okay if this happens:

  • (thread 1) write 0x12 to 0x06
  • (thread 2) write 0x10 to 0x08
  • (thread 1) read two bytes from 0x06. Slave 0x06 didn’t act on the command written to slave 0x08, so the data is correct.
  • (thread 2) read one byte from 0x08. Slave 0x08 didn’t empty it’s buffer during the read from slave 0x06, so the data is correct.

Here is another situation you might need to protect. If one thread configures a Distance Sensor and starts taking measurements, and then another thread comes along and initializes the sensor, you will need mutexes to prevent the first thread from taking readings while the second thread is re-configuring the sensor.

I’m “running” my safe_5v_test.py in two shells at a fast rep rate to investigate while I go “running” for a bit myself.

It does both the get_voltage_5v() call (SPI) and an EasyDistanceSensor.read_inches() (SPI + I2C) so hopefully (or hopefully not) I get a repeat.

********* CARL Basic STATUS *****
2018-09-04 12:22:24 up 2:04, 3 users, load average: 0.37, 0.31, 0.17
Battery Voltage: 9.16
5v Supply: 5.06
Estimated Life Remaining: 3 h 49 m
Processor Temp: 49.4’C
Clock Frequency: 600.0 MHz
throttled=0x0
Distance Sensor: 58.7 inches

********* CARL Basic STATUS *****
2018-09-04 12:22:25 up 2:04, 3 users, load average: 0.37, 0.31, 0.17
Battery Voltage: 9.17
5v Supply: 5.05
Estimated Life Remaining: 3 h 59 m
Processor Temp: 49.4’C
Clock Frequency: 600.0 MHz
throttled=0x0
Distance Sensor: 53.5 inches

To summarize:

  1. the original issue was due to running two EasyGoPiGo3 instances that did not set use_mutex, and calling reset_all() at the exit of one of the programs.

  2. Calling gopigo3 base class methods get_voltage_battery() and get_voltage_5v() can be considered thread-safe.

When I added use_mutex=True to the EasyGoPiGo3 object creation, and removed the reset_all(), I have not been able to cause the issue to reoccur.

Thanks for your patience on this thread. Please mark it SOLVED.

Alan

1 Like

This topic was automatically closed after 25 hours. New replies are no longer allowed.