RS485 servo control

Hello,

I am building a simple ROV test bed, meansing I want to control a motor and a servo using RS485 to send signals through a cable. I am sending commands from a master arduino to a slave arduino, both UNOs, using MAX485 modules (TLL to RS485, 8 pins). I am using SoftwareSerial because of issues with hardwareSerial (RX (pin 0) and TX (pin 1) not working when the rs485 module is connected to them, even preventing me from uploading code to the arduino).

My setup is working well for the most part. The motor works flawlessly and the servo is turning to the desired angles when turning a pair of potentiometer knobs on the master arduino. The only problem is that the servo is experiencing severe jitter. I have tested the servo on its own, even making it sweep independently of the potentiometer position, but as soon as the master arduino is turned on, the jitters start. I have also tried to connect the grounds of the master and slave sides, but there is no change, and the motor control works fine without that.

Hardware list:

Arduino UNO x2

MAX485 module x2

TB6612FNG motor controller

LM2596 buck converter x2

EFLR71000L digital servo

Here is the code for both arduinos as well as a wiring diagram describing the slave side. Sorry about the legibility, I’m not used to drawing diagrams. Both arduinos are powered via USB (or 7.4V lipo batteries, when not troubleshooting).

Master code:

#include <SoftwareSerial.h>

#define MASTER_EN 8       // RS485 Enable Pin
#define RX 10             // SoftwareSerial RX
#define TX 11             // SoftwareSerial TX

#define SWITCHMOTOR_PIN 4

//speed control potentiometer

int SpeedControl = A0;


int MotorSpeed = 0;


//rudder control potentiometer

int RudderControl = A1;


int RudderPos = 0;


SoftwareSerial rs485Serial(RX, TX); // Create SoftwareSerial instance

void setup() {


  pinMode(SWITCHMOTOR_PIN, INPUT_PULLUP);

  
  pinMode(MASTER_EN, OUTPUT);
  rs485Serial.begin(9600);
  Serial.begin(9600);                // debugging 
  digitalWrite(MASTER_EN, LOW);
}


void loop() {
  digitalWrite(MASTER_EN, HIGH);     // Enable transmit mode
  delay(10);                          // 5ms minimum delay


// foward or backward switch position and send data through rs485

  

  if (digitalRead(SWITCHMOTOR_PIN) == 0) {
  rs485Serial.print("FWD\n");
  rs485Serial.flush();
  delay(10);  // Small buffer time
} else {
  rs485Serial.print("BCK\n");
  rs485Serial.flush();
  delay(10);
}
  rs485Serial.flush();
  delay(20);
  

  //read potentiometer for motor speed and send data through rs485

  MotorSpeed = map(analogRead(SpeedControl), 0, 1023, 0 , 255);
 
  rs485Serial.print("SPD:");
  rs485Serial.println(MotorSpeed);
  rs485Serial.flush();  // Wait until data is sent
  delay(50);            


   //read potentiometer for rudder position and send data through rs485

  RudderPos = map(analogRead(RudderControl), 0, 1023, 0 , 255);
           
  rs485Serial.print("RUD:");
  rs485Serial.println(RudderPos);
  rs485Serial.flush();  // Wait until data is sent
  delay(50);            
 
 Serial.println(digitalRead(SWITCHMOTOR_PIN));        // Debug info
 Serial.println(MotorSpeed);                         // Debug info
 Serial.println(RudderPos);                         // Debug info

 delay(50);

  digitalWrite(MASTER_EN, LOW);      // Back to receive mode
  delay(20);                       // Wait before next send
}



Slave code:

#include <SoftwareSerial.h>
#include <Servo.h>

#define SLAVE_EN 8         // RS485 Enable Pin (DE/RE)

#define RS485_RX 10        // SoftwareSerial RX
#define RS485_TX 11        // SoftwareSerial TX

SoftwareSerial rs485(RS485_RX, RS485_TX); // RX, TX

//motor

int pwmA = 5;
int in1A = 3;
int in2A = 4;

int MotorSpeed = 0;

//rudder/servo

Servo rudder;
int RudderPos = 127;


void setup() {
  pinMode(SLAVE_EN, OUTPUT);
  pinMode(pwmA, OUTPUT);
  pinMode(in1A, OUTPUT);
  pinMode(in2A, OUTPUT);

  
  digitalWrite(SLAVE_EN, LOW);  // Set to receive mode

  rudder.attach(9);


  rs485.begin(9600);     // RS485 communication
  Serial.begin(9600);    // Optional: for debugging via Serial Monitor
  delay(10);             // 5ms minimum delay

}

void loop() {
  rs485.setTimeout(10);  // Set short timeout for string reads

  if (rs485.available()) {
    char firstChar = rs485.peek();

    // If data looks like ASCII letters, assume it's a string command
    if (isAlpha(firstChar)) {
      String incoming = rs485.readStringUntil('\n');



      if (incoming.startsWith("FWD")) {
  Serial.println("Received: Forward command");


  digitalWrite(in1A, HIGH);
  digitalWrite(in2A, LOW);
 

} else if (incoming.startsWith("BCK")) {
  Serial.println("Received: Backward command");


  digitalWrite(in1A, LOW);
  digitalWrite(in2A, HIGH);
  


} else if (incoming.startsWith("SPD:")) {
  MotorSpeed = incoming.substring(4).toInt(); // Extract numeric value
  Serial.print("MotorSpeed: ");
  Serial.println(MotorSpeed);
  //motor logic

  if(MotorSpeed < 8)MotorSpeed=0; //prevents buzzing at very low speed
  analogWrite(pwmA, MotorSpeed);
 



} else if (incoming.startsWith("RUD:")) {
  RudderPos = incoming.substring(4).toInt(); // Extract numeric value
  Serial.print("RudderPos: ");
  Serial.println(RudderPos);
  //rudder logic 

delay(15);  

 //Map 0-255 to 45-135 degrees (90° total range)
  int mappedRudder = map(RudderPos, 0, 255, 45, 135);

 rudder.write(mappedRudder);
 delay(20);   
  


} else {
  Serial.print("Unknown string: ");
  Serial.println(incoming);
}



    } 

    delay(10);
  }


}

Thank you.

Firstly, the ground of the servo supply and the ground of the Uno generating the servo position pulse must be directly tied. In my experience, even indirect connections can result in poor performance. Don't create dual ground paths (could lead to other noise problems), instead just ensure that what I have described is what you are doing.

Secondly, is the servo under any strain when holding any position? I.e. if it's position pulse is ceased, will it drift? Because one way to improve servo 'noise' is to detach() servos once they're repositioned. In your code, this would mean setting a new position, waiting for the servo to arrive(not necessarily using delay(), depends on the code I can't view), then detaching the servo. I do this all the time for my model railway turnout servos.

Aha, I see you did post code, my bad. I'll take a look.

Servo jitter is usually due to an inadequate power supply. Keep in mind that servos briefly draw the stall current (2.5 Amperes for the EFLR71000L), every time they start moving.

The buck converter may be the problem.

Add a large Cap over the servo power lines / google Glitch Busters

Hello, thanks for your input.

I don’t think this is a power issue, I’ve tested the servo using the same power setup, but with a simple sweep sketch, and it works without any jitter. Something interesting to note is that, while using the simple sweep sketch, the jitters started again once I plugged the master arduino back in after forgetting to re-upload my original slave code. Meaning it maybe has something to do with the way the signal is received / interpreted.

This points to changing power conditions - either current flowing via different paths, or some power component having too much current drawn.

I forgot that on the Arduino Uno, SoftwareSerial has a conflict with the servo library. Try the ServoTimer2 library, or use a more modern MCU with multiple hardware serial ports.

Hello,

I changed the servo library to ServoTimer2. I tried doing a sweep program with it and it works, but on my main project, with just this simple change, the servo now holds itself at it’s counter clockwise travel limit and has irregular spams. These spasms occurs irregularly and never more than once per second. I am now even more confused haha.

Thank you.

Hello,

I have a 1000uF electrolytic capacitor that I scavenged from a old printer. If I understand correctly, I just need to connect it in parallel with my power wire and ground wire?

Thank you.

Did you allow for the fact that the ServoTimer2 write() function takes the pulse width as its parameter and not the required servo angle

That is a problem with the code you forgot to post and will damage the servo.

With a new library, study the documentation (see post #10) and run an applicable library example to make sure that it is working correctly.

Alright, I took the time to properly learn how to use the Servotimer2 library. There has been an improvement of the servo’s behavior, but every 3 to 5 seconds, there is a spasm. For my current purposes, I can let it slide if no solution can be found. The servo follows commands smoothly and accurately (if we ignore the spasms/jitter). If the jitter can be fixed I would like to fix it right now though. Here’s the updated sketch:

#include <SoftwareSerial.h>
#include <ServoTimer2.h>

#define SLAVE_EN 8         // RS485 Enable Pin (DE/RE)

#define RS485_RX 10        // SoftwareSerial RX
#define RS485_TX 11        // SoftwareSerial TX

SoftwareSerial rs485(RS485_RX, RS485_TX); // RX, TX

//motor

int pwmA = 5;
int in1A = 3;
int in2A = 4;

int MotorSpeed = 0;

//rudder/servo

ServoTimer2 rudder;
int RudderPos = 127;


void setup() {
  pinMode(SLAVE_EN, OUTPUT);
  pinMode(pwmA, OUTPUT);
  pinMode(in1A, OUTPUT);
  pinMode(in2A, OUTPUT);

  
  digitalWrite(SLAVE_EN, LOW);  // Set to receive mode

  rudder.attach(6);


  rs485.begin(9600);     // RS485 communication
  Serial.begin(9600);    // Optional: for debugging via Serial Monitor
  delay(10);             // 5ms minimum delay

}

void loop() {
  rs485.setTimeout(10);  // Set short timeout for string reads

  if (rs485.available()) {
    char firstChar = rs485.peek();

    // If data looks like ASCII letters, assume it's a string command
    if (isAlpha(firstChar)) {
      String incoming = rs485.readStringUntil('\n');



      if (incoming.startsWith("FWD")) {
  Serial.println("Received: Forward command");
  // Add forward motor logic here

  digitalWrite(in1A, HIGH);
  digitalWrite(in2A, LOW);
 

} else if (incoming.startsWith("BCK")) {
  Serial.println("Received: Backward command");
  // Add backward motor logic here

  digitalWrite(in1A, LOW);
  digitalWrite(in2A, HIGH);
  


} else if (incoming.startsWith("SPD:")) {
  MotorSpeed = incoming.substring(4).toInt(); // Extract numeric value
  Serial.print("MotorSpeed: ");
  Serial.println(MotorSpeed);
  //motor logic

  if(MotorSpeed < 8)MotorSpeed=0; //prevents buzzing at very low speed
  analogWrite(pwmA, MotorSpeed);
 



} else if (incoming.startsWith("RUD:")) {
  RudderPos = incoming.substring(4).toInt(); // Extract numeric value
  Serial.print("RudderPos: ");
  Serial.println(RudderPos);

  // Map 0–255 to 45–135 degrees
  int mappedRudder = map(RudderPos, 0, 255, 1000, 2000);
mappedRudder = constrain(mappedRudder, 1000, 2000);

  static int lastRudder = -1;
  const int rudderThreshold = 2; // Only update if change is ≥2 degrees

  if (abs(mappedRudder - lastRudder) >= rudderThreshold) {
    rudder.write(mappedRudder);
    lastRudder = mappedRudder;
  }
 

} else {
  Serial.print("Unknown string: ");
  Serial.println(incoming);
}




    } 

    delay(10);  //delay for pacing
  }


}

again, thank you for your support and patience

Instead of printing the received rudder value, print the value you're writing to Servo. You might be surprised.

Also, increase your Serial baud rate to 115,200. You have a risk of causing a delay with transmission-related serial backlog, the way you're doing this. Upping the rate avoids that.

It is a good idea to test the servo alone, and make sure it is working correctly, before adding any other tasks to the code.

Hi, @kingofthehorses

Have you still got the servo using the same 5V supply as the UNO?

Can you post some images of your project?
So we can see your component layout.

Thanks... Tom.... :smiley: :+1: :coffee: :australia: