Specific bytes sent over serial causes teensy to crash?

Saaif88

Member
Hey everyone,

I have encountered a very strange bug where sending a specific set of bytes over serial causes the Teensy to crash and no longer respond to any further serial commands. I am using a Teensy LC to control a Dynamixel Pro. The Dynamixel pro has a range of +2,147,483,648 to -2,147,483,648 counts so I am using 4 bytes to send the 32-bit long positional data. The breakdown for the serial packet for moving to position 0 is as follows:

36 0 0 0 0 0 37 35
($) [Positional Data] [Checksum] [%] [#]

another example of some valid data would be for position 150000

36 0 2 73 240 196 37 35
($) [Positional Data] [Checksum] [%] [#]

Any variation of this packet is sent from the PC to the Teensy without issue, except for when I send this specific packet:

36 255 253 255 255 5 37 35
($) [-131073 Counts] [Checksum] [%] [#]

This causes the program to no longer respond to further positional commands and requires a hard reset of the Teensy before commands can be sent again. Here is the stripped down code that handles all of this:

Code:
#include <Dynamixel2Arduino.h>  // Official Dynamixel control library for Arduino
#define DXL_SERIAL Serial1      // Serial Port used to communicate with Dynamixel
#define PC_SERIAL Serial        // Serial Port used to communicate with the PC

using namespace ControlTableItem; //This namespace is required to use Control table item names. Required for the Dynamixel Library.

int           PC_numBytes;                  // Number of bytes that are ACTUALLY at the serial port (PC)
int           PC_Expected_Bytes = 8;        // Number of bytes that SHOULD be at the serial port (PC)
uint8_t       DXL_ID = 1;                   // The ID of the Dynamixel

Dynamixel2Arduino dxl(DXL_SERIAL, 2); // Setup the Dynamixel Serial Port, Pin 2 for Transciever DIR Pin

void setup() {

dxl.begin(57600);                 // Initialize communications with the Dynamixel at 57600 baud
dxl.setPortProtocolVersion(2.0);  // Set Port Protocol Version. This has to match with the DYNAMIXEL protocol version.
PC_SERIAL.begin(115200);          // Initialize communications with the PC at 115200 baud
while(!PC_SERIAL) delay(10);      // Only start sending data 10ms after the serial port is open

dxl.torqueOff(DXL_ID);     // Turn the Torque Off. Prerequisite for changing EEPROM data.
dxl.torqueOn(DXL_ID);     // Re-enable the torque
}

void loop() {
  PC_numBytes = PC_SERIAL.available(); // Check to see how many bytes are waiting at the serial port.

  if (PC_numBytes >= 1) // If there is something at the serial port:
  {
    // Peek to see if first character is the dollar sign. If not, clear the buffer. The $ indicates valid data.
    if (PC_SERIAL.peek() != '$')
    {
      while (PC_SERIAL.available() > 0) PC_SERIAL.read(); // Flush serial RX buffer by reading the data
    }
  }

  if (PC_numBytes >= PC_Expected_Bytes) // If there are the correct number of bytes waiting, then:
  {
    Serial_Parse(PC_Expected_Bytes);  // run the Serial_Parse Function
  }

}

void Serial_Parse(int Bytes)
{
  char        PC_Rx_Sentence[9] = "$000000#";  // Initialize received serial sentence (PC, 8 bytes)
  long        Desired_Position;                // The desired position of the Dynamixel
  long        Goal_Position;                   // The goal position value of the Dynamixel
  
  // Parse received serial
  for (int x=0; x < Bytes; x++) //Put each byte received into an array
  {
    PC_Rx_Sentence[x] = PC_SERIAL.read();
  }

  // If the command starts with the $ sign, ends with the # sign, and the checksum matches (Same checksum style as Dynamixel) do this:
  if (PC_Rx_Sentence[0] == '$' && PC_Rx_Sentence[Bytes - 1] == '#' && PC_Rx_Sentence[5] == lowByte(~(PC_Rx_Sentence[1] + PC_Rx_Sentence[2] + PC_Rx_Sentence[3] + PC_Rx_Sentence[4])) && PC_Rx_Sentence[6] == '%')
    {    
        Desired_Position = (PC_Rx_Sentence[1] << 24) | (PC_Rx_Sentence[2] << 16) | ( PC_Rx_Sentence[3] << 8 ) | (PC_Rx_Sentence[4]); // This is how you make a 32-Bit number with four bytes
        Goal_Position = (dxl.readControlTableItem(GOAL_POSITION, DXL_ID)); // Read the current goal position from the Dynamixel

        if (Desired_Position == Goal_Position) // If the desired position is the same as the goal position, don't write anything, as nothing has changed.
        {
          //Do nothing
        }

        else if (Desired_Position != Goal_Position) // If the desired position is not the same as the goal position:
        {
          dxl.setGoalPosition(DXL_ID, Desired_Position); // Set the new goal position 
        }
    }
}

I don't really understand what part of this code is breaking. If anyone could help me to understand what I am doing wrong, I would really appreciate it!
 
Could a shorter sketch with Dynamixel code removed be tested there and seen to repeat the failure?

Posting that sketch would allow others to run the code and experiment.

That would have to be done to test here - and then there would be no way of knowing if the problem persists.

Something like this ... edited here ... just for example of desired starting point:
Code:
#define PC_SERIAL Serial        // Serial Port used to communicate with the PC
int           PC_numBytes;                  // Number of bytes that are ACTUALLY at the serial port (PC)
int           PC_Expected_Bytes = 8;        // Number of bytes that SHOULD be at the serial port (PC)

void setup() {

PC_SERIAL.begin(115200);          // Initialize communications with the PC at 115200 baud
while(!PC_SERIAL) delay(10);      // Only start sending data 10ms after the serial port is open
}

void loop() {
  PC_numBytes = PC_SERIAL.available(); // Check to see how many bytes are waiting at the serial port.

  if (PC_numBytes >= 1) // If there is something at the serial port:
  {
    // Peek to see if first character is the dollar sign. If not, clear the buffer. The $ indicates valid data.
    if (PC_SERIAL.peek() != '$')
    {
      while (PC_SERIAL.available() > 0) PC_SERIAL.read(); // Flush serial RX buffer by reading the data
    }
  }

  if (PC_numBytes >= PC_Expected_Bytes) // If there are the correct number of bytes waiting, then:
  {
    Serial_Parse(PC_Expected_Bytes);  // run the Serial_Parse Function
  }

}

void Serial_Parse(int Bytes)
{
  char        PC_Rx_Sentence[9] = "$000000#";  // Initialize received serial sentence (PC, 8 bytes)
  long        Desired_Position;                // The desired position of the Dynamixel
  
  // Parse received serial
  for (int x=0; x < Bytes; x++) //Put each byte received into an array
  {
    PC_Rx_Sentence[x] = PC_SERIAL.read();
  }

  // If the command starts with the $ sign, ends with the # sign, and the checksum matches (Same checksum style as Dynamixel) do this:
  if (PC_Rx_Sentence[0] == '$' && PC_Rx_Sentence[Bytes - 1] == '#' && PC_Rx_Sentence[5] == lowByte(~(PC_Rx_Sentence[1] + PC_Rx_Sentence[2] + PC_Rx_Sentence[3] + PC_Rx_Sentence[4])) && PC_Rx_Sentence[6] == '%')
    {    
        Desired_Position = (PC_Rx_Sentence[1] << 24) | (PC_Rx_Sentence[2] << 16) | ( PC_Rx_Sentence[3] << 8 ) | (PC_Rx_Sentence[4]); // This is how you make a 32-Bit number with four bytes
[B]PC_SERIAL.println(Desired_Position)[/B] // add this to show completion value
    }
}
 
Sorry I have not played with the Dynamixel Pros... And awhile since I tried the Dynamixel2Arduino library.

If I were playing with this, I might try instrumenting the code with some more information and checking for error conditions and the like:

For example: dxl.setGoalPosition(DXL_ID, Desired_Position); // Set the new goal position
This method returns a bool where true it succeeded and false it failed. I would have the code check this and report if it failed.
And would probably print out the last error code.
Like: Serial.println(dxl.getLastLibErrCode());

If I received a failure, I might try reading the hardware error code on the servo. And if there is an error, maybe then try to reset the servo.

But again been a long time
 
@KurtE's debug attention will probably get to the problem.

The no Dynamixel version shouldn't fail as parse code works for all others and looks sane as I read it, though willing to confirm and see otherwise.
 
Thanks to both of you guys, you are most certainly correct that that code works fine without the Dynamixel stuff. Turns out, the Dynamixel stuff is the problem.

If I sent the Dynamixel to position -131072, it works just fine. If I try to send it to -131074, it works just fine, but if I try to send it to -131073 it sends correctly, and in fact moves there, but then afterwards the Dynamixel reports an Error 10, which according to the documentation is a DXL_LIB_ERROR_BUFFER_OVERFLOW error and sometimes and Error 12 which is a DXL_LIB_ERROR_WRONG_PACKET error.

I have absolutely no idea why this specific value causes this. Even sending the value directly in the code causes the same issue so it is not a problem with my serial parse code. I guess it's time to take this to the Dynamixel forums and see what they have to say.

Dynamixel Controller Error.PNG
 
Okay, further update, turns out I still don't know what I'm talking about. There is a whole lot of weird stuff going on here, I don't even know where to start. With my previous post above I modified the code a bit to add a case for $000000# and a case for #$111111#:

Code:
if (PC_Rx_Sentence[0] == '$' && PC_Rx_Sentence[Bytes - 1] == '#' && PC_Rx_Sentence[5] == lowByte(~(PC_Rx_Sentence[1] + PC_Rx_Sentence[2] + PC_Rx_Sentence[3] + PC_Rx_Sentence[4])) && PC_Rx_Sentence[6] == '%')
    {    
        Desired_Position = (PC_Rx_Sentence[1] << 24) | (PC_Rx_Sentence[2] << 16) | ( PC_Rx_Sentence[3] << 8 ) | (PC_Rx_Sentence[4]); // This is how you make a 32-Bit number with four bytes
        Goal_Position = (dxl.readControlTableItem(GOAL_POSITION, DXL_ID)); // Read the current goal position from the Dynamixel

        if (Desired_Position == Goal_Position) // If the desired position is the same as the goal position, don't write anything, as nothing has changed.
        {
          PC_SERIAL.print("Error ");
          PC_SERIAL.println((dxl.getLastLibErrCode()));
          PC_SERIAL.print("Dynamixel is already at ");
          PC_SERIAL.print(Goal_Position);
          PC_SERIAL.println("\r\n");
        }

        else if (Desired_Position != Goal_Position) // If the desired position is not the same as the goal position:
        {
          PC_SERIAL.print("Error ");
          PC_SERIAL.println((dxl.getLastLibErrCode()));
          Success = dxl.setGoalPosition(DXL_ID, Desired_Position); // Set the new goal position 
          PC_SERIAL.print("Dynamixel is going to ");
          PC_SERIAL.println(Desired_Position);
          PC_SERIAL.print("Success or Failure? ");
          PC_SERIAL.print(Success);
          PC_SERIAL.println("\r\n");
        }
    }

   else if (PC_Rx_Sentence[0] == '$' && PC_Rx_Sentence[Bytes - 1] == '#' && PC_Rx_Sentence[1] == '0' && PC_Rx_Sentence[2] == '0' && PC_Rx_Sentence[3] == '0' && PC_Rx_Sentence[4] == '0' && PC_Rx_Sentence[5] == '0' && PC_Rx_Sentence[6] == '0')
  {
          PC_SERIAL.print("Error ");
          PC_SERIAL.println((dxl.getLastLibErrCode()));
          Success = dxl.setGoalPosition(DXL_ID, -131073); // Set the new goal position 
          PC_SERIAL.print("Dynamixel is going to ");
          PC_SERIAL.println(-131073);
          PC_SERIAL.print("Success or Failure? ");
          PC_SERIAL.print(Success);
          PC_SERIAL.println("\r\n"); 
  }

     else if (PC_Rx_Sentence[0] == '$' && PC_Rx_Sentence[Bytes - 1] == '#' && PC_Rx_Sentence[1] == '1' && PC_Rx_Sentence[2] == '1' && PC_Rx_Sentence[3] == '1' && PC_Rx_Sentence[4] == '1' && PC_Rx_Sentence[5] == '1' && PC_Rx_Sentence[6] == '1')
  {
          PC_SERIAL.print("Error ");
          PC_SERIAL.println((dxl.getLastLibErrCode()));
          PC_SERIAL.print("Dynamixel is currently at ");
          PC_SERIAL.print(Goal_Position);
          PC_SERIAL.println("\r\n"); 
  }

$000000# moves it directly to -131073 without any serial transmission of that position, it is just hardcoded in.
$111111# just reads the error and whatever position the Dynamixel is currently trying to move to.

Doing this still causes the system to fail but unless I'm doing something wrong, the error information does not get updated. It does in fact move the position, but then reports that it is at 0, without any error. Only when I try to send it to another position does the error info get updated.

More Problems.PNG
 
You probably should post on robotis forum. I don’t have any pros so could not try it. Which ones do you have
 
Back
Top