Reset GoPiGo3 without shutting down RPi using GPIO18?

@mitch.kremm, and any forum hardware guru:

I am looking for a way to reset the GoPiGo3 board when it fails to properly handle I2C traffic. (Currently must perform hard shutdown to clear the issue.)

GrovePi+ user @Dimu has posted a method of resetting the GrovePi+ by cycling GPIO 8

On the GoPiGo3 schematic, I see that GPIO18 labeled RPI_RESET passes through a level shifter “2L” and exits as “2H” labeled AT_RESET which is connected to the ATSAMC20J RESET line with a 5v pull-up.

Will cycling the RaspberryPi GPIO18 (RPI GPIO Header pin 12) briefly low using user @Dimu 's method reset the GoPiGo3 board?

Does the EasyGoPiGo3() or GoPiGo3() class do any board initialization that would need to be repeated after a GoPiGo3-board-only reset?

Here is what I am thinking of running if I cannot detect the DistanceSensor on I2C 0x2A:

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import time

# add Carl's Python library to import path
import sys
sys.path.insert(1,"/home/pi/Carl/plib")

import lifeLog
import runLog

BOARD_PIN_12=12  # Physical Board Pin 12 in GPIO.BOARD mode (GPIO18 in GPIO.BCM mode)
			# is GoPiGo3 RPI_RESET connects through level shifter to AT_RESET
@runLog.logRun
def fixI2Cjam():
	lifeLog.logger.info("Forcing GoPiGo3 Board Reset Via GPIO18 To Clear I2C Failure")
	GPIO.setmode(GPIO.BOARD)  # use physical pin numbers
	GPIO.setup(BOARD_PIN_12,GPIO.OUT, pull_up_down=GPIO.PUD_UP))
	time.sleep(0.5)  # allow time for setup to complete
	GPIO.output(BOARD_PIN_12,GPIO.LOW)  # Yank the AT_RESET chain
	time.sleep(1)  # Keep it low to ensure reset
	GPIO.output(BOARD_PIN_12,GPIO.HIGH)  # Let GoPiGo3 run again
	time.sleep(0.5)
	runLog.entry("GoPiGo3 Board Reset to Clear I2C Failure Complete"


if __name__ == '__main__':  fixI2Cjam()

1 Like

That’s an interesting approach. There are a few things that the gopigo libraries set up upon initialisation but you should be able to get around that.
I know of these lines:
https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/gopigo3.py#L222-L224

but otherwise you may be just fine.

1 Like

Thanks for checking/thinking on this.

I saw those pin 9, 10, and 11 settings in the RPi initialized, but didn’t see any writes to the GoPiGo3 board in either __init__(). Those pins in the RPi get set up, and some instantiation vars get set from reading the board, but I couldn’t find any writes to the board.

I didn’t check the Distance Sensor init and the IMU init though. I’ll have to take a look there.

I’ve created a healthCheck.py that loops once a second reading the I2C bus address 0x2A to see if the Distance Sensor replies.

I’ve re-enabled my IMU, so perhaps a failure will happen and get caught by the healthCheck. I didn’t setup the resetGoPiGo3.fixI2Cjam() to run automatically, as I want to be nearby to try that the first time.

#!/usr/bin/env python3


# FILE:  healthCheck.py

# PURPOSE:  Check GoPiGo3 health once per second
#		by polling I2C for Distance Sensor on 0x2A
#		if not seen, force a reset of the GoPiGo3 board

import smbus
import time

# add Carl's Python library to path
import sys
sys.path.insert(1,"/home/pi/Carl/plib")
import runLog
import lifeLog
import speak

bus = smbus.SMBus(1)  # 1 indicates /dev/i2c-1
DISTANCE_SENSOR_0x2A = 0x2A

@runLog.logRun
def checkI2C():
	try:
		bus.read_byte(DISTANCE_SENSOR_0x2A)
		I2C_OK = True
	except:
		I2C_OK = False
		alert="I2C Bus Failure Detected"
		speak.say(alert)
		lifeLog.logger.info(alert)
	return I2C_OK

def main():
	I2C_WAS_OK = True  # presume good at start
	while True:
		try:
			if checkI2C() == False:
				if I2C_WAS_OK:
					print("I2C Failure Detected")
					I2C_WAS_OK = False
				else:
					pass
			elif I2C_WAS_OK:
				pass	# still good
			else:
				# I2C was down, now good
				alert="I2C Function Restored"
				print(alert)
				speak.say(alert)
				I2C_WAS_OK = True
			time.sleep(1)
		except KeyboardInterrupt:
			print("\nExiting healthCheck.py")
			break

if __name__ == '__main__': main()

1 Like

Actually, this is where the board setup happens - servos, and IMU on AD1 as a Software I2C port.

After I reset the GoPiGo3 board, I will need to instantiate the two servos, the IMU, and possibly the Distance Sensor. When those complete, I can blow them away by exiting the resetGoPiGo context.

I think my IMU driver will just count any exceptions it sees, and I have try/excepts around the distance sensor already to keep Carl alive as much as possible with reduced function.

The absolute most critical moment for an I2C failure is when Carl needs to get back on his dock. Currently, if he cannot measure the distance to the dock, he yells for help “MANUAL DOCKING REQUESTED” and if he doesn’t detect being put on the dock, he waits as long as the batteries allow then performs a safety shutdown.

If this works, I can have him recheck the I2C status and reattempt the docking if the distance sensor starts working again.

This is exciting. I really hope this works.

The distance sensor, on the hardware I2C probably won’t be affected.

1 Like

Learned something more about this today - I2C failures may be transient.

At 13:05 this afternoon my healthCheck.py logged “I2C Bus Failure Detected”, and then promptly crashed at a coding error (forgot to import datetime).

At 17:00 I noticed the crashed healthCheck, and manually checked to see if I2C was down with “i2cdetect -y 1”.

I2C was alive and well, so I added a 60 second “wait for it to go away like magic” delay to the healthCheck before attempting the resetGoPiGo3 board-only function.

Now waiting for the next failure.

(Since the healthCheck is going straight to the bus, I wonder if I need to add the di_I2C mutex around the check? Maybe my checker actually caused the glitch.)

Update: I added the di_sensors.easy_mutex around the check in my healthCheck and added the 60 second wait. Already saw a transient!

healthCheck.py: Monitoring I2C and SWAP
2021-02-01 18:42:49 I2C Bus Failure Detected
2021-02-01 18:42:52 Initiating 60 second wait for I2C recovery
2021-02-01 18:43:04 I2C Function Restored