Metro M4 write timeout using PySerial

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
lstnwndrlnd
 
Posts: 7
Joined: Tue Dec 03, 2019 4:07 pm

Metro M4 write timeout using PySerial

Post by lstnwndrlnd »

I apologize for the length of this post, however I have tried troubleshooting this for awhile and have not been able to figure out a long term solution to this problem (so I have lots of ideas). I am not sure if anyone can help me, but if I figure out the solution to this problem I will post it in a comment below the original post.

I have these:
0.8" mini 8x8 LED array with I2C backpack: https://www.adafruit.com/product/870
A stepper motor similar to this one: https://www.adafruit.com/product/324
An H-bridge integrated circuit: https://www.adafruit.com/product/807
An arduino Metro M4: https://www.adafruit.com/product/3382
And a proto-board to wire it all together: https://www.adafruit.com/product/2077

Ok, so my problem is that when I have this all wired together and programmed, and in general it works spectacularly. I know the wiring is not the issue because it works. I can send commands to the arduino using Python (PySerial) and I can change the pattern on the LED array and I can make the motor move in a repeatable way. My issue is that sometimes it randomly stops working. It sometimes works for days at a time without issue. Other times is works for an hour and then the communication breaks down between the metro and the computer running python. It started with once in a while (sometimes after an hour, sometimes after 2 hours, and sometimes after 3 days) the program would stop working and just hang without throwing an error or terminating. At some point I suspected it was a communication issue with the arduino, so I set "write_timeout = 10" (previously that was whatever the default value was, something about non-blocking?) and after setting a write_timeout value instead of hanging it would throw a write timeout error (example attached).

Ok so things I know that might be causing the problem:
1. I am trying to send commands to the arduino about 10x per second for 72 hours. This is why I use a metro M4 because the clock speed is much faster than an Uno and the Uno was not up to this task because it was too slow. Too slow meaning that the LED array would display random patterns because the Uno could not keep up with the rapidly incoming commands.
2. When this write timeout occurs, I have tried changing usb cables and ports on the computer. This does not solve the problem (It still occurs within an hour or 5 hours, I have no way of predicting when this error will occur). I have tried 3 different cables that are 2-3 feet long, and a 4th cable that is a little longer than a meter.
3. The short term solution is the reset the arduino, which requires me to physically press the button (sometimes works) or to unplug the arduino and plug it back in (always works), but then the problem will occur again in an hour or five hours, or ten hours...again no way to predict when it will occur.
4. At some point I suspected it could be a memory leak on the part of the arduino, but I sort of rule that out because the memory should reset every time I unplug it and plug it back in and the time it takes for this communication to break down seems random indicating that it shouldnt be something so deterministic as running out of memory, especially because the way that I send commands to it is very repeatable and does not vary at all (code below).
5. I really think it has something to do with the serial communication between the computer and the arduino. I originally had the baud rate at 57600, but I lowered it to 9600 to see if that helped the problem (it didn't). I tried this because I think it could have something to do with the IO stream on either the computers end or the arduino, but I am not sure how to check/flush the stream. This is why I use a terminating character for all communication with the arduino, to carefully control the input stream the arduino.

In general, the way the code works is to repeatedly call on the arduino to move the motor or change the pattern on the LED array. Every time a command is sent to the arduino, there is an input character first (either I or M) to signal to the arduino whether I am calling on the LED array or the motor. The Arduino then reads the rest of the input until it reaches the "\n" character, which is the terminating character telling the arduino that it has read the entire message. Based on the message it receives it acts appropriately either changing the pattern on the LED array or moving the motor an integer number of steps. I put a bunch of sleep commands in front of sending info to the arduino in order to give the arduino time to respond, but these do not seem to have helped very much.

I attached the code and a sample error message, but I appreciate any insight you can give me into this problem.

Python code for connecting to Metro (Metro class):

Code: Select all

######################################################################################
# LED.py
# Author: Eddie Polanco, Nik Parcell, Tyler Bodily
# Date: October 14, 2019

# This was updated from version StageLED_metro_v12 to have a clearer name since the
# classes inherited by the Metro_v13.py class are used in programming and operating the
# Metro M4 directly.
# and also to split up the LED and z stage motor code as separate classes to be
# inherited by the Metro class.

# This is a stable version of the LED class, and should be used in the future instead
# of using instead of microscope2.py. Something to note here is the empty constructor.
# This was done in favor of a helper function that connects to the LED array because
# of a nuance using multiple inheritance. Normally when multiple inheritance is done
# in python, it will only call the constructor of the first class inherited. Since the
# Camera class constructor is more complicated and difficult to change into a helper
# function, I decided to make it so that the LED class has a function to directly
# connect to the LED array rather than automatically through the constructor.
#
#Example of how to import class, instantiate class object, and connect to LED array:
# import LED
# led = LED.LED()
# led.connectLED()
#
#####################################################################################

import serial
from LED_v14 import LED
from Zstage_v15 import Zstage # for command line version of code

# from Zstage_v16_UI import Zstage # for GUI version of code

class Metro(LED,Zstage):

	def __init__(self, serial_port='COM19', baud_rate = 9600, read_timeout = 10, verbose = False):
		# pass
		""" Open serial communication with the LED. Note how the COM is different, and
		baud_rate is recommended to be at 57600 for the LED. Paramters for this experiment
		were baud_rate = 57600, read_timeout = 1000, verbose = False, parity = None,
		eight serial bits, 1 stopbit
		"""
		print('Connecting to Metro M4 controller ... ')
		self.conn = serial.Serial(port = serial_port, baudrate = baud_rate, timeout = read_timeout, bytesize = serial.EIGHTBITS, stopbits = serial.STOPBITS_ONE, parity = serial.PARITY_NONE, write_timeout = 10)
		self.conn.timeout = read_timeout

		self.step = 0
		self.z = 0
		#time.sleep(2)
		# self.conn.write(('F\n').encode())
		print('Connected to Metro m4')

	def connectMetro(self, serial_port='COM19', baud_rate = 9600, read_timeout = 10, verbose = False):
		print('Connecting to Metro M4 ... ')
		self.conn = serial.Serial(port = serial_port, baudrate = baud_rate, timeout = read_timeout, bytesize = serial.EIGHTBITS, stopbits = serial.STOPBITS_ONE, parity = serial.PARITY_NONE, write_timeout = 10)
		self.conn.timeout = read_timeout
		self.step = 0
		self.z = 0
		# time.sleep(2)
		# self.conn.write(('F\n').encode())

		print('Connected to Metro M4')

	def flushOutput(self):
		self.conn.flushOutput()

	def flushInput(self):
		self.conn.flushInput()

	def closeMetro(self):

		"""Important to close serial communication with the LED after each
		run. Must turn off LED prior to disconnecting otherwise it will remain on.
		"""
		# self.conn.write(('IO\n').encode())
		self.off()
		self.conn.close()
		print('Connection to Metro m4 closed')

---------------------------------------------------------------------------------------------------------------------

LED class inherited by Metro class:

Code: Select all

import serial

class LED():

	def __init__(self):
		pass

	def send(self, phrase):

		""" General phrase to send a serial command to the LED Arduino. General format
		is self.conn.write((' [ string of direction to move ]\n'.encode() )
		Commands for the LED are: Left half: 'L\n'. Right half: 'R\n'. Top Half: 'T\n'.
		Bottom half: 'B\n'. Full: 'F\n'. and Off: 'O\n'.
		"""

		self.conn.write(phrase)

	def left_half(self):

		""" Light the left-half of the LED array
		"""
		self.conn.write(('IL\n').encode())

	def right_half(self):

		""" Light the Right-Half of the LED array
		"""
		self.conn.write(('IR\n').encode())

	def top_half(self):

		""" Light the Top-Half of the LED array
		"""
		self.conn.write(('IT\n').encode())

	def bottom_half(self):

		""" Light the Bottom-Half of the LED array
		"""
		self.conn.write(('IB\n').encode())

	def edges(self):
		""" Light outer edges of the LED array
		"""
		self.conn.write(('IE\n').encode())

	def full(self):

		""" Light the Full circle configuration of the LED array
		"""
		self.conn.write(('IF\n').encode())

	def small(self):

		""" Light the Full circle configuration of the LED array
		"""
		self.conn.write(('IS\n').encode())

	def off(self):

		""" Turn off the LED array
		"""
		self.conn.write(('IO\n').encode())

z-stage class inherited by Metro class:

Code: Select all

import serial
import numpy as np

class Zstage():

	def __init__():
		pass

	def connectZ(self):
		pass

	def moveZ(self, um):
		# Note: This code sends a string with only z axis motion information followed by
		# a terminator return "\n" and is programmed to work on the motor when Arduino
		# code String_Control_v4 is uploaded

		#  time1 = time.time()
		self.step = int(um/.03)
		self.z += um
		if um < 1000 or um > -1000:
			self.conn.write(('M'+str(self.step)+'\n').encode())
		else:
			return 'warning: units in microns'

	def moveRelZ(self, um):
		self.currentPos = self.currentPos + um
		self.moveZ(um)

	def homeStageZ(self):
		self.currentPos = 0
		self.home = 0

	def returnToHome(self):
		self.moveAbsZ(self.home)

	def moveAbsZ(self, myZ):
		# might need to change myZ to myZ[0] if you get a tuple error
		if int(myZ) < 100 and int(myZ) > -100:
			oldPos = self.currentPos
			self.currentPos = myZ
			self.moveZ(self.currentPos-oldPos)
		elif myZ > 100:
			myZ = 100
			oldPos = self.currentPos
			self.currentPos = myZ
			self.moveZ(self.currentPos-oldPos)
		elif myZ < -100:
			myZ = -100
			oldPos = self.currentPos
			self.currentPos = myZ
			self.moveZ(self.currentPos-oldPos)

	def createPositionListZ(self):
		self.positionListZ = np.zeros((100,1))

	def goToPosition(self, pos):
		self.moveAbsZ(self.positionListZ[pos])

	def printPositionList(self):
		print(self.positionListZ)

	def removeZeros(self):
		self.positionListZ = self.positionListZ[~np.all(self.positionListZ==0, axis = 1)]

	def numPositions(self):
		rows = len(self.positionListZ)
		return rows

	def savePositionZ(self, pos):
		posZ = self.getPosZ()
		self.positionListZ[pos]= posZ

	def getPosZ(self):
		return self.currentPos

	def removeZeros(self):
		self.positionListZ = self.positionListZ[~np.all(self.positionListZ==0, axis = 1)]

	def createFocusLocZPos(self):
		self.focusLocZPos = 0

	def createFirstWellCenterZpos(self):
		self.firstWellCenterZPos = 0

	def setFocusLocZPos(self):
		self.positionListZ = self.positionListZ[~np.all(self.positionListZ==0, axis = 1)]

Arduino code:

Code: Select all


#include <Stepper.h>
long z; // number of z steps, defined variable
long sp = 400; // speed of motor

#include <Wire.h>
#include <Adafruit_GFX.h>   // Core graphics library
#include "Adafruit_LEDBackpack.h"

// Set steps per revolution
#define STEPS 100
//  fill_c = matrix.Color333(7, 0, 0);
// Motor pins


int Zpin1 = 10;   // H-bridge, Z-axis motor pin 1
int Zpin2 = 12;   // H-bridge, Z-axis motor pin 2
int Zpin3 = 13;   // H-bridge, Z-axis motor pin 3
int Zpin4 = 11;   // H-bridge, Z-axis motor pin 4

int trigpin = A1;
//String zstep;

// Motor pins
Stepper motor3(STEPS, Zpin2, Zpin1, Zpin3, Zpin4);

// 32x32 matrix has the SINGLE HEADER input,
// so we use this pinout:
//#define CLK A  // MUST be on PORTB! (Use pin 11 on Mega

//initialize variables for matrix control
//int      x, y;
//float    dx, dy;//, d;

//define variables for serial control
char inputChar = ' ';         // a string to hold incoming data
boolean stringComplete = false;  // whether the string is complete
String intvar;
String ledChar;
char intchar;
char intvar2;

Adafruit_8x8matrix matrix = Adafruit_8x8matrix();

void setup() {
  //pinMode(Zpin1, OUTPUT)
  //pinMode(Zpin2, OUTPUT)
  //pinMode(Zpin3, OUTPUT)
  //pinMode(Zpin4, OUTPUT)
  pinMode(trigpin, OUTPUT);

  //setup matrix
  matrix.begin(0x70);
  
  matrix.setBrightness(10);

  Serial.begin(9600);  // Baud rate for serial communication (originally 9600)
  Serial.setTimeout(1000);
  motor3.setSpeed(sp);

  // pinMode(pin,INPUT);
  pinMode(Zpin1, OUTPUT);
  pinMode(Zpin2, OUTPUT);
  pinMode(Zpin3, OUTPUT);
  pinMode(Zpin4, OUTPUT);
  //pinMode(readpin,OUTPUT);
  //turn off matrix to start
//  matrix.fillScreen(matrix.Color333(0, 0, 0));
  while (!Serial);

}

void topHalf() {
  matrix.fillCircle(4,4,3,LED_ON);
  matrix.fillCircle(4,3,3,LED_ON);
  matrix.fillCircle(3,3,3,LED_ON);
  matrix.fillCircle(3,4,3,LED_ON);
  //fullCrc();
  matrix.fillRect(4, 0, 4, 8, LED_OFF);
  matrix.writeDisplay();
  }

void botHalf() {
  matrix.fillCircle(3,4,3,LED_ON);
  matrix.fillCircle(4,3,3,LED_ON);
  matrix.fillCircle(3,3,3,LED_ON);
  matrix.fillCircle(4,4,3,LED_ON);
  //fullCrc();
  matrix.fillRect(0, 0, 4, 8, LED_OFF);
  matrix.writeDisplay();
  //matrix.writeDisplay();
  }

void lftHalf() {
  matrix.fillCircle(4,4,3,LED_ON);
  matrix.fillCircle(4,3,3,LED_ON);
  matrix.fillCircle(3,3,3,LED_ON);
  matrix.fillCircle(3,4,3,LED_ON);
  //fullCrc();
  matrix.fillRect(0, 0, 8, 4, LED_OFF);
  matrix.writeDisplay();
  }

void rgtHalf() {
  matrix.fillCircle(4,4,3,LED_ON);
  matrix.fillCircle(4,3,3,LED_ON);
  matrix.fillCircle(3,3,3,LED_ON);
  matrix.fillCircle(3,4,3,LED_ON);
  //fullCrc();
  matrix.fillRect(0, 4, 8, 4, LED_OFF);
  matrix.writeDisplay();
  }

void outEdge() {
//  matrix.fillScreen(fill_c);
//
//  matrix.fillCircle(16, 16, 15, matrix.Color333(0, 0, 0));
//  matrix.fillCircle(15, 15, 15, matrix.Color333(0, 0, 0));
//
//  matrix.drawPixel(0, 19, matrix.Color333(0, 0, 0));
//  matrix.drawPixel(31, 12, matrix.Color333(0, 0, 0));
//  matrix.drawPixel(19, 0, matrix.Color333(0, 0, 0));
//  matrix.drawPixel(12, 31, matrix.Color333(0, 0, 0));
}

void fullCrc() {
  //fill_c = matrix.Color333(7, 0, 0);\

//  digitalWrite(trigpin,HIGH);
//  delay(3000);
//  digitalWrite(trigpin,LOW);

  matrix.fillScreen(LED_OFF);
  matrix.fillCircle(4,4,3,LED_ON);
  matrix.fillCircle(4,3,3,LED_ON);
  matrix.fillCircle(3,3,3,LED_ON);
  matrix.fillCircle(3,4,3,LED_ON);
  matrix.writeDisplay();

  
//  matrix.drawPixel(0, 19, fill_c);
//  matrix.drawPixel(31, 12, fill_c);
//  matrix.drawPixel(19, 0, fill_c);
//  matrix.drawPixel(12, 31, fill_c);
}

void smallCrc() {
  //fill_c = matrix.Color333(7, 0, 0);
  matrix.fillScreen(LED_OFF);
  matrix.fillCircle(4,4,1,LED_ON);
  matrix.fillCircle(4,3,1,LED_ON);
  matrix.fillCircle(3,3,1,LED_ON);
  matrix.fillCircle(3,4,1,LED_ON);
  matrix.writeDisplay();
  
//  matrix.drawPixel(0, 19, fill_c);
//  matrix.drawPixel(31, 12, fill_c);
//  matrix.drawPixel(19, 0, fill_c);
//  matrix.drawPixel(12, 31, fill_c);
}

void emptySc() {
  matrix.fillRect(0, 0, 8,8,LED_OFF);
  matrix.writeDisplay();
}

void loop() {
  // alternate among different fill types
  //fill_c = matrix.Color333(7, 0, 0);

  // process response when a newline arrives:



// void serialEvent() {
  if (Serial.available()) {
//    intvar = Serial.readStringUntil();
//    intvar.toCharArray(intchar, 2);
//    Serial.print(intchar);

    intchar = (char)Serial.read();
//    intchar = String(Serial.read());
//    intvar2 = (char)Serial.read();
//    Serial.print(intvar);
//    Serial.print(intchar);
//    Serial.write('\n');
    
    if (intchar == 'I') 
    {
//      inputChar = ' ';
      while (Serial.available()) {
        // get the new byte:
        // add it to the inputString:
//        ledChar = (char)Serial.read();
        ledChar = Serial.readStringUntil('\n');
//        Serial.print(ledChar);
//        Serial.write('\n');
//        inputChar = (char)Serial.read();
        // if the incoming character is a newline, set a flag
        // so the main loop can do something about it:
//        if (inputChar == '\n') {
//          stringComplete = true;
//        }
//          if (stringComplete) {
        if (ledChar.equals("T")) {
          topHalf();
        } else if (ledChar.equals("B")) {
          botHalf();
        } else if (ledChar.equals("L")) {
          lftHalf();
        } else if (ledChar.equals("R")) {
          rgtHalf();
        } else if (ledChar.equals("F")) {
          fullCrc();
        } else if (ledChar.equals("O")) {
          emptySc();
        } else if (ledChar.equals("E")) {
          outEdge();
        } else if (ledChar.equals("S")) {
          smallCrc();
        }
//      }
      }
    }
    else if (intchar == 'M') {
      String zstep = Serial.readStringUntil('\n');
      z = zstep.toInt();

      motor3.step(z);

      digitalWrite(Zpin1, LOW);
      digitalWrite(Zpin2, LOW);
      digitalWrite(Zpin3, LOW);
      digitalWrite(Zpin4, LOW);
      //digitalWrite(readpin, LOW);
//      Serial.print(zstep + '\n');
    }
  }
}

python code to run this whole operation using the aforementioned classes:

Code: Select all


from Metro import Metro
metro = Metro()
metro.connectMetro()

for jj in range(250):
        start = time.time()
        for ii in range(1000):
            time.sleep(.1)
            if ii % 9 == 0 and int(ii/9) < 96:
                try:
                    metro.goToPosition(int(ii/9))
                except:
                    with smtplib.SMTP_SSL('smtp.gmail.com', port, context=context) as server:
                        server.login('***@gmail.com', password)
                        server.sendmail('***@gmail.com', '***@gmail.com', 'check on your metro!!!')

                    now = datetime.now()
                    now.strftime(""%d/%m/%Y %H:%M:%S")
                    time.sleep(1)
                    quit()
                time.sleep(.2)

            try:
                time.sleep(.1)
                metro.right_half()
                time.sleep(.1)
                metro.top_half()
                time.sleep(.1)
                metro.left_half()
                time.sleep(.1)
                metro.bottom_half()
                time.sleep(.1)

            except:
                with smtplib.SMTP_SSL('smtp.gmail.com', port, context=context) as server:
                    server.login('***@gmail.com', password)
                    server.sendmail('***@gmail.com', '***@gmail.com', 'check on your metro!!!')

                now = datetime.now()
                now.strftime(""%d/%m/%Y %H:%M:%S")
                time.sleep(1)
                quit()
            time.sleep(.1)
Thank you again for any insight you can provide. If I manage to solve this with help or on my own, then I will post the solution below in a reply either way in case anyone else runs into something similar.

Thanks!
Attachments
Screen Shot 2021-10-05 at 8.09.42 AM.png
Screen Shot 2021-10-05 at 8.09.42 AM.png (746.24 KiB) Viewed 406 times

User avatar
Gary_J
 
Posts: 7
Joined: Mon Nov 08, 2021 5:10 pm

Re: Metro M4 write timeout using PySerial

Post by Gary_J »

I am not sure if this will help but I had a similar issue on a project I did using a Mega 2560 and several other Arduinos. I had aen electrically noisy environment and as such, I doubled grounds to remove any ground potential and I also doubled my i2c lines. I had pull up resistors in the circuit and all looked solid with a scope.. then it would just randomly hang.. more so in the wet months. My fix was to move to thinner i2c lines and keep them as short as possible. I found that many issues can stem from wire management causing unwanted capacitance or reflections. It may not be your code. Shut down the i2c and run your code with dummy data to test it out.
Gary

Locked
Please be positive and constructive with your questions and comments.

Return to “Metro, Metro Express, and Grand Central Boards”