Servo motor accuracy

Hi,
I am using 12 motors to control 6 pan tilt mechanism:

with servo g9.
the pan tilt uses to adjust the position of 5 mirrors and one laser.
in my dreams it will create a few shapes in the space with smoke.
I created two codes, maybe later I will combine them.
one for building the cues and one for playing them.
the inspiration was this video:

and I used the same schematics which is pretty straight forward..
the main difference I made is:

  • connecting the power and ground of two servos which are on the same mirror
  • I extended all of the wires (common voltage, common ground, signal tilt, signal pan for every mirror - pan-tilt system) to 1 meter long.
  • I use 5v 10amp to power 12 sg9 which should be sufficient.

I will add videos and pictures later this week, my main question is:

How can i improve the reliability of the system?
and in more detail:
Sometimes when i change in one degree the servo motor doesn't respond, why?

my options (will be happy to hear your opinions):

  1. secure the pan-tilt heads better to the ground with weight and masking tape etc.
  2. maybe using the writeMicroseconds() instead of the pwm logic, and connect the servo motors to individual different pins on the esp32 (i heard this function is more accurate and provide a better resolution of control)
  3. giving up on the stand they stand on and do them fixed to the ground
  4. changing motors

the code:

  1. the code for building the cues:
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
// [62, 28, 110, 123, 35, 103, 30, 30, 127, 75, 50, 90, 0, 0, 0, 0]
Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40);

#define SERVOMIN  140 // minimum pulse length count (out of 4096)
#define SERVOMAX  590 // maximum pulse length count (out of 4096)

int servoAngles[17]; // Array to store angles for each servo and delay time

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  Serial.println("32 channel Servo test!");

  board1.begin();
  board1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  // Initialize all servo angles to zero
  for (int i = 0; i < 17; i++) {
    servoAngles[i] = 0;
  }
}

void loop() {
  digitalWrite(13, HIGH); 
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n'); // Read command from serial
    
    if (command.startsWith("print")) { // Check if command is "print"
      printServoAngles();
    } else if (command.startsWith("delay")) { // Check if command is "delay"
      int delayTime = command.substring(command.indexOf(' ') + 1).toInt();
      servoAngles[16] = delayTime;
    } else {
      Serial.print("received command.. moving...");
      // Parse servo number and angle
      int servoNumber = command.substring(0, command.indexOf(' ')).toInt();
      int angle = command.substring(command.indexOf(' ') + 1).toInt();

      if (angle >= 0 && angle <= 180 && servoNumber >= 0 && servoNumber < 16) {
        board1.setPWM(servoNumber, 0, angleToPulse(angle)); // Set servo position
        servoAngles[servoNumber] = angle; // Update stored angle
        Serial.print("Servo ");
        Serial.print(servoNumber);
        Serial.print(" set to angle ");
        Serial.println(angle);
      } else {
        Serial.println("Servo number or angle out of range");
      }
    }
  }
  // Wait for the servo to reach the desired position
  delay(500);
}

int angleToPulse(int ang) {
  int pulse = map(ang, 0, 180, SERVOMIN, SERVOMAX);
  return pulse;
}

void printServoAngles() {
  Serial.print("Servo angles array: [");
  for (int i = 0; i < 16; i++) {
    Serial.print(servoAngles[i]);
    if (i < 15) {
      Serial.print(", ");
    }
  }
  Serial.println("]");
  Serial.print("Delay time: ");
  Serial.println(servoAngles[16]);
}
  1. the code for playing the cues:
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40);

#define SERVOMIN  140 // minimum pulse length count (out of 4096)
#define SERVOMAX  590 // maximum pulse length count (out of 4096)

// Define servo positions
int servoAngles[] = {83, 60, 85,142, 45, 80, 0, 90, 115, 55, 58, 70, 0, 0, 0, 0,
                     95, 62, 3, 125, 100, 98, 90, 90, 114, 55, 90, 70, 0, 0, 0, 0};

int numServoAngles = sizeof(servoAngles) / sizeof(servoAngles[0]);

// Define array for moving times between queues
unsigned long movingTimes[] = {2000, 2000, 2000,1000}; // Example: 1s from 0 to 1, 1.5s from 1 to 2, 2s from 2 to 0

unsigned long moveStartTime;
unsigned long startAngle = 90;
unsigned long j = 0; // Global variable for servo position index
bool play = false;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  Serial.println("32 channel Servo test!");

  board1.begin();
  board1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  for (int k = 0; k < 16; k++) {
    board1.setPWM(k, 0, angleToPulse(startAngle));
  }
}

void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n'); // Read command from serial
    
    if (command.startsWith("play")) { // Check if command is "play"
      moveStartTime = millis(); // Record the start time
      digitalWrite(13, HIGH); 
      play = true;
    } else if (command.startsWith("stop")) { // Check if command is "stop"
      // Stop playing servo angles
      // You can add additional logic here if needed
      digitalWrite(13, LOW); 
      play = false;
    }
  }
  if (play) {
    playServoAngles(); // Update servo angles continuously
  }
}

int angleToPulse(int ang) {
  int pulse = map(ang, 0, 180, SERVOMIN, SERVOMAX);
  return pulse;
}

int pulseToAngle(int ang) {
  int pulse = map(ang, SERVOMIN, SERVOMAX, 0, 180);
  return pulse;
}

void playServoAngles() {
  unsigned long progress = millis() - moveStartTime; // Calculate progress

  if (j < numServoAngles / 16) {
    if (progress <= movingTimes[j]) {
      for (int k = 0; k < 16; k++) {
        // Get current and next angle for the servo
        int currentAngle = angleToPulse(servoAngles[k + j * 16]);
        int nextAngle;
        if (j + 1 < numServoAngles / 16) {
          nextAngle = angleToPulse(servoAngles[k + (j + 1) * 16]);
        } else {
          nextAngle = angleToPulse(servoAngles[k]);
        }
        if(k==0 or k==1){
        Serial.print("the moving time is: ");
        Serial.println(movingTimes[j]);
        Serial.print("and i am going from: ");
        Serial.print(pulseToAngle(currentAngle));
        Serial.print(" to: ");
        Serial.println(nextAngle);
        }
        // Calculate the angle based on progress
        long angle = map(progress, 0, movingTimes[j], currentAngle, nextAngle);
        
        // Set servo position
        board1.setPWM(k, 0, angle);
      }
    } else {
      j++;
      moveStartTime = millis(); // Reset move start time for next position
    }
  } else {
    j = 0; // Reset j to start from the first position
  }
}

By sometimes do you mean that first you go 1 degree in one direction and then 1 drgree back in the other direction?

no, I mean going to 67 and then to 68
thanks for helping me make that clear

That could be a problem but we need to see your set-up

Just my experience:
yes, it is not always changing (on small changes in degree).
Think about it: there is some electronic hardware in the servo, it is not perfect!
It uses a potentiometer that changes with the angle. That potentiometer has a tolerance/inaccuracy and it is used to generate the power for your motor. You will not get it perfect! If you want exact angles, you need to use a stepper motor!

Some of the cheap SG90 servos have a deadband of up to 10us, which means you lucky if you can get it to move in 1 degree increments even without a load
The Tower Pro SG90 has a deadband of 1us, so you will get precise movements without skipped movements.

FYI: 1 degree = 1000us/180 = 5.56us

Hi can you please verify or send a link for the product you talk about?
In my google search I found this:

in the original tower pro website i found different information but not click to purchase.
this is the servo motors i used:
https://www.amazon.com/dp/B07L2SF3R4?ref=ppx_yo2ov_dt_b_product_details&th=1
which provide:

  • Dead band width: 5 μsec

for the circuit diagram and the pictures i am sorry for the latency but all of the objects and circuts at my school of arts.. so Sunday will be the earliest i could provide those.
I also suspected my engines are not sufficient for this purpose, is that the go to solution? or is there a software solution i could use?
Thank you for your help.
Stepper motor is also an option? will that provide better accuracy?
if so every stepper motor needs 4 pins, which means 4*12 = 48 which is bigger than the esp32 34 GPIO pins.. If I go in that direction i will need two microcontrollers synched?

In one place it says 5ms in another it says 5us
The title says SG90 but the picture shows S51
Then it says: Operating trave: 180°±1° (500→2500 μsec). Something wrong there!

You get what you pay for. $1.87 each is cheap

FYI, Difference between Analog and Digital

okay all very intersting and i didn't know about the significant difference in quality between the two.
Thought about going with these:

as they are in a reasonable price for a student and from a reliable website

didn't find a way to purchase from their website :frowning:

SG90 vs MG90

There are MG90 servos (metal gear).

≈ 10 for $40.00

didn't find a MG90 with a smaller dead band width than 5us, found at tower pro but still trying to figure what how to purchase form them.
Thanks a lot for the help!

  • You won’t, just pointing out metal vs plastic gears.

Let's take a step back here. You won't be doing that with servos, especially those cheap ones.

I managed to create one cue or two, but playing them repeatedly was definitely hard. (we talk about small mirrors in a shape of a square, something around 5cm*5cm - will measure and take pictures and videos on Sunday, and doing another test with weight on the base on Wednesday)
Don't mind upgrading for the cause of a good art project.
Wanted to advice and consult before purchasing..
Stepper motor is a better alternative? if not servo motors then what?

Check this out

okay that is amazing but way beyond what I intended..
I meant for just the laser hit on mirror and that mirror will reflect on another and so on..
and after that adjust the laser to hit another mirror and the the mirror will hit another mirror..
I don't intended to create shapes via vibrations just by combinations, i did this as a performer by hand with selfie sticks and it came what to look like this:

OK I see.