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.

No comments:

Post a Comment