Servo motor MG996R precision accuracy at 5mm level

Hi all,
I am new bee to arduino and we are trying to use servo motor Mg996R to control.
The Arduino is connected to PCA9685 controller which is connected to servo motor.
we are able to send points using serial command and moving from one place to another.
we are using PCA9685 which we used from the following Github path
/GitHub - NachtRaveVL/PCA9685-Arduino: Arduino Library for the PCA9685 16-Channel PWM Driver Module.
The current issue we are facing is

  1. we need precision of 5 mm to be controlled from arduino which we are not able to achieve.
  2. Can we achieve smaller precision using MG996R?
    Kindly guide us how do we proceed to achieve this.
    regards
    Ravi
1 Like

Welcome to the forum

Please post your full sketch, using code tags when you do

Are you using write() or writeMicroseconds() to control the servo ?

Where exactly is the accuracy of 5mm needed ? Is it 20mm or 100mm from the centre of servo rotation, for instance ?

Since position is involved 5 mm post a rough mechanical sketch with an explanation.

Hi ,
Thanks for your reply.
below is the link where we have given the mechainical sketch

let us know your thought if we have to move 5mm how do we solve this
code is below here

#include <Wire.h>
#include "PCA9685.h" //https://github.com/NachtRaveVL/PCA9685-Arduino
#include <EEPROM.h>

PCA9685 driver;

#define SERVOMIN            102
#define SERVOMAX            470

#define MOTOR_ONE_PIN       0
#define MOTOR_TWO_PIN       1
#define MOTOR_THREE_PIN     2

#define MOTOR_ONE_ADDRESS   0
#define MOTOR_TWO_ADDRESS   2
#define MOTOR_THREE_ADDRESS 4

#define ROTATE_DELAY        50

#define LowByte(w) ((uint8_t) ((w) & 0xff))
#define HighByte(w) ((uint8_t) ((w) >> 8))

String serialCommand;
int serialRecv, MOTOR_ONE_POS, MOTOR_TWO_POS, MOTOR_THREE_POS;


// PCA9685 outputs = 12-bit = 4096 steps
// 2.5% of 20ms = 0.5ms ; 12.5% of 20ms = 2.5ms
// 2.5% of 4096 = 102 steps; 12.5% of 4096 = 512 steps
PCA9685_ServoEvaluator pwmServo(SERVOMIN, SERVOMAX); // (-90deg, +90deg)

void update_pos(int ADDR, int data) {
  byte low = LowByte(data);
  byte high = HighByte(data);
  EEPROM.update(ADDR, low);
  EEPROM.update(ADDR + 1, high);
}
int get_pos(int ADDR) {
  byte lsb = EEPROM.read(ADDR);
  byte msb = EEPROM.read(ADDR + 1);
  return (msb << 8) | lsb;
}
void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps
  serialRecv = 0;
  Wire.begin();                 // Wire must be started first
  Wire.setClock(400000);        // Supported baud rates are 100kHz, 400kHz, and 1000kHz
  driver.resetDevices();        // Software resets all PCA9685 devices on Wire line

  driver.init(B000000);         // Address pins A5-A0 set to B000000
  driver.setPWMFrequency(50);   // Set/ frequency to 50Hz

  MOTOR_ONE_POS = get_pos(MOTOR_ONE_ADDRESS);
  if (SERVOMIN > MOTOR_ONE_POS | MOTOR_ONE_POS > SERVOMAX) {
    MOTOR_ONE_POS = 300;
    update_pos(MOTOR_ONE_ADDRESS, MOTOR_ONE_POS);
  }
  driver.setChannelPWM(MOTOR_ONE_PIN, MOTOR_ONE_POS);

  MOTOR_TWO_POS = get_pos(MOTOR_TWO_ADDRESS);
  if (SERVOMIN > MOTOR_TWO_POS | MOTOR_TWO_POS > SERVOMAX) {
    MOTOR_TWO_POS = 300;
    update_pos(MOTOR_TWO_ADDRESS, MOTOR_TWO_POS);
  }
  driver.setChannelPWM(MOTOR_TWO_PIN, MOTOR_TWO_POS);

  MOTOR_THREE_POS = get_pos(MOTOR_THREE_ADDRESS);
  if (SERVOMIN > MOTOR_THREE_POS | MOTOR_THREE_POS > SERVOMAX) {
    MOTOR_THREE_POS = 300;
    update_pos(MOTOR_THREE_ADDRESS, MOTOR_THREE_POS);
  }
  driver.setChannelPWM(MOTOR_THREE_PIN, MOTOR_THREE_POS);
  help();


}
void loop() {
  byte ch;
  if (Serial.available()) {
    ch = Serial.read();
    serialCommand += (char)ch;
    if (ch == '\r') { // Command recevied and ready.
      serialCommand.trim();
      serialRecv = 1;
    }
  }
  if (serialRecv == 1) {
    command_controller(serialCommand);
    serialRecv = 0;
    serialCommand = "";

  }
}

void help() {
  Serial.println("Servo Motor 1:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_ONE_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_ONE_POS);
  Serial.println();

  Serial.println("Servo Motor 2:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_TWO_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_TWO_POS);
  Serial.println();

  Serial.println("Servo Motor 3:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_THREE_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_THREE_POS);
  Serial.println();
}

int move_to(int pin, int pos, int current_pos) {
  if ((SERVOMIN <= current_pos <= SERVOMAX) && (SERVOMIN <= pos <= SERVOMAX)) {
    if (pos > current_pos && pos <= SERVOMAX) {
      for (int p = current_pos ; p <= pos ; p++) {
        driver.setChannelPWM(pin, p);
        Serial.println((String)"pos " + p);
        delay(ROTATE_DELAY);
      }
    } else if (pos < current_pos && pos >= SERVOMIN) {
      for (int p = current_pos ; p >= pos ; p--) {
        driver.setChannelPWM(pin, p);
        Serial.println((String)"pos " + p);
        delay(ROTATE_DELAY);
      }
    }
    int temp_pos = driver.getChannelPWM(pin);
    Serial.println((String)"servo motor moved to " + temp_pos);
    return temp_pos;
  } else {
    return current_pos;
  }

}

void command_controller(String command) {
  int start = 0;
  int end_ind = 0;
  bool flag = true;

  if (command == "help") {
    help();
  } else if (command == "M1?") {
    Serial.println(driver.getChannelPWM(MOTOR_ONE_PIN));
  } else if (command == "M2?") {
    Serial.println(driver.getChannelPWM(MOTOR_TWO_PIN));
  } else if (command == "M3?") {
    Serial.println(driver.getChannelPWM(MOTOR_THREE_PIN));
  } else {
    end_ind = command.indexOf(' ');
    String user_motor = command.substring(start, end_ind);
    start = end_ind + 1;
    end_ind = command.length();
    String user_deg = command.substring(start, end_ind);
    if (user_motor == "M1") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_ONE_PIN,MOTOR_ONE_POS,EEPROM.read(MOTOR_ONE_ADDRESS));
      MOTOR_ONE_POS = get_pos(MOTOR_ONE_ADDRESS);
      MOTOR_ONE_POS = move_to(MOTOR_ONE_PIN, DEST_POS, MOTOR_ONE_POS);
      update_pos(MOTOR_ONE_ADDRESS, MOTOR_ONE_POS);

    } else if (user_motor == "M2") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_TWO_PIN,MOTOR_TWO_POS,EEPROM.read(MOTOR_TWO_ADDRESS));
      MOTOR_TWO_POS = get_pos(MOTOR_TWO_ADDRESS);
      MOTOR_TWO_POS = move_to(MOTOR_TWO_PIN, DEST_POS, MOTOR_TWO_POS);
      update_pos(MOTOR_TWO_ADDRESS, MOTOR_TWO_POS);
    } else if (user_motor == "M3") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_THREE_PIN,MOTOR_THREE_POS,EEPROM.read(MOTOR_THREE_ADDRESS));
      MOTOR_THREE_POS = get_pos(MOTOR_THREE_ADDRESS);
      MOTOR_THREE_POS = move_to(MOTOR_THREE_PIN, DEST_POS, MOTOR_THREE_POS);
      update_pos(MOTOR_THREE_ADDRESS, MOTOR_THREE_POS);
    } else {
      Serial.println("invalid command");
    }
  }


}

Hi,
I have attached the code here

#include <Wire.h>
#include "PCA9685.h" //https://github.com/NachtRaveVL/PCA9685-Arduino
#include <EEPROM.h>

PCA9685 driver;

#define SERVOMIN            102
#define SERVOMAX            470

#define MOTOR_ONE_PIN       0
#define MOTOR_TWO_PIN       1
#define MOTOR_THREE_PIN     2

#define MOTOR_ONE_ADDRESS   0
#define MOTOR_TWO_ADDRESS   2
#define MOTOR_THREE_ADDRESS 4

#define ROTATE_DELAY        50

#define LowByte(w) ((uint8_t) ((w) & 0xff))
#define HighByte(w) ((uint8_t) ((w) >> 8))

String serialCommand;
int serialRecv, MOTOR_ONE_POS, MOTOR_TWO_POS, MOTOR_THREE_POS;


// PCA9685 outputs = 12-bit = 4096 steps
// 2.5% of 20ms = 0.5ms ; 12.5% of 20ms = 2.5ms
// 2.5% of 4096 = 102 steps; 12.5% of 4096 = 512 steps
PCA9685_ServoEvaluator pwmServo(SERVOMIN, SERVOMAX); // (-90deg, +90deg)

void update_pos(int ADDR, int data) {
  byte low = LowByte(data);
  byte high = HighByte(data);
  EEPROM.update(ADDR, low);
  EEPROM.update(ADDR + 1, high);
}
int get_pos(int ADDR) {
  byte lsb = EEPROM.read(ADDR);
  byte msb = EEPROM.read(ADDR + 1);
  return (msb << 8) | lsb;
}
void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps
  serialRecv = 0;
  Wire.begin();                 // Wire must be started first
  Wire.setClock(400000);        // Supported baud rates are 100kHz, 400kHz, and 1000kHz
  driver.resetDevices();        // Software resets all PCA9685 devices on Wire line

  driver.init(B000000);         // Address pins A5-A0 set to B000000
  driver.setPWMFrequency(50);   // Set/ frequency to 50Hz

  MOTOR_ONE_POS = get_pos(MOTOR_ONE_ADDRESS);
  if (SERVOMIN > MOTOR_ONE_POS | MOTOR_ONE_POS > SERVOMAX) {
    MOTOR_ONE_POS = 300;
    update_pos(MOTOR_ONE_ADDRESS, MOTOR_ONE_POS);
  }
  driver.setChannelPWM(MOTOR_ONE_PIN, MOTOR_ONE_POS);

  MOTOR_TWO_POS = get_pos(MOTOR_TWO_ADDRESS);
  if (SERVOMIN > MOTOR_TWO_POS | MOTOR_TWO_POS > SERVOMAX) {
    MOTOR_TWO_POS = 300;
    update_pos(MOTOR_TWO_ADDRESS, MOTOR_TWO_POS);
  }
  driver.setChannelPWM(MOTOR_TWO_PIN, MOTOR_TWO_POS);

  MOTOR_THREE_POS = get_pos(MOTOR_THREE_ADDRESS);
  if (SERVOMIN > MOTOR_THREE_POS | MOTOR_THREE_POS > SERVOMAX) {
    MOTOR_THREE_POS = 300;
    update_pos(MOTOR_THREE_ADDRESS, MOTOR_THREE_POS);
  }
  driver.setChannelPWM(MOTOR_THREE_PIN, MOTOR_THREE_POS);
  help();


}
void loop() {
  byte ch;
  if (Serial.available()) {
    ch = Serial.read();
    serialCommand += (char)ch;
    if (ch == '\r') { // Command recevied and ready.
      serialCommand.trim();
      serialRecv = 1;
    }
  }
  if (serialRecv == 1) {
    command_controller(serialCommand);
    serialRecv = 0;
    serialCommand = "";

  }
}

void help() {
  Serial.println("Servo Motor 1:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_ONE_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_ONE_POS);
  Serial.println();

  Serial.println("Servo Motor 2:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_TWO_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_TWO_POS);
  Serial.println();

  Serial.println("Servo Motor 3:");
  Serial.println((String)"\tPCA9685 Channel : " + MOTOR_THREE_PIN);
  Serial.println((String)"\tMin pwm         : " + SERVOMIN);
  Serial.println((String)"\tMax pwm         : " + SERVOMAX);
  Serial.println((String)"\tCurrent pwm     : " + MOTOR_THREE_POS);
  Serial.println();
}

int move_to(int pin, int pos, int current_pos) {
  if ((SERVOMIN <= current_pos <= SERVOMAX) && (SERVOMIN <= pos <= SERVOMAX)) {
    if (pos > current_pos && pos <= SERVOMAX) {
      for (int p = current_pos ; p <= pos ; p++) {
        driver.setChannelPWM(pin, p);
        Serial.println((String)"pos " + p);
        delay(ROTATE_DELAY);
      }
    } else if (pos < current_pos && pos >= SERVOMIN) {
      for (int p = current_pos ; p >= pos ; p--) {
        driver.setChannelPWM(pin, p);
        Serial.println((String)"pos " + p);
        delay(ROTATE_DELAY);
      }
    }
    int temp_pos = driver.getChannelPWM(pin);
    Serial.println((String)"servo motor moved to " + temp_pos);
    return temp_pos;
  } else {
    return current_pos;
  }

}

void command_controller(String command) {
  int start = 0;
  int end_ind = 0;
  bool flag = true;

  if (command == "help") {
    help();
  } else if (command == "M1?") {
    Serial.println(driver.getChannelPWM(MOTOR_ONE_PIN));
  } else if (command == "M2?") {
    Serial.println(driver.getChannelPWM(MOTOR_TWO_PIN));
  } else if (command == "M3?") {
    Serial.println(driver.getChannelPWM(MOTOR_THREE_PIN));
  } else {
    end_ind = command.indexOf(' ');
    String user_motor = command.substring(start, end_ind);
    start = end_ind + 1;
    end_ind = command.length();
    String user_deg = command.substring(start, end_ind);
    if (user_motor == "M1") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_ONE_PIN,MOTOR_ONE_POS,EEPROM.read(MOTOR_ONE_ADDRESS));
      MOTOR_ONE_POS = get_pos(MOTOR_ONE_ADDRESS);
      MOTOR_ONE_POS = move_to(MOTOR_ONE_PIN, DEST_POS, MOTOR_ONE_POS);
      update_pos(MOTOR_ONE_ADDRESS, MOTOR_ONE_POS);

    } else if (user_motor == "M2") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_TWO_PIN,MOTOR_TWO_POS,EEPROM.read(MOTOR_TWO_ADDRESS));
      MOTOR_TWO_POS = get_pos(MOTOR_TWO_ADDRESS);
      MOTOR_TWO_POS = move_to(MOTOR_TWO_PIN, DEST_POS, MOTOR_TWO_POS);
      update_pos(MOTOR_TWO_ADDRESS, MOTOR_TWO_POS);
    } else if (user_motor == "M3") {
      int DEST_POS = user_deg.toInt();
      //move_to_by_pin(MOTOR_THREE_PIN,MOTOR_THREE_POS,EEPROM.read(MOTOR_THREE_ADDRESS));
      MOTOR_THREE_POS = get_pos(MOTOR_THREE_ADDRESS);
      MOTOR_THREE_POS = move_to(MOTOR_THREE_PIN, DEST_POS, MOTOR_THREE_POS);
      update_pos(MOTOR_THREE_ADDRESS, MOTOR_THREE_POS);
    } else {
      Serial.println("invalid command");
    }
  }


}

This is the illustration from the document

I can make no sense of it all in relation to the question

I think what @sravich is getting at is that they want to know if they can position the servos' angles with sufficient precision so as to get the end of that arm to be positioned within 5mm side to side.

Hi,
sorry..we require 5mm step movement from the M1 pointer till end..so move on step by step..
thats the requirement.
regards
Ravi

Have you worked out how many degrees each servo needs to move through to achieve a 5mm movement ?

Hi,
thanks for your reply.we are not using degree based on servo.h..we are sending PWM signal which maps to certain rotation...so our example is as follows
when we give M1 = 100 , and when we try to move to 500 this moves..when we try to move from 100 to 105 or 110 ..for smaller PWM signal as given in the .ino file ..
move_to function....in that driver.setChannelPWM(pin, p); function ..let me know anything else you require

So you send commands to the servos, they move to certain positions, and the end of the arm moves to some point in space.

But what are you asking? Do you need someone to calculate the required servo positions so that your pointer goes where you want it?

Hi ,
In the move_to function I give as
curr_pos , des_pos
'if curr_pos = 100 and desti_pos = 120 my servo motor position moves and works fine
but when I change curr_pos = 100 and desti_pos=105/106/107/108 '
then we do not see any movement in the motor...we want the position to move 5mm each time we set..thats the problem definition...any change required in the code let us know..thank you very much for your replies.
--Ravi

Do you have to use the PCA9685 servo driver ? From previous questions in the forum its use only seems to confuse things, whereas the Servo library allows you to specify an angle to move the servo to

In your drawing the 5mm steps all appear to be in a straight line. If this is a requirement then you cannot move the end of a standard servo output arm in a straight line and at least 2 servos will need to move to achieve the straight line movement

For motion in a straight line, you can buy linear servos: https://www.amazon.com/linear-servo/s?k=linear+servo

Hi,
we already are working with 3 servo motors..as per my previous diagram I had shared

  1. M3 Base Motor which rotates the set up
  2. M2 Elbow servo motor
  3. M1 Final wrist servo motor which actually moves from one point to another point.
    ...when we send PWM signal this corresponds to M1,M2 and M3...so you are telling to use 2 motors at the place of M1 as per the image??...Let me know
    Thanks
    Ravi

I assume that the servos are normal rotational servos that move an arm through an arc. If that is the case then when you rotate any one servo the end of its arm will not move in a straight line, but through an arc.

You could move the arm through an arc such that the start and end positions are 5mm apart but it will not have moved in a straight line as in your drawing. Does that matter for your project ?

If the output must move in a straight line then you will need to move 2 servos at the same time such that the second one cancels the arcing motion of the first one

So, more details of the requirement are needed and I still can't make sense of your diagram. Where is the centre of rotation of each of the servos ? Do you have a photograph of the project ?

Hi ,
Thanks for your reply.
Below attached are the pics.
From the pic what we need is ,we need to move from power rectangle to power end as suggested in our previous diagram.
we are using PCA9685 servo driver and MG996R servo motor.
Is this a limitation in the driver itself??..which better servo motor should we choose??
Let us know you need anything else.
regards
Ravi

another pic

You don't actually need the driver board but can use it if you want to as long as you understand how to control the servos

Looking at your second picture you can see that moving just one servo cannot cause the head to move 5mm along the board

If I were you I would start by establishing what angle each of the servos needs to be at to position the head at the first position. However, this raises another question. Is the board always in the same position ?

Hi,
Thanks for the reply.
1.Here the angle of each servo is based on PWM signal which we use values such as M1(100),M2(200),M3(300)..something like this..which inturn converts into angle.
2.Yes the board will always be in same position..we are not moving its position.
Any suggestion from driver library where we need to change this??.
regards
Ravi