Issues Controlling 6 Servo Arduino Robot Arm Using Serial Communication

Background information: I am working to control a 6 servo robot arm (Tinkerkit Braccio robot, Tinkerkit Braccio robot โ€” Arduino Official Store) using serial communication to an Arduino Uno R3. The serial communication is received by the Arduino through the hardware serial/USB plug and processed using the code given below,

#include <Servo.h>
#include <Braccio.h>
#include <NeoSWSerial.h>


#define SERVO_BASE_PIN 11
#define SERVO_SHOULDER_PIN 10
#define SERVO_ELBOW_PIN 9
#define SERVO_WRIST_VER_PIN 6
#define SERVO_WRIST_ROT_PIN 5
#define SERVO_GRIPPER_PIN 3

#define BASE_START 0
#define SHOULDER_START 90 //Shoulder servo angles are currently off from what they should be.
// Need to rotate the servo plate 45 degrees to left (when looking at the plate)
#define ELBOW_START 90
#define WRIST_VER_START 90
#define WRIST_ROT_START 90
#define GRIPPER_START 30

NeoSWSerial mySerial(2,4); // RX = 2, TX = 4

Servo base;
Servo shoulder;
Servo elbow;
Servo wrist_ver;
Servo wrist_rot;
Servo gripper;

uint8_t idx = 0;
uint8_t value_idx = 0;
char value[4] = "";
//The problem is in this if and for loop. Something is causing the arduino to add an additional zero to the pos counter.


void reach_goal(Servo& motor, String motorN,  int goal){
  if(goal>=motor.read()){
    for(int pos = motor.read(); pos <= goal; pos += 1){
      mySerial.print("Current goal: "); 
      mySerial.println(goal);
      mySerial.print("Current position: ");
      mySerial.println(pos);
      mySerial.println("Servo used: " + motorN);
      motor.write(pos);
      delay(5);
    }
  }

  else{
    for(int pos = motor.read(); pos >= goal; pos -= 1){
      //mySerial.print("Current goal: ");
      //mySerial.println(goal);
      //mySerial.print("Current position: "); 
      //mySerial.println(pos);
      //mySerial.println("Servo used:" + motorN);
      
      motor.write(pos);
      delay(5);
    }
  }
}

void setup() {
  // put your setup code here, to run once:
  Braccio.begin();
  base.attach(SERVO_BASE_PIN);
  shoulder.attach(SERVO_SHOULDER_PIN);
  elbow.attach(SERVO_ELBOW_PIN);
  wrist_ver.attach(SERVO_WRIST_VER_PIN);
  wrist_rot.attach(SERVO_WRIST_ROT_PIN);
  gripper.attach(SERVO_GRIPPER_PIN);
  
  base.write(BASE_START);
  shoulder.write(SHOULDER_START);
  elbow.write(ELBOW_START);
  wrist_ver.write(WRIST_VER_START); 
  wrist_rot.write(WRIST_ROT_START);
  gripper.write(GRIPPER_START);

  Serial.begin(115200);
  Serial.setTimeout(1);
  //Serial.println("Initial value:");
  //Serial.print(value);

  //mySerial.begin(9600);
  //delay(1000);
  //mySerial.println("Connection to Elegoo Arduino initiated");

}

void loop() {
  // put your main code here, to run repeatedly:
  while(Serial.available() > 0){
    //mySerial.println("Reading serial data");
    
    char chr = Serial.read();
    //Serial.println(chr);

    if(chr == 'b'){
      idx = 0;
      value_idx = 0;
    }
    else if (chr == 's'){
      idx = 1;
      value_idx = 0;
    }
    else if (chr == 'e'){
      idx = 2;
      value_idx = 0;
    } 
    else if (chr == 'r'){
      idx = 3;
      value_idx = 0;
    }
    else if (chr == 'v'){
      idx = 4;
      value_idx = 0;
    }
    else if (chr == 'g'){
      idx = 5;
      value_idx = 0;
    }
    else if (chr == ','){
      //Serial.print("Initial value before atoi");
      //Serial.println(value);
      int val = strtol(value, NULL, 0);

        if(idx == 0){
          //Serial.print("value0: ");
          //Serial.println(val);
          reach_goal(base, "base", val);
        }
        else if (idx == 1){
          //Serial.print("value1: ");
          //Serial.println(val);
          reach_goal(shoulder, "shoulder", val);
        }
        else if (idx == 2){
          //Serial.print("value2: ");
          //Serial.println(val);
          reach_goal(elbow, "elbow", val);
        }
        else if (idx == 3){
          //Serial.print("value3: ");
          //Serial.println(val);
          reach_goal(wrist_rot, "wrist_rot", val);
        }
        else if (idx == 4){
          //Serial.print("value4: ");
          //Serial.println(val);
          reach_goal(wrist_ver, "wrist_ver", val);
        }
        else if (idx == 5){
          //Serial.print("value5: ");
          //Serial.println(val);
          reach_goal(gripper, "gripper", val);
        }

        value[0] = 0;
        value[1] = 0;
        value[2] = 0;
        value[3] = '\0"';
    }
    else{
      value[value_idx] = chr;
      value_idx++;
      
      //String current_pos = String(base.read()) + "," + String(shoulder.read()) + "," + String(elbow.read()) + "," + String(wrist_ver.read()) + "," + String(wrist_rot.read()) + "," + String(gripper.read());
      
      //mySerial.println("The current position of each servo is (in order):" + current_pos);
      //delay(1000);
    
    }
  }
  

}

The data being sent is the wanted angle positions for each of the servos, with a corresponding letter identifier (Ex. b90, s90, e90, v90, r90, g20). This data is generated and sent by Robot Operating System 2 (ROS2, Humble distribution) and MoveIt2 on an Ubuntu 22.04 system. The system operates by being given wanted servo/joint positions and having MoveIt2 generate a movement pathing plan. This plan is then sent to the Arduino in step increments, using the following code and a baud rate of 115200. I have included the entire body of code, but the main function is write.

#include <braccio_arm_config/braccio_interface.hpp>
#include <hardware_interface/types/hardware_interface_type_values.hpp>
#include <pluginlib/class_list_macros.hpp>
#include <chrono>

namespace braccio_arm_config{

    BraccioInterface::BraccioInterface(){



    }

    BraccioInterface::~BraccioInterface(){

        if(arduino_.IsOpen()){
            try{
                arduino_.Close();
            }
            catch(...){
                RCLCPP_FATAL_STREAM(rclcpp::get_logger("BraccioInterface"), "Something has gone wrong while closing the connection with the port " << port_);
            }
        }

    }

CallbackReturn BraccioInterface::on_init(const hardware_interface::HardwareInfo & hardware_info){

    CallbackReturn result = hardware_interface::SystemInterface::on_init(hardware_info);

    if(result != CallbackReturn::SUCCESS){
        return result;
    }

    try{
        port_ = info_.hardware_parameters.at("port");
    }
    catch(const std::out_of_range &e){
        RCLCPP_FATAL_STREAM(rclcpp::get_logger("BraccioInterface"), "No serial port provided. Aborting.");
        return CallbackReturn::FAILURE;
    }

    position_commands_.reserve(info_.joints.size());
    position_states_.reserve(info_.joints.size());
    prev_position_commands_.reserve(info_.joints.size());
    
    return CallbackReturn::SUCCESS;
}

std::vector<hardware_interface::StateInterface> BraccioInterface::export_state_interfaces(){

    std::vector<hardware_interface::StateInterface> state_interfaces;
    for(size_t i = 0; i < info_.joints.size(); i++){
        state_interfaces.emplace_back(hardware_interface::StateInterface(info_.joints[i].name, hardware_interface::HW_IF_POSITION, &position_states_[i]));
    }

    return state_interfaces;


}

std::vector<hardware_interface::CommandInterface> BraccioInterface::export_command_interfaces(){

    std::vector<hardware_interface::CommandInterface> command_interfaces;
    for(size_t i = 0; i < info_.joints.size(); i++){
        command_interfaces.emplace_back(hardware_interface::CommandInterface(info_.joints[i].name, hardware_interface::HW_IF_POSITION, &position_commands_[i]));
    }

    return command_interfaces;

}

CallbackReturn BraccioInterface::on_activate(const rclcpp_lifecycle::State & previous_state){

    RCLCPP_INFO(rclcpp::get_logger("BraccioInterface"), "Starting the Braccio robot hardware.");
    //angles go in order base, shoulder, elbow, wrist_ver, wrist_rot, grippers
    position_commands_ = {0.0, -0.506, -0.611, -1.100, 0.0, 0.0}; //Need to adjust these starting positions.
    prev_position_commands_ = {0.0, -0.506, -0.611, -1.100, 0.0, 0.0};
    position_states_ = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};   

    try{
        arduino_.Open(port_);
        arduino_.SetBaudRate(LibSerial::BaudRate::BAUD_115200);
    }
    catch(...){
        RCLCPP_FATAL_STREAM(rclcpp::get_logger("BraccioInterface"), "Something went wrong while interacting with the serial port " << port_);
        return CallbackReturn::FAILURE;
    }

    RCLCPP_INFO(rclcpp::get_logger("BraccioInterface"), "The Braccio arm has started. Ready to receive position input.");
    return CallbackReturn::SUCCESS;
}


CallbackReturn BraccioInterface::on_deactivate(const rclcpp_lifecycle::State & previous_state){
    RCLCPP_INFO(rclcpp::get_logger("BraccioInterface"), "Stopping the Braccio arm.");
    if(arduino_.IsOpen()){
        try{
            arduino_.Close();
        }
        catch(...){
            RCLCPP_FATAL_STREAM(rclcpp::get_logger("BraccioInterface"), "Something went wrong while disconnecting from the serial port " << port_);
        return CallbackReturn::FAILURE;
        }
    }

    RCLCPP_INFO(rclcpp::get_logger("BraccioInterface"), "The Braccio arm has stopped.");
    return CallbackReturn::SUCCESS;
}

hardware_interface::return_type BraccioInterface::read(const rclcpp::Time & time, const rclcpp::Duration & period){
    //Due to having dumb servos we will assume the servos reached the requested position. ***Not inherently true***.
    //position_states_ = arduino_.Read();
    //std::string return_msg;

    
    position_states_ = position_commands_;
    return hardware_interface::return_type::OK;
}

hardware_interface::return_type BraccioInterface::write(const rclcpp::Time & time, const rclcpp::Duration & period){

    if(position_commands_ == prev_position_commands_){
        return hardware_interface::return_type::OK;
    }   

    //b## = base<angle>, s## = shoulder<angle>, e## = elbow<angle>, v## = Wrist_ver<angle>, r## = Wrist_rot<angle>, g## = gripper<angle>

    std::string msg; 
    std::string msg_rad;// The equations given in the lecture are provided by the UDEMY course instructor. I do not believe they apply to the Braccio arm so they are ommitted.
    //The following equations are converting from radians to degrees. As ros2_control uses radians.
    //Shifts are applied to each joint angle output due to the angle limits in Moveit/RViz differing from the real hardware. 
    //This is due to the zero position of the urdf model and the real hardware being different.
    int base = static_cast<int>((position_commands_.at(0) * 180) / M_PI);
    double base_rad = static_cast<double>(position_commands_.at(0));
    msg.append("b");
    msg.append(std::to_string(base));
    msg.append(",");
    
    msg_rad.append("b");
    msg_rad.append(std::to_string(base_rad));
    msg_rad.append(",");

    int shoulder = static_cast<int>(((position_commands_.at(1)*180)/M_PI)+119);
    double shoulder_rad = static_cast<double>(position_commands_.at(1));

    msg.append("s");
    msg.append(std::to_string(shoulder));
    msg.append(",");
    
    msg_rad.append("s");
    msg_rad.append(std::to_string(shoulder_rad));
    msg_rad.append(",");

    int elbow = static_cast<int>(((position_commands_.at(2)*180)/M_PI)+125);
    double elbow_rad = static_cast<double>(position_commands_.at(2));
    
    msg.append("e");
    msg.append(std::to_string(elbow));
    msg.append(",");

    msg_rad.append("e");
    msg_rad.append(std::to_string(elbow_rad));
    msg_rad.append(",");


    int wrist_ver = static_cast<int>(((position_commands_.at(3)*180)/M_PI)+153);
    double wrist_ver_rad = static_cast<double>(position_commands_.at(3));
    
    msg.append("v");
    msg.append(std::to_string(wrist_ver));
    msg.append(",");

    msg_rad.append("v");
    msg_rad.append(std::to_string(wrist_ver_rad));
    msg_rad.append(",");

    int wrist_rot = static_cast<int>((position_commands_.at(4)*180)/M_PI);
    double wrist_rot_rad = static_cast<double>(position_commands_.at(4));
    
    msg.append("r");
    msg.append(std::to_string(wrist_rot));
    msg.append(",");

    msg_rad.append("r");
    msg_rad.append(std::to_string(wrist_rot_rad));
    msg_rad.append(",");
    
    int grippers = static_cast<int>(((position_commands_.at(5)*180)/M_PI)+28);
    double grippers_rad = static_cast<double>(position_commands_.at(5));

    msg.append("g");
    msg.append(std::to_string(grippers));
    msg.append(",");

    msg_rad.append("g");
    msg_rad.append(std::to_string(grippers_rad));
    msg_rad.append(",");
    
    try{
        RCLCPP_INFO_STREAM(rclcpp::get_logger("BraccioInterface"), "Attempting to send the following message to the Arduino (in degrees): " << msg);
        RCLCPP_INFO_STREAM(rclcpp::get_logger("BraccioInterface"), "Attempting to send the following message to the Arduino (in radians): " << msg_rad);
        arduino_.Write(msg);
        arduino_.DrainWriteBuffer();
        std::chrono::milliseconds timespan(1000);
    }
    
    catch(const std::runtime_error& re)
    {
    // specific handling for runtime_error
    RCLCPP_ERROR_STREAM(rclcpp::get_logger("BraccioInterface"), "Runtime error: " << re.what() << std::endl);
    }
    catch(const std::exception& ex)
    {
        // specific handling for all exceptions extending std::exception, except
        // std::runtime_error which is handled explicitly
        RCLCPP_ERROR_STREAM(rclcpp::get_logger("BraccioInterface"), "Error occurred: " << ex.what() << std::endl);
    }    
    
    catch(...){
        RCLCPP_ERROR_STREAM(rclcpp::get_logger("BraccioInterface"), "Something has gone wrong while sending the message to the arduino. " << msg);
        RCLCPP_ERROR_STREAM(rclcpp::get_logger("BraccioInterface"), "The exact error is unknown.");
        return hardware_interface::return_type::ERROR;
    }

    

    prev_position_commands_ = position_commands_;
    return hardware_interface::return_type::OK;
}

}

PLUGINLIB_EXPORT_CLASS(braccio_arm_config::BraccioInterface, hardware_interface::SystemInterface);


The wanted positions can either be generated through the robot visualization software or through a simple Python GUI which I had made.

#!/usr/bin/env python3


import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from braccio_msgs.action import BraccioTask
import time
import PySimpleGUI as sg

#Defining the button event for each position button.
def position0(self):
    self.goal.task_number = 0  
    self.get_logger().info("Moving Braccio arm to the safety position. Please wait until movement stops.")

def position1(self):
    self.goal.task_number = 1
    self.get_logger().info("Moving Braccio arm to the pickup position with grippers open. Please wait until movement stops.")

def position2(self):
    self.goal.task_number = 2
    self.get_logger().info("Moving Braccio arm to the pickup position with grippers closed. Please wait until movement stops.")

def position3(self):
    self.goal.task_number = 3
    self.get_logger().info("Moving Braccio arm to its resting position. Please wait until movement stops.")

def positionEnd(self):
    self.get_logger().info("Moving the Braccio arm to the safety position and shutting down program.")
    self.goal.task_number = 0
    self.action_client.send_goal_async(self.goal)
    time.sleep(10)
    self.destroy_node()
    rclpy.shutdown()

class PythonInterface(Node):
    def __init__(self):
        super().__init__("python_interface")
        self.action_client = ActionClient(Node("python_interface"), BraccioTask, "task_server")

        self.action_client.wait_for_server()
            
        self.goal = BraccioTask.Goal()

        self.get_logger().info("Python interface initialized.")
        
        dispatch_dictionary = {'0 - Safety position':position0, '1 - Pickup position, grippers open':position1, '2 - Pickup position, grippers closed':position2, '3 - Rest position':position3, '* - Exit and shutdown':positionEnd}

        sg.theme('Default')
        layout = [[sg.Text("Please click the button corresponding to the desired position. The positions are as follows:")],
                  [sg.Button('0 - Safety position')],
                  [sg.Button('1 - Pickup position, grippers open')],
                  [sg.Button('2 - Pickup position, grippers closed')],
                  [sg.Button('3 - Rest position')],
                  [sg.Button('* - Exit and shutdown')],
                  [sg.Text('You may also input custom angle positions (degrees) in the format: b##,s##,e##, v##, r##, g## (I.e. b0,s90,e90,v120,r20,g10)')],
                  [sg.InputText(focus=True, key='-IN-')]
        ]

        window = sg.Window('Braccio Arm Control Interface', layout, finalize=True)
        window['-IN-'].bind("<Return>", "_Enter")

        while True:
            event, values = window.read()

            if event == sg.WIN_CLOSED:
                break #The exit window button was clicked. The GUI window will close and shutdown.
            if event in dispatch_dictionary: #If a button for a predefined position is clicked then we check which one and set the task_number to the respective value for the position.
                called_movement = dispatch_dictionary[event]
                called_movement(self)

                self.goal.position_angles = "empty"


            elif event == "-IN-" + "_Enter": #If a custom set of inputs were given then we input them into a string and pass them to the task server.
                self.goal.position_angles = values['-IN-']
                self.goal.task_number = 0
            

            self.action_client.send_goal_async(self.goal) #Sends the goal to the task server.

        
        


def main():
    rclpy.init()
    action_client = PythonInterface()
    rclpy.spin(action_client)

if __name__ == '__main__':
    main()

(Sorry if the previous section was quite long. I do not have a background in robotics so I want to be thorough in the explanation of how I set things up in case I made a rookie mistake)

Problems: The servos act erratically when rapid sets of serial data is sent from the computer to the Arduino. The servos will typically behave well if individual servo positions are changed or if the serial communication is sent using the Python GUI.

However, if positions are sent through the robot visualization interface, or too rapidly, 5 of the servos (base doesn't) will begin to move from their minimum positions to their maximum positions in order of the servos (shoulder, elbow, wrist vertical, wrist rotation, grippers). The servos then proceed to their desired positions afterwards. I have included a video to show this behaviour.

This problem becomes present when using the Python GUI after it starts, but I haven't seen it start from using the Python GUI.

Troubleshooting: I have been attempting to troubleshoot this for a couple weeks. Similar topics I have read suggest power issues, serial buffer overflow, or out of array bounds.

Power issues - I haven't seen direct signs on power issues such as the Arduino resetting. There is a set up function for the Braccio arm which initializes the servos and moves them to a set position. I do not see the arm move back to its preset position so I don't believe it is resetting due to insufficient power. Though this may be incorrect.

Serial buffer overflow - I have attempted prevent the risk of a serial buffer overflow by implementing a delay time for the sending of serial data on the computer side. I have also used the Libserial DrainWriteBuffer() function to wait until the previous serial data has been sent.

Out of array bounds - I have tried to look for anywhere in the Arduino code which could result in an out of array bounds error but I haven't found one that sticks out. My knowledge of the different functions used is not perfect though.

Secondary Problem: I have tried to troubleshoot what is being written to the servos on the Arduino end but I cannot use the Serial Monitor while the computer is connected to the serial port. Instead I have been trying to use a second Arduino and the NeoSWSerial library to pass the information to the computer. This results in the robot arm shaking when serial data is read and the serial data is not fully sent to the second Arduino. I believe this is due to the Servo library conflicting with NeoSWSerial due to interrupts but I haven't found a way to properly fix this.

Thank you to those who made it this far and I appreciate any advice that can be given.

TL;DR Five of the servos (excluding base) in a 6 servo robot arm begin moving to their minimum position then maximum position, one at a time, before moving to the requested positions. This seems to happen when the Arduino controlling the servos receives serial data rapidly.

What does this function do?

Your code is not complete. Braces are not paired.

For some parts of the code I had followed along with a Udemy course, particularly for C++ sections (GitHub repository: Robotics-and-ROS-2-Learn-by-Doing-Manipulators/Section9_Build/arduinobot_ws/src/arduinobot_controller/src/arduinobot_interface.cpp at humble ยท AntoBrandi/Robotics-and-ROS-2-Learn-by-Doing-Manipulators ยท GitHub). To my understanding that constructor is meant to be left empty. The .cpp file is also paired with this .hpp file,

#ifndef BRACCIO_INTERFACE_H
#define BRACCIO_INTERFACE_H
#include <rclcpp/rclcpp.hpp>
#include <hardware_interface/system_interface.hpp>
#include <rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp>
#include <rclcpp_lifecycle/state.hpp>
#include <libserial/SerialPort.h>

#include <vector>
#include <string>

namespace braccio_arm_config{
    using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;

    class BraccioInterface : public hardware_interface::SystemInterface{

        public:
            BraccioInterface();
            virtual ~BraccioInterface();

            virtual CallbackReturn on_activate(const rclcpp_lifecycle::State & previous_state) override;
            virtual CallbackReturn on_deactivate(const rclcpp_lifecycle::State & previous_state) override;

            virtual CallbackReturn on_init(const hardware_interface::HardwareInfo & hardware_info) override;
            virtual std::vector<hardware_interface::StateInterface> export_state_interfaces() override;
            virtual std::vector<hardware_interface::CommandInterface> export_command_interfaces() override;

            virtual hardware_interface::return_type read(const rclcpp::Time & time, const rclcpp::Duration & period) override;
            virtual hardware_interface::return_type write(const rclcpp::Time & time, const rclcpp::Duration & period) override;
    
        private:
            LibSerial::SerialPort arduino_;
            std::string port_; //Port is declared in the ros2_control.xacro for the braccio arm. Under /braccio_arm_config/config

            std::vector<double> position_commands_;
            std::vector<double> prev_position_commands_;
            std::vector<double> position_states_;   
};



}

#endif

Sorry, I forgot to include that initially.

Anything else? If not, what issues are you having "controlling 6 servo arduino robot arm using serial communication?"

The main issue I am having is that the Arduino/Robot arm side seems to be moving the joints to unexpected and unrequested positions before moving to the actually requested positions. The behaviour can be seen in the embed video where the arm collides with the table repeatedly before moving to a stable position.

Did you start with control of one servo, then each servo, then multiple servos?

Servos are not rapid, especially under load, and need time to "catch up" - I have read as much as 2ms per degree (360ms per half-circle - almost half a second).

Also look at your power. Is the power not enough and causing erratic behavior? Most motor issues are power related, so verify you have adequate power.

Start with controlling one servo, then each servo (#1 then #2 ... then #6) then multiple (1 and 2 then 1, 2 and 3 then 1, 2, 3...6), then pairs. Note the movements. Check available volts.

I haven't used one servo at a time when taking joint angles from the computer, just all at the same time. I have controlled all 6 servos directly through the serial monitor of the Arduino without any issues.

I'll disconnect all but one servo and send data over to see the behaviour. If a power issue was the cause wouldn't the Arduino reset itself though? If so then I should be able to see the arm reset to its starting position that's written in the setup.

What would be a valid character string to send to to the Serial port? I can send angle commands to individual servos, but could not figure out how to string a command together.

Start:

Commands: b,15... s,30... e,90... v,120... r,150... g,180

Your code works. The issue is in your power supply and wiring.

The string format is something like "b90, s90, e90, v90, r90, g30,". You would just change the numbers for each. Also you are correct that when a single string is inputted to the code that all the servos move correctly. This is what I see with the physical hardware too. The weird issues happen when sets of angles are sent over rapidly.

I will look at the power for the servos and see if there is any unexpected droppage. The kit came with an external power supply but maybe its not supplying enough power for this situation. The wiring I can't really modify as the servos are connected through a shield that came with the robot arm kit.

Check your parsing code. The simulation does not process strings of commands consistently with an input like this:

b30, s60, e90, v120, r150, g180

The comma seems to make the servos go to zero, then move into the commanded position. Not desired for a robot. If I used commas between every element (letters and numbers), the motors moved consistently.

,b,30,s,60,e,90,v,120,r,150,g,180,

Here is the simulation I have set up (beware: I made the variables for easier reading, but the code is intact).

That's.... interesting. When I input b30, s60, e90, v120, r150, g60 (reduced g value due to joint limit) into the serial monitor I get the arm moving to those positions cleanly as expected. When input ,b,30,s,60,e,90,v,120,r,150,g,60, into the serial monitor each servo rapidly goes to zero before moving to the expected positions. I can provide videos of this behaviour if you'd like.

When I double check with the simulation you included I see the same behaviour as for the real hardware.

Okay. That is good.

Have you verified the incoming data by echoing it to the Serial Monitor?

Is your receiving system parsing the commas as needed components of the position command (you saw "s,90" sent the "s" servo to zero before 90 due to the extra comma. The sender and receiver should remove the extra comma).

Also, character arrays (c-strings) need a "null terminator" (\0) inside the data being sent to indicate the end of the string. Without the null, reading beyond that memory location will cause unpredictable results.

You should add a limit checking routine as the data leaves the sender and arrives at the receiver.

1 Like

Have you verified the incoming data by echoing it to the Serial Monitor?

This is something I've been attempting to do with a little bit of issue. The information is sent over the serial port and the Arduino Uno only has the one hardware serial port I can use. So data can't be sent to the serial monitor while the computer side program is using the serial port to send data.

I've tried using software serial and alternative libraries but they've have conflicted with the Servo library.

Also, character arrays (c-strings) need a "null terminator" (\0) inside the data being sent to indicate the end of the string. Without the null, reading beyond that memory location will cause unpredictable results.

This is something I haven't done. I will fix this and see if that remedies the unusual behaviour.

You should add a limit checking routine as the data leaves the sender and arrives at the receiver.

There is a limit checking routine that is run on the computer side, but having it on the Arduino side would be a good idea. Thank you!

Have a look at positioning servos without a library.

So it seems I have fixed the issue! I had added the null terminator at the end of the data being sent through the serial port. I also noticed an issue with the simulation model of the robot arm. The limits for the gripper joint was set correctly, but the rotation direction seemed to be inverted. That has now been fixed.

Applying both these changes had made the robot arm (and servos) act as expected.

Thank you for your assistance!

That is great! Have fun with programming it!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.