Saturday, January 28, 2012

Can I Control More Than X Servos With An Arduino ?

Yes, Yes, Yes, infact 'Yes' Twelve Times.

The Servo library for the Arduino UNO allows the control of upto 12 Servos on a single Arduino UNO. As the electronic speed controllers or ESC's used in remote controlled cars also use the same type of signal, you can control any combination of servos and speed controllers up to a total of 12 devices.

If you really need more devices you can -

1) Interface to an external servo controller - these are generally additional micro controllers but programmed purely to take servo related instructions from another source and manage the corresponding bank of servos.

2) Interface to another Arduino - this is the same as option 1) above but gives you some additional flexibility for instance you can share the collection and processing of sensor data across the two devices without having to learn a new micro controller.

3) Use one of the larger Arduinos - always an option, but when you can build a basic Arduino for less than 10 dollars option 2 looks hard to beat.

How does it work ?

If you have read the previous posts on interfacing to an RC Receiver you will understand that a servo signal is not at all what you might have been expecting. If you haven't read those posts, here they are -

How To Read An RC Receiver With A Microcontroller - Part 1

How To Read An RC Receiver With A Microcontroller - Part 2

So now that we understand that a servo expects a pulse of between 1 and 2 milliseconds to be sent about every 20 milliseconds, how does the Arduino control up to 12 servos all expecting these signals 50 times a second ?


Fortunately someone by the name of Michael Margolis has created the Arduino Servo library so that all we have to do is call two functions -

Arduino Servo Library


The Main Servo Library Functions -

1)  aServo.attach(pin_number); - This tells the library that there is a servo attached to pin number 'pin_number'. When the library attaches it will initially begin pulsing the servo with 1500us, this is the centered position for a servo, which is also the neutral position for a (RC Car) electronic speed controller.

2) aServo.writeMicroseconds(uS) - This tells the library to update the pulse width for your servo. There is another version 'write' which takes an angle instead of a microseconds argument. This version converts the angle to micro seconds and then calls writeMicroseconds, so if you do not need angles in your application, don't use them.

And now for an example, forget sweep, try 'multi sweep' -

Quick warning - Don't connect 12 Servos to the Arduino power pins, use external power or to just test the code using a single servo, connecting its signal pin to each of the arduino digital pins 2-13 in turn.

// Multi Sweep, Duane B
// Using the servo library created by Michael Margolis 
// to control 12 Servos with one Arduino Uno

#include <Servo.h>
// Sample sketch for driving 12 Servos from an Arduino UNO, servos are attached to digital pins 2,3,4,5,6,7,8,9,10,11,12,13

#define CONNECTED_SERVOS 12

// macro just adds two - the first servo is attached to digital pin 2, this gives us upto 12 servos - digital 2 to 13
#define SERVO_TO_PIN(x) (x+2)

Servo myServos[CONNECTED_SERVOS];

#define COUNT_DOWN -1
#define COUNT_UP +1
#define INCREMENT 10 // move in steps of 10 milliseconds

int nPulseWidth = DEFAULT_PULSE_WIDTH ; // 1500, defined in servo.h
int nDirection = COUNT_UP;

volatile unsigned long ulStart = 0;
volatile unsigned long ulStartToEnd = 0;

void setup()
{
  // attach the servos
  for(int nServo = 0;nServo < CONNECTED_SERVOS;nServo++)
  {
    myServos[nServo].attach(SERVO_TO_PIN(nServo));
  }
 
  // the library sets all servos to 1500 ms pulse width by default, this is center for a steering servo
  Serial.begin(9600);
  Serial.println("Completed setup"); 
}

void loop()
{
  delay(10);  // give the servos time to move after each update
 
  if(ulStartToEnd)
  {
    Serial.println(ulStartToEnd);
    ulStartToEnd = 0;
  }
 
  nPulseWidth += nDirection * INCREMENT;
 
  if(nPulseWidth >= 2000)
  {
    nPulseWidth = 2000;
    nDirection = COUNT_DOWN;
  }
 
  if(nPulseWidth <= 1000)
  {
    nPulseWidth = 1000;
    nDirection = COUNT_UP;
  }
 
  for(int nServo = 0;nServo < CONNECTED_SERVOS;nServo++)
  {
    myServos[nServo].writeMicroseconds(nPulseWidth);
  }
}

Great, so that works, but how and what does it cost us ?

The library makes extensive use of the 16 bit timer/counter, this has a cost to us. By taking over the timer, the library prevents us using analogWrite on digital pins 9 and 10, however on the up side we can control upto 12 Servos and there are still pins 3,5,6 and 11 to use with analogWrite. 

So how does the library manage to control 12 servos with a single timer ? is it a production quality approach or just a proof of concept ?

Note : Moderate simplification used to aid understanding and descriptive clarity below.

The library attaches an interrupt function to the timer which is called whenever the timer value matches its output compare register value. Think of this like the red hand on an alarm clock, when the hour hand matches the red hand, the alarm goes off. When the timer matches the output compare value, the interrupt function gets called.

In addition to this interrupt function, the library maintains an array of servo objects. Each servo object has two main fields, the pin it is attached to and the pulse width. Each time the interrupt function is called, it uses an array index to find the current servo object, then sets its pin LOW.

Next the array index is incremented to point at the next servo object, the code immediately sets this servos pin HIGH. These few lines of code represent the end of the pulse for the first servo and the start of the pulse on the next servo.

Next the function adds together the current timer value and the pulse width required for the current servo, this value is then put into the output compare register. This has the effect of ensuring that the interrupt will be called again in servo.pulsewidth microseconds from now.

This code has an effect a bit like the snooze button on the alarm, its basically saying, can you get back to me in a short while. In our case the short while is whatever the current servos pulsewidth is set to.

Note - This allows your code to carry on doing whatever it needs to, the timers and interrupts will work in the background to keep your servos where you want them.
 
At this point, however many microseconds later, the cycle starts again, the interrupt is called, the current pin it taken low, the next pin is set high, the pulse width of the new pin is added to the current time and then set in the timers output compare register to start the cycle again and so it goes on.

The table below provides a vastly simplified view of what happens each time the interrupt function is called -

Three servos 1,2,3 with pulsewidths of 1000,1500 and 2000

TIMER Servo 1 Servo 2 Servo 3 New value to set in output compare register OCRA1
0   HIGH LOW LOW current time 0 + pulse width 1000 1000
1000   LOW HIGH LOW current time 1000 + pulse width 2500 2500
2500   LOW LOW HIGH current time 2500 + pulse width 2000 4500

 
As far as I am concerned this is both elegant and simple as all the best code should be. Thanks to Michael Margolis for creating the library.

I hope this post will help clear some of the confusion around Arduino and mulitple servos using this great library.


In a future post I plan to cover servo trouble shooting.

Duane B

Stay Tuned ... and in the meantime let me know if you have found any of this useful.

38 comments:

  1. definitely interesting! think i can use it later on...
    i am looking forward to that servo trouble shooting-post. if there is time, let me know if you want to help me on getting my servo started, as you said!?
    http://arduino.cc/forum/index.php/topic,89047.15.html
    i would appreciate this!
    thanks
    flo

    ReplyDelete
  2. Hi,
    Can you post an update on the current status of your issue on arduino.cc and I will have a look.

    Duane

    ReplyDelete
  3. Hi,
    I wonder if there is any other libraries I can use to achieve the same thing.
    Unfortunately, I think the Arduino standard library for Servo conflicts with Virtual Wire, which I have to use to do the RC part.
    Can I somehow modify the library? or is there another library I can use?

    Thank you very much

    ReplyDelete
    Replies
    1. Hi,
      VirtualWire uses timer1 which is the same hardware timer that all of the servo libraries use. Which Arduino are you using ? The Mega has more 16 bit timers and so it should be simple to change one of the libraries to use an alternative timer, if you are using an UNO, there is only one 16 bit timer so you do not have this option. I believe that there is a Timer2 Servo library which will solve your problem on the UNO, if I can find it I will post a link here.
      Duane B

      Delete
  4. Here is it, I haven't used it but it looks like a workable solution for using servos with libraries which also use Timer1

    http://arduino.cc/forum/index.php/topic,21975.0.html

    Duane B

    ReplyDelete
  5. Can you control each servo separately with this method?

    ReplyDelete
  6. Hi,
    Yes, you can control servos individually with any of the Arduino servo libraries. The standard library supports upto 12 independent servos, your only problem will be supplying them with enough power. the Arduino is a controller, not a power supply, so ypu will need batteries or an alternative power supply for the servos.

    Duane B

    ReplyDelete
  7. Thank you for the quick reply!

    Would it be possible to see some sample code of the servos being controlled individually within the sample code you provided?

    Also would it be possible to use two Arduino's as one to double the number of servos controlled? In other words have communication between the two?

    ReplyDelete
  8. The servos are being controlled individually, i am just individually telling them all to do the same thing.

    For some variety, you could try -

        myServos[nServo].writeMicroseconds(1000 + (nPulseWidth-1000)/nServo);

    This will drive the first servo through the full range, the second through half range and so on.

    There are lots of ways to drive large numbers of servos with Arduino, how many servos do you need ? What are you building ?

    Duane B

    ReplyDelete
  9. Hey Thanks again!

    I'm building a mini humanoid style robot similar to a robonova with grasping hands and a moving head and whatever else I manage to cram in its small body... I'm looking at starting with around 16 servos but I would like room to expand for any future additions I dream up.
    The reason I was thinking of using multiple Arduinos is because I happen to have a few Arduino bootloaded Atmega328 chips laying around and if I knew how to in someway make them act more as one it would save me from buying a servo controller board.
    Also, I plan to use Xbee modules to provide RC functionality as well as remotely triggered autonomous action and I need a few extra pins for those as well.

    Drew

    ReplyDelete
  10. Hello

    Please am trying to build a robotic arm for my project. I would like to use an arduino uno board to control the movement of the arm. How can I use the servo library to control three separate servos at different angles at the same time with maybe a slight delay so that the servos could move at different intervals. Thanks a lot

    ReplyDelete
  11. You can build this easily with an Arduino UNO, the quickest and easiest way is to start building and learn as you go.

    Duane B

    ReplyDelete
  12. Hi all,
    I want to use 24 servos for my project, I was wondering... anyone used it before and how to control with which board? am using Arduino Leonardo board...

    Suggestion please...

    Thanks
    Ryan

    ReplyDelete
  13. Hi,
    Your Leonardo is not going to have enough pins to drive 24 servos, you might look at the Arduino Mega that supports upto 48 and has the additional memory you will probably need to program for so many servos.

    If you can get away with 20 servos you can try this approach which uses just 4 pins -

    http://rcarduino.blogspot.ae/2012/10/arduino-serial-servos-20-servos-4-pins.html

    Duane B

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Hi,

    Thank you for posting this, I wish I had found it sooner.

    I have a question about power the unit. I have a 5V 800mA wall adapter. Will this work to power 8 micro servos? Would it work if I made the project smaller and powered only 4 micro servos?

    Thank you

    ReplyDelete
  16. Hi
    My project is a quadcopter with "yellow Siagel" esc´s, Arduino Uno and Spektrum Dx6i. Can I also use the servo library?
    Thanks

    ReplyDelete
  17. hey,

    I have built a robotic hand using components made from a 3-D printer. I have actuated the (5) fingers & wrist using servo motors, but am having trouble getting a program to work that actuates all the fingers to make gestures...i.e.. a thumbs up, a peace sign, a hand pointing...etc..etc... I am using an Arduino Uno and was wondering if anyone had a program or knew of where i find one to compare to and/or build off of. I'm sure it's my inexperience in writing programming for this type of code, so any help or guidance would be greatly appreciated.

    Thanks,
    reply @
    CKBrandeker@gmail.com

    ReplyDelete
  18. I need to program my arduino to start one servo then another with different functions and make the second one stop when the first one does!! HELP?

    ReplyDelete
  19. My little brother has been talking about getting servos for a while now and finally has enough to get one. He has been looking at different ones but doesn't know what kind he wants to get yet. Does anyone have any suggestions that can help him figure this out?
    http://www.hobbyease.com/servos-for-sale-online-k5

    ReplyDelete
  20. I am getting this error code when I go to upload, the program verifies but then this error comes up.
    Can you assist me with debugging?

    Arduino: 1.6.3 (Windows Vista), Board: "Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

    Sketch uses 6,388 bytes (2%) of program storage space. Maximum is 253,952 bytes.

    Global variables use 447 bytes (5%) of dynamic memory, leaving 7,745 bytes for local variables. Maximum is 8,192 bytes.

    avrdude: ser_open(): can't open device "\\.\COM1": The system cannot find the file specified.




    avrdude: ser_drain(): read error: The handle is invalid.




    Problem uploading to board. See http://www.arduino.cc/en/Guide/Troubleshooting#upload for suggestions.

    This report would have more information with
    "Show verbose output during compilation"
    enabled in File > Preferences.

    ReplyDelete
  21. I have been using this library successfully to parse a PPM signal and write values to my speed controllers using an UNO. I would like to to move to a smaller board and have chosen an Arduino (compatible) Pro Micro from Sparkfun with a ATMega32u4.

    I have no issues parsing the PPM signal, but my servo writes are not working. Any suggestions on how i might be able to migrate the library for the ATMega 32u4? Thanks!

    ReplyDelete
  22. This comment has been removed by the author.

    ReplyDelete
  23. I am building an AGV with lifting mechanism. I have 2 DC, 3 servo, 1 4 channel line sensor, 1 ultrasonic sensor and a limit switch. Is arduino Uno able to support or should I get Arduino Mega for this project? thanks for helping.

    ReplyDelete
  24. I most likely appreciating each and every bit of it. It is an incredible site and decent impart. I need to much obliged
    SM

    ReplyDelete
  25. Hello,
    Is this page still open?

    I would like to get some help writing a code for a 5 servo Audrino robot arm.

    We have the batteries to power we just need the code to make it move.

    Any help would be appreciated.

    Thank you

    ReplyDelete
  26. I am new at this and I need code to operate 8 servos. I need to operate each servo separately. They will only have to move about 20 degrees but each with a separate button. I have a UNO and a PCA9685 board. Ive tried many different codes but no luck. Im 72 guys cant play with this forever. If its not possible just say so Ill do it another way. Thanks bsteel

    ReplyDelete
  27. Getting rib of Herpes virus wasn't easy, I came across Dr voodoo on how he
    has cured so many people from herpes and other illness so I decided to give
    him a try, now am permanently cured using the natural herbs medication and
    cleansing supplement sent by Dr voodoo no more herpes, contact Dr voodoo
    WhatsApp number +2348140120719 also e-mail him at:
    voodoospelltemple66@gmail.com

    ReplyDelete