A Servo is a small device that has an output shaft. This shaft can be positioned to specific angular positions by sending the servo a coded signal. As long as the coded signal exists on the input line, the servo will maintain the angular position of the shaft. As the coded signal changes, the angular position of the shaft changes. In practice, servos are used in radio controlled airplanes to position control surfaces like the elevators and rudders. They are also used in radio controlled cars, puppets, and of course, robots.
What's inside:
The servo motor has some control circuits and a potentiometer (a variable resistor) that is connected to the output shaft. This pot allows the control circuitry to monitor the current angle of the servo motor. If the shaft is at the correct angle, then the motor shuts off. If the circuit finds that the angle is not correct, it will turn the motor the correct direction until the angle is correct. The output shaft of the servo is capable of traveling somewhere around 180 degrees. Usually, its somewhere in the 210 degree range, but it varies by manufacturer. A normal servo is used to control an angular motion of between 0 and 180 degrees. A normal servo is mechanically not capable of turning any farther due to a mechanical stop built on to the main output gear.
The amount of power applied to the motor is proportional to the distance it needs to travel. So, if the shaft needs to turn a large distance, the motor will run at full speed. If it needs to turn only a small amount, the motor will run at a slower speed. This is called proportional control.
Angles of Rotation: Continuous Rotation servos
The continuous rotation servos keep on rotating when pulsed with power. The
most common continuous rotation servos from Parallax rotate in
one direction when pulsed with power with a delay above 1500
microseconds and the other way when pulsed with a delay below
1500 microseconds.
This program will make the continuous rotation servo go 100 steps
one way then 100 steps the other way.
Roboticists, movie effects people, and
puppeteers use them extensively
Any time you need controlled, repeatable
motion
Can turn rotation into linear movement
with clever mechanical levers
Where you find them The underpinnings of one of the five transforming dresses featured in Hussein Chalayan's latest runway show. Wires within the tubes connect to motors at the bottom of the dress. The motors reel in wires attached to the outer layer of the garment, altering its shape.
When using the servo, it is helpful to mark the servo horn. This allows you to follow the rotation:
Connect the servo to the Microcontroller like this:
The computer can't really output analog values, but it can fake it.
It fakes it using Pulse Width Modulation or PWM.
This program moves a servo across its full range:
int servoPin = 7; // Control pin for servo motor
int myAngle;
int pulseWidth; // Amount to pulse the servo
void setup(){
pinMode(_______,________);
}
void servoPulse(int servoPin,int myAngle){
pulseWidth=(myAngle*11)+500; //converts angle to microseconds
digitalWrite(_______,HIGH);
delayMicroseconds(_________);
digitalWrite(_______,______);
delay(20-(pulseWidth/1000));
}
void loop(){
for(myAngle=0;myAngle<=180;myAngle++){
servoPulse(_____,______);
}
delay(1000);
}
This program allows you to control the movement of the servo through the keyboard and the serial monitor:
/*
* Servo Control Serial
* modified for TUI October 2007
* Servo Serial Better
* -------------------
*
* Created 18 October 2006
* copyleft 2006 Tod E. Kurt <tod@todbot.com>
* http://todbot.com/
*
* adapted from "http://itp.nyu.edu/physcomp/Labs/Servo"
*/
int servoPin = 7; // Control pin for servo motor
int pulseWidth = 0; // Amount to pulse the servo
long lastPulse = 0; // the time in millisecs of the last pulse
int refreshTime = 20; // the time in millisecs needed in between pulses
int val; // variable used to store data from serial port
int minPulse = 500; // minimum pulse width
int maxPulse = 2250; // maximum pulse width
void setup() {
pinMode(servoPin, OUTPUT); // Set servo pin as an output pin
pulseWidth = minPulse; // Set the motor position to the minimum
Serial.begin(9600); // connect to the serial port
Serial.println("Servo control program ready");
}
void loop() {
val = Serial.read(); // read the serial port
if (val >= '1' && val <= '9' ) {
val = val - '0'; // convert val from character variable to number variable
val = val - 1; // make val go from 0-8
pulseWidth = (val * (maxPulse-minPulse) / 8) + minPulse; // convert val to microseconds
Serial.print("Moving servo to position ");
Serial.println(pulseWidth,DEC);
}
updateServo(); // update servo position
}
// called every loop().
// uses global variables servoPi, pulsewidth, lastPulse, & refreshTime
void updateServo() {
// pulse the servo again if rhe refresh time (20 ms) have passed:
if (millis() - lastPulse >= refreshTime) {
digitalWrite(servoPin, HIGH); // Turn the motor on
delayMicroseconds(pulseWidth); // Length of the pulse sets the motor position
digitalWrite(servoPin, LOW); // Turn the motor off
lastPulse = millis(); // save the time of the last pulse
}
}
Exercise 1
Wire up the Servo and a potentimeter:
You are going to drive the servo with a potentiomer.
/*
* Servo with Potentiometer control
* Theory and Practice of Tangible User Interfaces
* October 11 2007
*/
int servoPin = 7; // Control pin for servo motor
int potPin = 0; // select the input pin for the potentiometer
int pulseWidth = 0; // Amount to pulse the servo
long lastPulse = 0; // the time in millisecs of the last pulse
int refreshTime = 20; // the time in millisecs needed in between pulses
int val; // variable used to store data from potentiometer
int minPulse = 500; // minimum pulse width
void setup() {
pinMode(servoPin, OUTPUT); // Set servo pin as an output pin
pulseWidth = minPulse; // Set the motor position to the minimum
Serial.begin(9600); // connect to the serial port
Serial.println("servo_serial_better ready");
}
void loop() {
val = analogRead(potPin); // read the value from the sensor, between 0 - 1024
if (val > 0 && val <= 999 ) {
pulseWidth = val*2 + minPulse; // convert angle to microseconds
Serial.print("moving servo to ");
Serial.println(pulseWidth,DEC);
}
updateServo(); // update servo position
}
// called every loop().
void updateServo() {
// pulse the servo again if rhe refresh time (20 ms) have passed:
if (millis() - lastPulse >= refreshTime) {
digitalWrite(servoPin, HIGH); // Turn the motor on
delayMicroseconds(pulseWidth); // Length of the pulse sets the motor position
digitalWrite(servoPin, LOW); // Turn the motor off
lastPulse = millis(); // save the time of the last pulse
}
}
What's going on in the loop?
In converting the range of an analog input to an analog output, it's necessary to know the maxmum and minimum values of each range so that a ranging function can be made up.
Ultimately your analog inputs will return an idiosyncratic range of numbers.
The range will vary depending on many factors, such as the
brand of transducer, the values of the resistors and capacitors in your circuit,
the quality of your microcontroller and the time of day. To use these numbers you
will have to know the range your output device needs and convert the input range into that range.
The code below was written for a potentiometer.
It assumes you're going to get values from 0 to 1023 from the sensor.
If you don't, the servo won't move through its whole range. Determine
the range of numbers the sensor is giving you and adjust the servo formula to fit.
Here's a a scaling formula that will make the adjustment:
(pulseWidth - minPulse) / pulseRange
OR
(sensorValue - minSensorValue) /sensorRange
Multiply both sides by pulseRange, and you get:
(pulseWidth - minPulse)
OR
(sensorValue - minSensorValue * pulseRange /sensorRange
That's the full ranging formula. However, because there are no floating-point variables
(i.e. fractions) in the Arduino language (and in most microcontroller languages),
you have to fudge the formula a bit. For example, if you have a sensor range from 0 to 512,
and a servo range from 500 to 2500 (i.e. a 2000-point range), you know that you can just
multiply the servo by 4 and add 500. You'll lose a little detail, but not enough to make
a difference to the end user.
When you're making approximations like this, you have to test the approximation at the
extremes of both the sensor and the output to make sure it does the job.
/*Servo control from an analog input
The minimum (minPulse) and maxiumum (maxPuluse) values
will be different depending on your specific servo motor.
Ideally, it should be between 1 and 2 milliseconds, but in practice,
0.5 - 2.5 milliseconds works well for me.
Try different values to see what numbers are best for you.
This program uses the millis() function to keep track of when the servo was
last pulsed. millis() produces an overflow error (i.e. generates a number
that's too big to fit in a long variable) after about 5 days. if you're
making a program that has to run for more than 5 days, you may need to
account for this.
by Tom Igoe
additions by Carlyn Maw
Created 28 Jan. 2006
Updated 7 Jun. 2006
*/
int servoPin = __; // Control pin for servo motor
int minPulse = 500; // Minimum servo position
int maxPulse = 2500; // Maximum servo position
int pulse = 0; // Amount to pulse the servo
long lastPulse = 0; // the time in milliseconds of the last pulse
int refreshTime = 20; // the time needed in between pulses
int analogValue = 0; // the value returned from the analog sensor
int analogPin = ___; // the analog pin that the sensor's on
void setup() {
pinMode(________, OUTPUT); // Set servo pin as an output pin
pulse = minPulse; // Set the motor position value to the minimum
Serial.begin(9600);
}
void loop() {
analogValue = analogRead(analogPin); // read the analog input
pulse = (analogValue * 19) / 10 + minPulse; // convert the analog value
// to a range between minPulse
// and maxPulse.
// pulse the servo again if rhe refresh time (20 ms) have passed:
if (millis() - lastPulse >= refreshTime) {
digitalWrite(________, HIGH); // Turn the motor on
delayMicroseconds(pulse); // Length of the pulse sets the motor position
digitalWrite(________, LOW); // Turn the motor off
lastPulse = millis(); // save the time of the last pulse
}
}
/*
* Servo Serial Better uses updateServo
* -------------------
*
*
* Created 18 October 2006
* copyleft 2006 Tod E. Kurt <tod@todbot.com>
* http://todbot.com/
*
* adapted from "http://itp.nyu.edu/physcomp/Labs/Servo"
*/
int servoPin = _; // Control pin for servo motor
int pulseWidth = 0; // Amount to pulse the servo
long lastPulse = 0; // the time in millisecs of the last pulse
int refreshTime = 20; // the time in millisecs needed in between pulses
int val; // variable used to store data from serial port
int analogPin = ___; // the analog pin that the sensor's on
int minPulse = 700; // minimum pulse width
void setup() {
pinMode(servoPin, OUTPUT); // Set servo pin as an output pin
pulseWidth = minPulse; // Set the motor position to the minimum
Serial.begin(19200); // connect to the serial port
Serial.println("Servo Serial Better ready");
}
void loop() {
analogValue = analogRead(analogPin); // read the analog input
pulseWidth = (analogValue * 19) / 10 + minPulse; // convert the analog value
// to a range between minPulse
// and maxPulse.
updateServo(); // update servo position
}
// called every loop().
// uses global variables servoPin, pulsewidth, lastPulse, & refreshTime
void updateServo() {
// pulse the servo again if rhe refresh time (20 ms) have passed:
if (millis() - lastPulse >= refreshTime) {
digitalWrite(servoPin, HIGH); // Turn the motor on
delayMicroseconds(pulseWidth); // Length of the pulse sets the motor position
digitalWrite(servoPin, LOW); // Turn the motor off
lastPulse = millis(); // save the time of the last pulse
}
}
Doesn't Arduino have PWM?
Arduino has built-in PWM on pins 9,10,11.
It uses analogWrite(pin,value) It operates at a high, fixed frequency and isn't usable for servos, but great for LEDs and motors. The PWM speed used for analogWrite() is set to 450Hz or 30 kHz currently. It's
not something changeable without understanding more about how AVRs work.
So when programming AVRs in C outside of Arduino, PWM speed can be set to just about any value.
Critter
Build your own critter by transforming the movement of the servo into linear motion by attaching objects to your servo:
<
Program your critter so that a pot controls the crawler's speed or use random(min,max) to make a crazy critter/cat toy!
/*
* Two servos with two potentiometer control
* Theory and Practice of Tangible User Interfaces
* October 11 2007
*
*/
int servoPin1 = 7; // Control pin for servo motor 1
int servoPin2 = 8; // Control pin for servo motor 2
int potPin1 = 0; // select the input pin for the potentiometer 1
int potPin2 = 1; // select the input pin for the potentiometer 2
int pulseWidth1 = 0; // Amount to pulse the servo 1
int pulseWidth2 = 0; // Amount to pulse the servo 2
long lastPulse1 = 0; // the time in millisecs of the last pulse of servo 1
long lastPulse2 = 0; // the time in millisecs of the last pulse of servo 2
int val_1; // variable used to store data from potentiometer 1
int val_2; // variable used to store data from potentiometer 2
int refreshTime = 20; // the time in millisecs needed in between pulses
int minPulse = 500; // minimum pulse width
void setup() {
pinMode(servoPin1, OUTPUT); // Set servo pin 1 as an output pin
pinMode(servoPin2, OUTPUT); // Set servo pin 2 as an output pin
pulseWidth1 = minPulse; // Set the motor position to the minimum
pulseWidth2 = minPulse; // Set the motor position to the minimum
Serial.begin(9600); // connect to the serial port
Serial.println("Two servos with two potentiometers ready");
}
void loop() {
val_1 = analogRead(potPin1); // read the value from the sensor 1, between 0 - 1024
val_2 = analogRead(potPin2); // read the value from the sensor 2, between 0 - 1024
if (val_1 > 0 && val_1 <= 999 ) {
pulseWidth1 = val_1*2 + minPulse; // convert angle to microseconds
}
updateServo1(); // update servo 1 position
if (val_2 > 0 && val_2 <= 999 ) {
pulseWidth2 = val_2*2 + minPulse; // convert angle to microseconds
}
updateServo2(); // update servo 2 position
}
void updateServo1() {
if (millis() - lastPulse1 >= refreshTime) {
digitalWrite(servoPin1, HIGH);
delayMicroseconds(pulseWidth1);
digitalWrite(servoPin1, LOW);
lastPulse1 = millis();
}
}
void updateServo2() {
if (millis() - lastPulse2 >= refreshTime) {
digitalWrite(servoPin2, HIGH);
delayMicroseconds(pulseWidth2);
digitalWrite(servoPin2, LOW);
lastPulse2 = millis();
}
}