Sunday, November 13, 2011

Sparkfun Inventor's Kit Circ-04: Servos



Servos are the "bees knees" when it comes to robotics.  Motors are great when it comes to continuous spin, and will be very useful for propelling our PORPOISE Roboboat, but to be able to move actuators to specific positions for specific tasks, the servo, which is basically a geared motor (to make the movement more powerful) with some electronics inside (to make it easier to control) is the workhorse.

Fortunately, most servos are fairly inexpensive and in fact, because of that, because motors can be expensive, many people hack servos back into motors!

A servo, then, is a very important piece of hardware to learn to control.  The booklet tells us that "a standard servo is positionable from 0 to 180 degrees" (it is when this restriction is removed so that it can spin 360 degrees that it turns back into a motor!) and that "positioning is controlled through a timed pulse where 1.25 milliseconds equals straight up 0 degrees, 1.5 milliseconds moves it to 90 degrees, a right angle, and 1.75 milliseconds would be 180 degrees (straight down).  "If the pulse is sent every 25 to 50 milliseconds the servo will run smoothly" says the book. They also inform us that the Arduino software library enables you to control two servos with a single line of code when the servo is connected to pin 9 or 10.

It also appears that the Servo circuit is much simpler than the motor circuit in Circ-03; no diode, no resistor, no capacitor, and no transistor.  Just a couple of wires and the servo itself. The rest is handled by the code.  What could be simpler?

While in the first two sketches we didn't use the 5 Volt pin for anything, now we are using the 5 volt pin of the Arduino to run the motor or the servo with pin 9 providing the control signal.

In this sketch you get to use a 3 pin header that plugs into the input of the servo. The servo does not get mounted on the breadboard.


This time, when you go to http://www.oomlout.com/a/products/ardx/circ-04 for the code, the website also gives you this  great visual diagram for how to hook everything up. 
And the code is as follows:

// Sweep
// by BARRAGAN

#include <Servo.h>
Servo myservo;  // create servo object to control a servo
                // a maximum of eight servo objects can be created

int pos = 0;    // variable to store the servo position
void setup()
{
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
}

void loop()
{
  for(pos = 0; pos < 180; pos += 1)  // goes from 0 degrees to 180 degrees
  {                                  // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
  for(pos = 180; pos>=1; pos-=1)     // goes from 180 degrees to 0 degrees
  {                              
    myservo.write(pos);              // tell servo to go to position in variable ‘pos’
    delay(15);                       // waits 15ms for the servo to reach the position
  }
}


Loading it up was a breeze and instantly my servo is spinning right and left 180 degrees, titillating me with possibilities for controlling a rudder or a sail or a cannon.

It appears that the way this program works so effortlessly is that it uses Arduino's included library (hence the statement at the beginning of the code "#include <Servo.h>. 
Here is the Servo.h file for reference (from my C:\Program Files\arduino-0022\libraries\Servo folder):


/*
  Servo.h - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2
  Copyright (c) 2009 Michael Margolis.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/*
 
  A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method.
  The servos are pulsed in the background using the value most recently written using the write() method

  Note that analogWrite of PWM on pins associated with the timer are disabled when the first servo is attached.
  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

  The methods are:

   Servo - Class for manipulating servo motors connected to Arduino pins.

   attach(pin )  - Attaches a servo motor to an i/o pin.
   attach(pin, min, max  ) - Attaches to a pin setting min and max values in microseconds
   default min is 544, max is 2400 

   write()     - Sets the servo angle in degrees.  (invalid angle that is valid as pulse in microseconds is treated as microseconds)
   writeMicroseconds() - Sets the servo pulse width in microseconds
   read()      - Gets the last written servo pulse width as an angle between 0 and 180.
   readMicroseconds()   - Gets the last written servo pulse width in microseconds. (was read_us() in first release)
   attached()  - Returns true if there is a servo attached.
   detach()    - Stops an attached servos from pulsing its i/o pin.
 */

#ifndef Servo_h
#define Servo_h

#include <inttypes.h>

/*
 * Defines for 16 bit timers used with  Servo library
 *
 * If _useTimerX is defined then TimerX is a 16 bit timer on the curent board
 * timer16_Sequence_t enumerates the sequence that the timers should be allocated
 * _Nbr_16timers indicates how many 16 bit timers are 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

#define Servo_VERSION           2      // software version of this library

#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo 
#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached
#define REFRESH_INTERVAL    20000     // minumim time to refresh servos in microseconds

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

#define INVALID_SERVO         255     // flag indicating an invalid servo index

typedef struct  {
  uint8_t nbr        :6 ;             // a pin number from 0 to 63
  uint8_t isActive   :1 ;             // true if this channel is enabled, pin not pulsed if false
} ServoPin_t   ; 

typedef struct {
  ServoPin_t Pin;
  unsigned int ticks;
} servo_t;

class Servo
{
public:
  Servo();
  uint8_t attach(int pin);           // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
  uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes.
  void detach();
  void write(int value);             // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds
  void writeMicroseconds(int value); // Write pulse width in microseconds
  int read();                        // returns current pulse width as an angle between 0 and 180 degrees
  int readMicroseconds();            // returns current pulse width in microseconds for this servo (was read_us() in first release)
  bool attached();                   // return true if this servo is attached, otherwise false
private:
   uint8_t servoIndex;               // index into the channel data for this servo
   int8_t min;                       // minimum is this value times 4 added to MIN_PULSE_WIDTH   
   int8_t max;                       // maximum is this value times 4 added to MAX_PULSE_WIDTH  
};

#endif


The manual says "While it is easy to control a servo using the Arduino's included library, sometimes it is fun to figure out how to program something yourself. Try it. We're controlling the pulse directly so you could use this method to control servos on any of the Arduino's 20 available pins (you need to highly optimize this code before doing that)."

It then gives this code:

int servoPin = 9
void setup(){
     pinMode(servoPin, OUTPUT)
}
void loop() {
int pulseTime = 2100; //(the number of microseconds
//to pause for (1500 90 degrees
//900 0 degrees 2100 180 degrees)
digitalwrite(servoPin, HIGH);
delaymicroseconds(pulseTime);
digitalwrite(servoPin, LOW);
delay(25);
}

Unfortunately when I go to compile I get the error:  "expected  unqualified-id before numeric constant".  Oh my, looks like I have blundered into the land of debugging even before I've gotten my feet wet. What to do?
I cut and paste the above code into google, for these are the times we live in,  and see if anybody has posted the same code but without mistakes I might have made typing it in to the computer.

Here is what I find on http://www.instructables.com/id/Arduino-Expermentation-Kit-How-to-get-Started-wi/step6/A-Single-Servo-Servos-CIRC04/

int servoPin = 9;
void setup(){ 
pinMode(servoPin,OUTPUT);
}
void loop()

int pulseTime = 2100;
//(the number of microseconds         
//to pause for (1500 90 degrees         
// 900 0 degrees 2100 180 degrees) 
digitalWrite(servoPin, HIGH);
delayMicroseconds(pulseTime); 
digitalWrite(servoPin, LOW); 
delay(25);
}

I compile it and it loads. It doesn't do much (it kind of makes the servo jump once and stop) but it loads.  So what did I do wrong?  By comparing what I typed in and what I got from Instructables I see I left out a semi-colon after pinMode(servoPin, OUTPUT).  So I recopy and paste my code in and add the semi-colon and... I get the same error.  Hmm. So I hunt for other differences.  Ah... I used digitalwrite with a small w and they use a capital W; I used a small m in dealyMicroseconds... so this could be the problem... so I change those... and of course I should have noticed -- using the small letters in the double word they appeared black in the Arduino sketch.  Change the first letter of the second concatenated word to a capital and all of a sudden they turn brown, indicating that Arduino has recognized the core commands.

But the compile and upload still doesn't work.  Still the same error.  That is when I start noticing that the Arduino debugger is trying to help me, displaying in red each error with a line number.  It says
"sketch_nov13b:-1: error: expected unqualified-id before numeric constant" so I look at that line (noticing that the line number is in white on blue down at the very bottom of the window) and realize I left a semi-colon out at the very first line where I typed in int servoPin = 9;.  Duh.  Yet these are the simple errors that can screw everything up.

And once I've fixed that it compiles just fine.

Welcome to the world of debugging! Enjoy your time here -- you will spend a lot of frustrating hours looking for these tiny errors that screw everything up!

Next I start playing with the  pulseTime, but I realized that to see the effect I had to remove the Servo cross armature and replace it with the single arm. This way, if I put it on facing 90 degrees I can see it change direction easily.  Now when I change int pulseTime = 1500; I can see that it stays at 90 degrees. And when I make it int pulseTime = 2100; it moves toward 180 degrees (but doesn't quite get there!). Actually, contrary to the commented fields, 3000 gets us a lot closer. int pulseTime = 900; does get us closer to 0 degrees.  So it seems that the book was right when it said "Timing varies between manufacturers."  You have to experiment. But that is half the fun and most of the point.

So the insight one gets from this lesson is that there is a specific pulseTime for each Servo position.  The only thing I can't figure out is why the thing stays vibrating once it has moved into position. I am afraid that I will strip the servo if I keep it plugged in. Eventually I'll figure out how to stop the darn thing.  But that will be for another lesson!

(Don't forget to check out the Servo ideas code for:
Xmas Hit Counter:  http://adrx.orgXMAS
Open Source Robotic Arm (uses a servo controller as well as the Arduino): http://ardx.org/RARM
Servo Walker http://ardx.org/SEWA )






No comments:

Post a Comment