Problem controlling 20 servo with VarSpeedServo

Hello all! Im using the VarSpeedServo library to control servos for my robot. I say robot but its actually a life size T-800 terminator lol. I love this library because i can control the speed of the servos. The issue im having is that this library only seems to control up to 12 servos on one timer. I have 20 servos in all that i need to control. From what ive read, the library says to add another timer in order to control more servos. So i included the Timer.h library found here https://github.com/JChristensen/Timer/archive/master.zip
But its still not controlling more than 12 servos. I've attached the VarSpeedServo zip.

Here is my code. You will see that i have commented out a bunch of servos in order to test out whether or not the number of servos mattered. Has any one had this issue before using this library? how would i add another timer?
VarSpeedServo.zip (6.2 KB)

#include <timer.h>
#include <VarSpeedServo.h>
#include <EEPROM.h>

String readData;

//attach servos
//VarSpeedServo eyesLeftRight;
//VarSpeedServo eyesUpDown;
VarSpeedServo headLeftRight;
VarSpeedServo headUpDown;
VarSpeedServo torso;
VarSpeedServo leftShoulder;
VarSpeedServo leftArm;
VarSpeedServo leftForearm;
VarSpeedServo leftHand;
//VarSpeedServo leftHip;
//VarSpeedServo leftKnee;
//VarSpeedServo leftCalf;
VarSpeedServo rightShoulder;
VarSpeedServo rightArm;
VarSpeedServo rightForearm;
VarSpeedServo rightHand;
//VarSpeedServo rightKnee;
//VarSpeedServo rightHip;
//VarSpeedServo rightCalf;

//Define Servo tolerances
int eyesPositionLeftRight = 40; //right 80 Left = 2 
int eyesPositionUpDown = 40; //down max = 10 up max 80
int centerHead = 40; // Right = 90 - Left = 0
int neckCentered = 90; //70 -- 90 --  120
int torsoCentered = 35;//0 -- 35 -- 65
/**head up down positions
 * down = 70
 * center = 50
 * up = 30
 * 
 */

/**head left right positions
 * right = 70
 * center = 42
 * left = 15
 * 
 */

/**EEPROM servo position addresses
Set servo locations**/

int eyesLeftRightAddr = 4;
int eyesUpDownAddr = 13;
int headLeftRightAddr = 6;
int headUpDownAddr = 3;
int leftShoulderAddr = A3;
int leftArmAddr = A4;
int leftForearmAddr = 1;
int leftHandAddr = A5;
int leftHipAddr = 0;
int leftKneeAddr = 8;
int leftCalfAddr = 10;
int torsoAddr = 2;
int rightShoulderAddr = A1;
int rightArmAddr = A2;
int rightForearmAddr = 7;
int rightHandAddr = A0;
int rightHipAddr = 9;
int rightKneeAddr = 11;
int rightCalfAddr = 12;

//Set min / max levels
int leftHipForward = 100;
int leftHipStraight = 105;
int leftHipBack = 110;

int leftKneeBack = 120;
int leftKneeStraight = 65;

int rightHipForward = 70;
int rightHipStraight = 65;
int rightHipBack = 60;

int rightKneeBack = 80;
int rightKneeStraight = 150;

int calfBack = 120;
int calfForward = 130;
int calfStraight = 125;
int calfSpeed;

int eyes_leds = 5;
bool eyes_on = false;
bool animateHead = false;

void setup() {
  
  // Start Serial Monitor                                                 
  Serial.begin(115200);
 
  pinMode(eyes_leds, OUTPUT);
  pinMode(A2, OUTPUT);

  //attach servos
//  eyesLeftRight.attach(4);
//  eyesUpDown.attach(13);
  headLeftRight.attach(6);
  headUpDown.attach(3);
  leftShoulder.attach(A3);
  leftArm.attach(A4);
  leftForearm.attach(1);
  leftHand.attach(A5);
//  leftHip.attach(0);
//  leftKnee.attach(8);
//  leftCalf.attach(10);
  torso.attach(2);
  rightShoulder.attach(A1);
  rightArm.attach(A2);
  rightForearm.attach(7);
  rightHand.attach(A0);
//  rightHip.attach(9);
//  rightKnee.attach(11);
//  rightCalf.attach(12);
  
  //write from memory
//  eyesLeftRight.write(eyesPositionLeftRight);
//  eyesUpDown.write(eyesPositionUpDown);
  headLeftRight.write(EEPROM.read(headLeftRightAddr));
  headUpDown.write(EEPROM.read(headUpDownAddr));
  leftHand.write(EEPROM.read(leftHandAddr));
//  leftHip.write(EEPROM.read(leftHipAddr));  
//  rightHip.write(EEPROM.read(rightHipAddr)); 
//  leftKnee.write(EEPROM.read(leftKneeAddr));
//  rightKnee.write(EEPROM.read(rightKneeAddr));
//  leftCalf.write(EEPROM.read(leftCalfAddr));
//  rightCalf.write(EEPROM.read(rightCalfAddr));
  leftForearm.write(EEPROM.read(leftForearmAddr));
  leftShoulder.write(EEPROM.read(leftShoulderAddr));
  leftArm.write(EEPROM.read(leftArmAddr));
  rightShoulder.write(EEPROM.read(rightShoulderAddr)); 
  rightArm.write(EEPROM.read(rightArmAddr));
  torso.write(EEPROM.read(torsoAddr));
}

//parse serial data
String parseData(String data, char separator, int index)
{
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==separator || i==maxIndex){
        found++;
        strIndex[0] = strIndex[1]+1;
        strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }

  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void eyesOn(){
 for(int i = 10; i < 255; i++){
    analogWrite(eyes_leds, i);
    delay(60);
  }
}

void eyesOff(){
 for(int i = 255; i >= 0; i--){
    analogWrite(eyes_leds, i);
    delay(10);
  }
}

void loop() {

  if(animateHead) 
  {
    int speed = 10;
    headLeftRight.slowmove(15, speed);//70 - 150
    EEPROM.write(headLeftRightAddr, 15);
    delay(5000);
    headUpDown.slowmove(40, speed);//30 - 70
    EEPROM.write(headUpDownAddr, 40);
    delay(4000);
    headUpDown.slowmove(60, speed);//30 - 70
    EEPROM.write(headUpDownAddr, 60);
    headLeftRight.slowmove(70, speed);//70 - 150
    EEPROM.write(headLeftRightAddr, 70);
    delay(3000);
    headUpDown.slowmove(40, speed);//30 - 70
    EEPROM.write(headUpDownAddr, 40);
    delay(5000);
    headUpDown.slowmove(60, speed);//30 - 70
    EEPROM.write(headUpDownAddr, 60);
    headLeftRight.slowmove(30, speed);//70 - 150
    EEPROM.write(headLeftRightAddr, 30);
    delay(15000);
  }
  while (Serial.available()>0) {
    readData = Serial.readStringUntil('\n');
    //Serial.println(readData);
    String device = parseData(readData,',',0);
    String pos = parseData(readData,',',1);
    String servoSpeed = parseData(readData,',',2);
//    Serial.println(device);
//   Serial.println(pos);
//   Serial.println(servoSpeed);

    /*********Head*********/
    if(readData == "eyesOn"){
      eyesOn();
    }
    if(readData == "eyesOff"){
      eyesOff();
    }
    if(readData == "animate"){
      eyesOn();
      animateHead = true;
    }
    if(readData == "stopanimate"){
      animateHead = false;
      eyesOff();
    }
    if(device == "headLeftRight") 
    {
      headLeftRight.slowmove(pos.toInt(), servoSpeed.toInt());//70 - 150
      EEPROM.write(headLeftRightAddr, pos.toInt());
    }
    if(device == "headUpDown") 
    {
      headUpDown.slowmove(pos.toInt(), servoSpeed.toInt());//30 - 70
      EEPROM.write(headUpDownAddr, pos.toInt());
    }
//    if(readData == "eyesLeftRight"){
//      Serial.println("eyes left right");
//      eyesLeftRight.slowmove(pos.toInt(), servoSpeed.toInt());
//      EEPROM.write(eyesLeftRightAddr, pos.toInt());
//    }

    /*********Arms / Shoulders*********/
    if(device == "ls") 
    {
      leftShoulder.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(leftShoulderAddr, pos.toInt());
    }
    if(device == "la") 
    {
      Serial.println("left arm");
      leftArm.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(leftArmAddr, pos.toInt());
    }
    if(device == "lfa")
    {
      Serial.println(device);
      leftForearm.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(leftForearmAddr, pos.toInt());
    }
    if(device == "rs") 
    {
      Serial.println(device);
      rightShoulder.write(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(rightShoulderAddr, pos.toInt());
    }
    if(device == "ra") 
    {
      Serial.println(device);
      Serial.println(pos.toInt());
      Serial.println(servoSpeed.toInt());
      rightArm.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(rightArmAddr, pos.toInt());
    }
    if(device == "rfa") 
    {
      rightForearm.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(rightForearmAddr, pos.toInt());
    }
    if(device == "rh") 
    {
      rightHand.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(rightHandAddr, pos.toInt());
    }
    
    if(device == "lh") 
    {
      leftHand.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(leftHandAddr, pos.toInt());
    }
    if(device == "lfa") 
    {
      leftForearm.slowmove(pos.toInt(), servoSpeed.toInt());//
      EEPROM.write(leftForearmAddr, pos.toInt());
    }
    /*********Legs / Torso*********/
    if(device == "torso"){
      Serial.println("torso");
      torso.slowmove(pos.toInt(), servoSpeed.toInt());
      EEPROM.write(torsoAddr, pos.toInt());
    }
  }
}

what I see here is

The VarSpeedServo.h Arduino library allows the use of up to 8 servos moving asynchronously (because it uses interrupts). In addition, you can set the speed of a move, optionally wait (block) until the servo move is complete, and create sequences of moves that run asynchronously.

EDIT: but I see in the .h

#define SERVOS_PER_TIMER       12     // the maximum number of servos controlled by one timer
#define MAX_SERVOS   (_Nbr_16timers  * SERVOS_PER_TIMER)

so... weird

which arduino are you running on?

so looking at your file, it says it's automatic

Timers are seized as needed in groups of 12 servos - 24 servos use two timers, 48 servos will use four.
The sequence used to sieze timers is defined in timers.h

==> your arduino needs to have enough 16 bit timers

Thanks for the reply! Im running an Arduino Uno. Using every pin lol. Yeah its weird, the library says that if over the max amount of servos that it will add another timer. I wasn't sure if i needed to programmatically add them or if its automatic. How many 16 bit timers does an Uno have?

then you are stuck... You have 3 timers but only one is 16 bits

the library knows what type of Arduino you are running on and adjusts the timer16_Sequence_t to match what's available. Your UNO is in the last case, with only timer1 available

// Say which 16 bit timers can be used and in what order
#if defined(__AVR_ATmega1280__)  || defined(__AVR_ATmega2560__)
#define _useTimer5
#define _useTimer1
#define _useTimer3
#define _useTimer4
typedef enum { _timer5, _timer1, _timer3, _timer4, _Nbr_16timers } timer16_Sequence_t ;

#elif defined(__AVR_ATmega32U4__)
#define _useTimer3
#define _useTimer1
typedef enum { _timer3, _timer1, _Nbr_16timers } timer16_Sequence_t ;

#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
#define _useTimer3
#define _useTimer1
typedef enum { _timer3, _timer1, _Nbr_16timers } timer16_Sequence_t ;

#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__)
#define _useTimer3
#define _useTimer1
typedef enum { _timer3, _timer1, _Nbr_16timers } timer16_Sequence_t ;

#else  // everything else
#define _useTimer1
typedef enum { _timer1, _Nbr_16timers } timer16_Sequence_t ;
#endif

➜ move to an Arduino MEGA and you'll get 4 timers

If you use a propeller-chip which is a eight core microcontroller you can have up to 32 servos controlled individually.

An Arduino Uno is pretty limited with internal hardware like timers

or you take a teensy 4.1
https://www.pjrc.com/store/teensy41.html

https://www.pjrc.com/teensy/td_libs_Servo.html

best regards Stefan

Thank you both for the reply's! So the MEGA has 4 16 bit timers? If so I'll move to that and see if it will work.

yes, Timers 1,3, 4 and 5 are 16 bits

awesome! thanks for the help! I'll let you know if it works

So i upgraded to the mega and i still have the same problem. some servos work and some don't. I've plugged the non working ones into a pin that does work and the servo moves, so i know the connection to the servo is fine. It's weird because I don't have 12 servos attached so it should work using one timer, right?

In coding it is always a good idea to post the complete code

Even in case your code has 10000 lines it is still a good idea to post the complete sketch

A. if your code is well organised in functions it will be easy for everybody to find that lines that are interesting because the self-explaining names of all the functions will guide her/him.
And additionally you can post where you assume the problem is.

B. if your code is not well organised in functions to giveyou the hint to do so.

best regards Stefan

Code would help. Also a clear drawing of your circuit and how everything is powered / driven

Can you also post a link to where you got the library from?

The code is posted in the very first post, plus the link to the library. The complete sketch. Scroll to the top. It's a pretty basic sketch.

I think I may know what the problem is. The servo I'm trying to control is on pin 1 which from what I read is not on a 16 bit timer. I'll try moving it to another pin today

very good idea as pin 0 and pin 1 are used for the serial connection to your PC

best regards Stefan

The question was more where did you find it. Usually there is more info or doc available in the GitHub

The link I provided is possibly an outdated library

There's a few repos for that library but mine I believe came from here https://github.com/netlabtoolkit/VarSpeedServo

Is there a way to assign a pin to a timer? Looks like in total, there are only 13 pins that use 16 bit timers for the mega. Is that correct?

Servos need a different signal than PWM. There is a fixed relation between PWM and the hardware-timers. But as servos need a signal that is different than PWM (pulse-width-modulation)
grafik

grafik

pretty short pulses of 1,0 to 2,0 milliseconds compared to how often these pulses occur
once every 20 milliseconds.

A code that creates Servo-signals can't use pure hardware-PWM. There must be involved software for the pausing between the short pulses. But I guess that there is still a relation between the internal hardwaretimer and the IO-pin. Though these details should be managed completely by the library. Except that you have to use IO-pins that are PWM-cabable.

best regards Stefan

Thats some great info Stefan! thank you for providing that.

Update: i switched from pin 1 to pin 23 and the servo is working, I'll check the rest of the servos today and see if they will work as well. I'll update this post when I'm done.

Just wanted to update you guys and let you know that the Mega board did indeed work for my needs. The mega will control all my servos (20). So far so good. The Uno was only able to run 11 with that library.