[Solved] How to troubleshoot Serial Communication

Hey guys I am building a robotic arm controlled with a kinematic model. For that purpose I want python to send 3 digits to the arduino via the serial port. The code for that seems to be working partially at least but it still has some bugs. My question is how do I go about debugging the arduino recieve code without being able to use Serial.print as the Serial port on the arduino is busy when talking to python and the arduino gets reset when I try and open the Serial monitor after closing python. Ill post my code just in case it helps or somebody maybe knows whats going wrong. The function in the Arduino code that is concerned with reading Serial data is the getData() function at the bottom of the code.

Thanks in advance!

Python code:

import serial
import time

arduino = serial.Serial('COM4',9600)
time.sleep(4)
x = 0

while x == 0:
   x = 1
   var1 = 110
   var2 = 105
   var3 = 110
   data = str([var1,var2,var3])
   data = data.replace("[","")
    data = data.replace("]","")
   arduino.write(bytes(data,encoding="ascii"))
   print(data)
   print("done")
[code]

Arduino Code:
[code]
#include "BTS7960.h"

const uint8_t upper_en =12;
const uint8_t upper_lpwm =11;
const uint8_t upper_rpwm =10;

const uint8_t lower_en =9;
const uint8_t lower_lpwm =8;
const uint8_t lower_rpwm =7;

const uint8_t b1 = 3;
const uint8_t b2 = 4;

const uint8_t stepperbase_step =6;
const uint8_t stepperbase_dir =5;

const uint8_t stepperlower_step =A0;
const uint8_t stepperlower_dir =A1;

const uint8_t stepperupper_step =A2;
const uint8_t stepperupper_dir =A3;

int Index;
int x = 0;

int a_upper;
int a_lower;
int a_base;

int current_upper;
int current_lower;
int current_base;


BTS7960 uppermotor(upper_en, upper_lpwm, upper_rpwm);
BTS7960 lowermotor(lower_en, lower_lpwm, lower_rpwm);


void setup() {
pinMode(upper_en,OUTPUT);
pinMode(upper_lpwm,OUTPUT);
pinMode(upper_rpwm,OUTPUT);
pinMode(lower_en,OUTPUT);
pinMode(lower_lpwm,OUTPUT);
pinMode(lower_rpwm,OUTPUT);
pinMode(stepperbase_step,OUTPUT);
pinMode(stepperbase_dir,OUTPUT);
pinMode(stepperlower_step,OUTPUT);
pinMode(stepperlower_dir,OUTPUT);
pinMode(stepperupper_step,OUTPUT);
pinMode(stepperupper_dir,OUTPUT);

pinMode(b1,INPUT);
pinMode(b2,INPUT);

Serial.begin(9600);

uppermotor.Disable();
lowermotor.Disable();
}

void loop() {
 while (x==0){
 HOME();
 delay(500);
 x = 1;
 }
 //upperStepper(0,3000,42);
 //Serial.println("current upper: ");
 //Serial.println(current_upper);                              // 0 == UP , 1 == DOWN       42 = about 1 mm nut travel
 //delay(4000);
 //upperStepper(1,3000,42);
 //Serial.println("current upper: ");
 //Serial.println(current_upper);
 //delay(4000);
 getData();
 
}

void upperMotor(int dir,int speed){
  uppermotor.Enable();
 if(dir == 1){
   uppermotor.TurnLeft(speed); 
 }
 else{
   uppermotor.TurnRight(speed);
 }

}

void lowerMotor(int dir,int speed){
 lowermotor.Enable();
 if(dir == 0){
   lowermotor.TurnLeft(speed);
 }
 else{
   lowermotor.TurnRight(speed);
 }

}

void upperStepper(int dir, int speeder, int duration  ){
  if(dir == 1){
   Serial.println("upperStepper dir 1");
   current_upper = current_upper + (duration/42); //umwandlung von duration in mm bewegung der mutter
   Serial.println("upper Stepper dir1 current upper: ");
   Serial.println(current_upper);
digitalWrite(stepperupper_dir,HIGH);
upperMotor(1,255);
Serial.println("motor duration: ");
 Serial.println(duration);
 for(Index = 0; Index < duration; Index++)
 {
   Serial.println("HI");
   digitalWrite(stepperupper_step,HIGH);
   delayMicroseconds(speeder);
   digitalWrite(stepperupper_step,LOW);
   delayMicroseconds(speeder);
 }
 uppermotor.Stop();
  }

  if(dir == 0){
   current_upper = current_upper - (duration/42); //umwandlung von duration in mm bewegung der mutter
   Serial.println("upper Stepper dir0 current upper: ");
   Serial.println(current_upper);
   digitalWrite(stepperupper_dir,LOW);
   upperMotor(0,255);
   Serial.println("motor duration: ");
 Serial.println(duration);
 for(Index = 0; Index < duration + 8; Index++)
 {

   digitalWrite(stepperupper_step,HIGH);
   delayMicroseconds(speeder);
   digitalWrite(stepperupper_step,LOW);
   delayMicroseconds(speeder);
 }
 uppermotor.Stop();
  }
}

void lowerStepper(int dir, int speeder, int duration  ){
  if(dir == 1){
   current_lower = current_lower + (duration/42); //umwandlung von duration in mm bewegung der mutter
   
digitalWrite(stepperlower_dir,HIGH);
lowerMotor(1,180);
 
 for(Index = 0; Index < duration; Index++)
 {
   
   digitalWrite(stepperlower_step,HIGH);
   delayMicroseconds(speeder);
   digitalWrite(stepperlower_step,LOW);
   delayMicroseconds(speeder);
 }
 lowermotor.Stop();
  }

  if(dir == 0){
   current_lower = current_lower - (duration/42); //umwandlung von duration in mm bewegung der mutter
   
   digitalWrite(stepperlower_dir,LOW);
   lowerMotor(0,255);
   
 for(Index = 0; Index < duration + 26; Index++)
 {

   digitalWrite(stepperlower_step,HIGH);
   delayMicroseconds(speeder);
   digitalWrite(stepperlower_step,LOW);
   delayMicroseconds(speeder);
 }
 lowermotor.Stop();
  }
}

int getbutton1(){ //upper
bool buttonstate = digitalRead(b1); 
return buttonstate;
}

int getbutton2(){ //lower
bool buttonstate = digitalRead(b2); 
return buttonstate;
}

void HOME(){
 while(getbutton1() == 0){
 upperStepper(0,3000,10);
 Serial.println(getbutton1());
 }
 upperStepper(1,3000,200);

 delay(500);

 while(getbutton2() == 0){
 lowerStepper(0,3000,10);
 Serial.println(getbutton2());
 }
 lowerStepper(1,3000,200);
 current_upper = 105;
 current_lower = 105;
}

void getData() {

 if(Serial.available() >= 0){

String input = Serial.readString();

String value1, value2, value3;

   value1 = input.substring(0, 3);
   value2= input.substring(4,7);
   value3= input.substring(8,11);

   
   int bvar1 = value1.toInt();
   int bvar2 = value2.toInt();
   int bvar3 = value3.toInt();
   
   if(bvar1 != 0){
      a_upper = bvar1;
   }

   if(bvar2 != 0){
      a_lower = bvar2;
   }

   if(bvar3 != 0){
      a_base = bvar3;
   }
   Serial.println("a_upper: ");
   Serial.println(a_upper);
   Serial.println("a_lower: ");
   Serial.println(a_lower);
}
if(current_upper < a_upper){ //down                    1
 Serial.println("eins");
 Serial.println("current upper: ");
 Serial.println(current_upper);
 int duration = 0;
 duration = (a_upper - current_upper) * 42;
 upperStepper(1,3000,duration);
}

else if(current_upper > a_upper){                     //2
 Serial.println("zwei");
 int duration = 0;
 duration =  (current_upper - a_upper) * 42 ;
 Serial.println("duration: ");
 Serial.println(duration);
 upperStepper(0,3000,duration);
}

     
if(current_lower < a_lower){                          //3
 Serial.println("drei");
 int duration = 0;
 duration = (a_lower - current_lower ) * 42;
 lowerStepper(1,3000,duration);
}

else if(current_lower > a_lower){                     //4
 Serial.println("vier");
 int duration = 0;
 duration =  (current_lower - a_lower) * 42 ;
 lowerStepper(0,3000,duration);
}
}

[/code]

Generally, you have to have serial available for debugging. You can light LEDs to show you where you are in the code or put some debug info on an LCD but when you're struggling, Serial is where it's at.

You can also print debug to a file on an SD card, but of course you only get to look at it afterwards.

If you have another Arduino, you can pass debug info to it over software Serial and then have it echo to the Serial monitor.

In your particular case though, it looks like that python code sends one string, so you could hard code it on the receiver and debug that way. Similarly, you could type it into the Serial monitor by hand to debug the receiver.

I tend to split the receive serial thing into 2 pieces.

One piece is a function that just does the receiving. When sending serial I send a sentence such as

<%,123,456,789,10.01>

The sentence has a structure of a beginning of sentence marker, data dividers, and end of sentence. By sending a sentence the data to be received has a structure. If I receive a 123 but I have not received a < then that data sentence will be ignored because it is an incomplete data sentence. Here is an example of receive serial that I’ve used since using an Uno

void fReceiveSerial_LIDAR( void * parameters  )
{
  bool BeginSentence = false;
  char OneChar;
  char *str;
  str = (char *)ps_calloc(300, sizeof(char) ); // put str buffer into PSRAM
  // log_i("Free PSRAM before String: %d", ESP.getFreePsram());
  for ( ;; )
  {
    EventBits_t xbit = xEventGroupWaitBits (eg, evtReceiveSerial_LIDAR, pdTRUE, pdTRUE, portMAX_DELAY);
    if ( LIDARSerial.available() >= 1 )
    {
      while ( LIDARSerial.available() )
      {
        OneChar = LIDARSerial.read();
        if ( BeginSentence )
        {
          if ( OneChar == '>')
          {
            if ( xSemaphoreTake( sema_ParseLIDAR_ReceivedSerial, xSemaphoreTicksToWait10 ) == pdTRUE )
            {
               xQueueOverwrite( xQ_LIDAR_Display_INFO, ( void * ) &str );
              xEventGroupSetBits( eg, evtParseLIDAR_ReceivedSerial );
            }
            BeginSentence = false;
            break;
          }
          strncat( str, &OneChar, 1 );
        }
        else
        {
          if ( OneChar == '<' )
          {
            strcpy( str, ""); // clear string buffer
            BeginSentence = true; // found beginning of sentence
          }
        }
      } //  while ( LIDARSerial.available() )
    } //if ( LIDARSerial.available() >= 1 )
    xSemaphoreGive( sema_ReceiveSerial_LIDAR );
    //        log_i( "fReceiveSerial_LIDAR " );
    //        log_i(uxTaskGetStackHighWaterMark( NULL ));
  }
  free(str);
  vTaskDelete( NULL );
} //void fParseSerial( void * parameters  )

Basically, a buffer ( str = (char *)ps_calloc(300, sizeof(char) ); ) is created to hold the data to be received. In coming data is checked for the sentence begin ( if ( OneChar == ‘<’ ) ), if a sentence begin is found then the incoming is stored until a sentence end is received, where the data is bundled up and sent over to a parser.

void fParseLIDAR_ReceivedSerial ( void * parameters )
{
  // distribute received LIDAR info
  String sTmp = "";
  sTmp.reserve ( 20 );
  String sMessage = "";
  sMessage.reserve ( StringBufferSize300 );
  for ( ;; )
  {
    EventBits_t xbit = xEventGroupWaitBits (eg, evtParseLIDAR_ReceivedSerial, pdTRUE, pdTRUE, portMAX_DELAY) ;
    xQueueReceive ( xQ_LIDAR_Display_INFO, &sMessage, QueueReceiveDelayTime );
    int commaIndex = sMessage.indexOf(',');
    sTmp.concat ( sMessage.substring(0, commaIndex) );
    sMessage.remove( 0, (commaIndex + 1) ); // chop off begining of message
    if ( sTmp == "!" )
    {
      xSemaphoreGive ( sema_LIDAR_OK );
      //  Display info from LIDAR
      sLIDAR_Display_Info = sMessage;
    }
    if ( sTmp == "$" )
    {
      xEventGroupSetBits( eg1, evtResetWatchDogVariables );
    }
    if ( sTmp == "#")
    {
      xSemaphoreTake( sema_LIDAR_Alarm, xSemaphoreTicksToWait );
      sLIDAR_Alarm_info = sMessage;
      xSemaphoreGive( sema_LIDAR_Alarm );
      xEventGroupSetBits( eg, evtfLIDAR_Alarm );
    }
    sTmp = "";
    sMessage = "";
    xSemaphoreGive( sema_ParseLIDAR_ReceivedSerial );
  }
  vTaskDelete( NULL );
} // void fParseReceivedSerial ( void * parameters )

Thats the strange thing. When I hard code the string everything works. There seems to be a distinct issue with the transmission

Idahowalker:
I tend to split the receive serial thing into 2 pieces.

One piece is a function that just does the receiving. When sending serial I send a sentence such as

<%,123,456,789,10.01>

The sentence has a structure of a beginning of sentence marker, data dividers, and end of sentence. By sending a sentence the data to be received has a structure. If I receive a 123 but I have not received a < then that data sentence will be ignored because it is an incomplete data sentence. Here is an example of receive serial that I’ve used since using an Uno

void fReceiveSerial_LIDAR( void * parameters  )

{
 bool BeginSentence = false;
 char OneChar;
 char *str;
 str = (char *)ps_calloc(300, sizeof(char) ); // put str buffer into PSRAM
 // log_i(“Free PSRAM before String: %d”, ESP.getFreePsram());
 for ( ;; )
 {
   EventBits_t xbit = xEventGroupWaitBits (eg, evtReceiveSerial_LIDAR, pdTRUE, pdTRUE, portMAX_DELAY);
   if ( LIDARSerial.available() >= 1 )
   {
     while ( LIDARSerial.available() )
     {
       OneChar = LIDARSerial.read();
       if ( BeginSentence )
       {
         if ( OneChar == ‘>’)
         {
           if ( xSemaphoreTake( sema_ParseLIDAR_ReceivedSerial, xSemaphoreTicksToWait10 ) == pdTRUE )
           {
              xQueueOverwrite( xQ_LIDAR_Display_INFO, ( void * ) &str );
             xEventGroupSetBits( eg, evtParseLIDAR_ReceivedSerial );
           }
           BeginSentence = false;
           break;
         }
         strncat( str, &OneChar, 1 );
       }
       else
       {
         if ( OneChar == ‘<’ )
         {
           strcpy( str, “”); // clear string buffer
           BeginSentence = true; // found beginning of sentence
         }
       }
     } //  while ( LIDARSerial.available() )
   } //if ( LIDARSerial.available() >= 1 )
   xSemaphoreGive( sema_ReceiveSerial_LIDAR );
   //        log_i( "fReceiveSerial_LIDAR " );
   //        log_i(uxTaskGetStackHighWaterMark( NULL ));
 }
 free(str);
 vTaskDelete( NULL );
} //void fParseSerial( void * parameters  )




Basically, a buffer ( str = (char *)ps_calloc(300, sizeof(char) ); ) is created to hold the data to be received. In coming data is checked for the sentence begin ( if ( OneChar == '<' ) ), if a sentence begin is found then the incoming is stored until a sentence end is received, where the data is bundled up and sent over to a parser.



void fParseLIDAR_ReceivedSerial ( void * parameters )
{
 // distribute received LIDAR info
 String sTmp = “”;
 sTmp.reserve ( 20 );
 String sMessage = “”;
 sMessage.reserve ( StringBufferSize300 );
 for ( ;; )
 {
   EventBits_t xbit = xEventGroupWaitBits (eg, evtParseLIDAR_ReceivedSerial, pdTRUE, pdTRUE, portMAX_DELAY) ;
   xQueueReceive ( xQ_LIDAR_Display_INFO, &sMessage, QueueReceiveDelayTime );
   int commaIndex = sMessage.indexOf(’,’);
   sTmp.concat ( sMessage.substring(0, commaIndex) );
   sMessage.remove( 0, (commaIndex + 1) ); // chop off begining of message
   if ( sTmp == “!” )
   {
     xSemaphoreGive ( sema_LIDAR_OK );
     //  Display info from LIDAR
     sLIDAR_Display_Info = sMessage;
   }
   if ( sTmp == “$” )
   {
     xEventGroupSetBits( eg1, evtResetWatchDogVariables );
   }
   if ( sTmp == “#”)
   {
     xSemaphoreTake( sema_LIDAR_Alarm, xSemaphoreTicksToWait );
     sLIDAR_Alarm_info = sMessage;
     xSemaphoreGive( sema_LIDAR_Alarm );
     xEventGroupSetBits( eg, evtfLIDAR_Alarm );
   }
   sTmp = “”;
   sMessage = “”;
   xSemaphoreGive( sema_ParseLIDAR_ReceivedSerial );
 }
 vTaskDelete( NULL );
} // void fParseReceivedSerial ( void * parameters )




This is of course a more proper solution. However Im not very good at programming. Im more of a home shop machinist who does some coding when its necessary. I thought that my solution would be an easy workaround in my case where I have complete control over the string that is being sent. Do you think that the this approch with the substrings is fundamentally flawed, even for my application?

I don't use Python if I can possibly avoid it, but I don't see any angle brackets in your Python code.

What does the printed version of data look like?

wildbill:
I don't use Python if I can possibly avoid it, but I don't see any angle brackets in your Python code.

What does the printed version of data look like?

110, 105, 110
done

AaronSilas:
This is of course a more proper solution. However Im not very good at programming. Im more of a home shop machinist who does some coding when its necessary. I thought that my solution would be an easy workaround in my case where I have complete control over the string that is being sent. Do you think that the this approch with the substrings is fundamentally flawed, even for my application?

I do not think a technique s wrong when it works.

AaronSilas:
110, 105, 110
done

What happens if you use the serial monitor and paste in the line with the numbers to the serial monitor and use that instead of Python? Set line ending in the monitor to none.

Alright guys Ive solved the problem! The python script added spaces that I didnt account for in the Arduino code.