Servos!
How to move things around, crossing gates, cranes, switch points, pecking chickens and all other moving things.
A servo is in simple terms a motor with a little feedback circuit to sense where it is, and when you tell it to go to x, the sensor will make sure that the motor keeps moving till you are there, and if it went too far, the sensor will ensure the motor moves back to the commanded position.
What made a servo harder to use in the past? Well, the position you want to go to on a typical hobby or aircraft servo motor is not set by toggling a double pole double throw switch, or with a simple analog voltage, no, it decides where to move to, by measuring the width of a pulse that is sent to it on the control wire. If the pulse is 1.5 milliseconds long, the servo motor will move towards the middle of its range, in other words, if you have a servo that can rotate 180 degrees, a 1.5 ms wide pulse will send it to 90 degrees. Similarly, A 1 ms pulse will move the motor to 0 degrees and a 2 ms wide pulse will send the servo's horn to 180 degrees. If it is a continuous running servo, 1.5 ms will stop the servo, 1 ms will make it go backwards as fast as it can and 2 ms will do forward as fast as it can.
So, these things also like to get more than one pulse, why, because I think they want something to do! So, a typical RC servo motor is driven with a pulse every 20 ms, so the whole pulse train is running at 50 Hz. And if you look at the four oscilloscope images below, you will notice what the pulses look like when the servo is driven to the far left and then the far right:
And when you take a closer look at the pulses in the images on the right hand side, you might see your first need for a PWM signal (Pulse Width Modulation that is). Each Arduino using an ATMEGA328 chip comes with 6 pins that have PWM built in hardware. Plus, you can use software to make the other pins low and high as well, so you can do a software driven PWM on any or all of the other pins.
In Arduino code, the Servo object is instantiated and the pin that will be used is assigned with the "attach( pin )" method. This method can also take the min and max microseconds to move the servo to specific end points...the default min and max, when not specified, are 544 and 2400.
and when you "Verify" this code, after saving it in a .ino file, you will also see something like this:
So what did we do? We created a Servo object called myServo, which sets up some timers in the chip to do the PWM stuff (so keep this in mind when you try to add sound or other timer based code, since their is only so many timers, and your Servo might not move if the voice synthesizer steps on your toes). In the setup() routine, we make pin A0 an output pin, which we will short to ground with a toggle switch or push button and then we tell the Servo object to use pin 3 as the control pin.
And then in loop() we read the input pin and decide if we need to move to 30 degrees or 150 degrees (if we have a 0-180 degree Servo). After that a small delay (100 ms) to give the servo some time to move and avoid the servo bouncing around when the switch is "bouncing". Of course you can change any of the values to move the servo elsewhere.
A few things to note here. When the input pin changes, the code will eventually, when it comes out of the 100 ms delay tell the servo to move to the other side. This is not a single instruction change, and if you are not careful here, the time the software spends in the write() function, might cause you some headaches if you are also listening to CAN bus messages on an LCC or OpenLCB node. The other part to note, is that the servo is now commanded to move as fast as it physically can to the other side when the write() function returns, and that is not very prototypical for a grade crossing to slam to the top, or a switch point to slam to the other side! Therefore you might want to look into telling the servo to move to points in between the final destination at a slower pace. (Watch this space, that code will be shown).
Other servo notes, for an N Scale layout, the switch points only need to move about 1 mm and the fill 180 degree stroke with the biggest horn on a $2.69 9G servo is more than an inch ... so scale appropriately back on the angles and distances the servo moves. But again, if the servo is only moving 1 degree, it is going to happen fast, so play around with the range of the servo in your application too. When Arduinos and servos turn on, they might jump to places you did not expect, so test this all out before you glue it down, you do not want the startup routine to pull your turnout apart. And that brings us to the biggest MUST HAVE, a spring! Simply put, a Tortoise piano wire connected to a $2.69 servo motor WILL rip the throw bar out of your $20 PECO turnout if you did not mechanically limit the range of the movement OR bend the piano wire to have a spring action in it. So next time you throw that blue pen that does not write anymore, pull the spring out first, and use that between the servo and the throw bar.
- One that randomly moves, without needing an input pin? See diagram below:
Part II:
Ok, no for the code that is a little bit easier to use again and again. No more direct pin numbers all over the code, all constants are #defined at the top, so it is a single place to change the input pin from like A0 to A4. Else you need to find and replace EVERY A0 with A4 throughout the whole document. Now you only need to change the INPUT1PIN line at the top.
Servo.writeMicroseconds() give a little better position control, so it is used instead of Servo.write() here
Another favorite thing I like, is to initialize the Serialport on the Arduino and spit the program name, version and date, so when you pick up an Arduino 5 years later, and don't know what is running on it, you can stick it into a computer USB port, and press Ctrl-m to open the Serial Port monitor in the Arduino IDE, and the Arduino will spit that text (VERSION) out...now you know which source file the search for in your Dropbox account or local terabyte hard drive:
Part III
One now for the real guys, two servos, in an OOP style where we use millis() to decide when to make the next move, as well as two output LED pins to show which way we are going. One of these outputs can also be used to change a relay to control your Frog polarity.
The MyServo class also has the object keep track of it's own Normal and Throw positions, which pin is the input pin and which pins are the outputs. You can can the have the Normal and Throw values on either side of each other, no problem. Using the Normal( ) and Throw( ) functions does not move the servo, it only sets the next position and update the LEDs. So you can dance the LEDs during power up and not worry where the servo goes. Only the Output( ) function, as called by the Update( ) function will make it move towards the commanded position. And it does so every updateTime interval, which can be different for the two servos. Also note, to add the third servo, all you need is three more lines of code:
declare:
in setup( ):
and at the end of loop( ):
and nothing else!!!
Part IV
And then there is the VarSpeedServo library ... a quick way to make Servos turn slower than the default library does. It also do blocking and non-blocking calls, so you can queue up commands to the servo and know they will execute in order, or you can use the non-blocking default version and see if another command was received over the CAN bus or Infrared.
Other Links:
How does a servo work
Sparkfun's tutorial
Jameco introduction
Science Buddies also take one apart
How to move things around, crossing gates, cranes, switch points, pecking chickens and all other moving things.
A servo is in simple terms a motor with a little feedback circuit to sense where it is, and when you tell it to go to x, the sensor will make sure that the motor keeps moving till you are there, and if it went too far, the sensor will ensure the motor moves back to the commanded position.
What made a servo harder to use in the past? Well, the position you want to go to on a typical hobby or aircraft servo motor is not set by toggling a double pole double throw switch, or with a simple analog voltage, no, it decides where to move to, by measuring the width of a pulse that is sent to it on the control wire. If the pulse is 1.5 milliseconds long, the servo motor will move towards the middle of its range, in other words, if you have a servo that can rotate 180 degrees, a 1.5 ms wide pulse will send it to 90 degrees. Similarly, A 1 ms pulse will move the motor to 0 degrees and a 2 ms wide pulse will send the servo's horn to 180 degrees. If it is a continuous running servo, 1.5 ms will stop the servo, 1 ms will make it go backwards as fast as it can and 2 ms will do forward as fast as it can.
So, these things also like to get more than one pulse, why, because I think they want something to do! So, a typical RC servo motor is driven with a pulse every 20 ms, so the whole pulse train is running at 50 Hz. And if you look at the four oscilloscope images below, you will notice what the pulses look like when the servo is driven to the far left and then the far right:
And when you take a closer look at the pulses in the images on the right hand side, you might see your first need for a PWM signal (Pulse Width Modulation that is). Each Arduino using an ATMEGA328 chip comes with 6 pins that have PWM built in hardware. Plus, you can use software to make the other pins low and high as well, so you can do a software driven PWM on any or all of the other pins.
In Arduino code, the Servo object is instantiated and the pin that will be used is assigned with the "attach( pin )" method. This method can also take the min and max microseconds to move the servo to specific end points...the default min and max, when not specified, are 544 and 2400.
So lets show how easy it is to move a Servo from A to B, based on another pin that is high or low. I will show the show the shortest number of code lines, and then a longer version that is easier to read and modify 5 years later:
#include <Servo.h>
Servo myServo; // the servo object to control
void setup( ) {
pinMode( A0, INPUT_PULLUP ); // using pin A0 as input pulled HIGH
// internally so all we need to do is
// short it to GND to make a change
myServo.attach( 3 ); // pin 3, 5, 7, 9, 10 or 11
// connected to the servo control pin
} // setup
void loop( ) {
if ( digitalRead( A0 ) )
myServo.write( 30 ); // move to one side
else
myServo.write( 150 ); // move to the other side
delay( 100 ); // a small delay before the next move
} // loop
and when you "Verify" this code, after saving it in a .ino file, you will also see something like this:
Sketch uses 2214 bytes (7%) of program storage space. Maximum is 30720 bytes.
Global variables use 54 bytes of dynamic memory.
So what did we do? We created a Servo object called myServo, which sets up some timers in the chip to do the PWM stuff (so keep this in mind when you try to add sound or other timer based code, since their is only so many timers, and your Servo might not move if the voice synthesizer steps on your toes). In the setup() routine, we make pin A0 an output pin, which we will short to ground with a toggle switch or push button and then we tell the Servo object to use pin 3 as the control pin.
And then in loop() we read the input pin and decide if we need to move to 30 degrees or 150 degrees (if we have a 0-180 degree Servo). After that a small delay (100 ms) to give the servo some time to move and avoid the servo bouncing around when the switch is "bouncing". Of course you can change any of the values to move the servo elsewhere.
A few things to note here. When the input pin changes, the code will eventually, when it comes out of the 100 ms delay tell the servo to move to the other side. This is not a single instruction change, and if you are not careful here, the time the software spends in the write() function, might cause you some headaches if you are also listening to CAN bus messages on an LCC or OpenLCB node. The other part to note, is that the servo is now commanded to move as fast as it physically can to the other side when the write() function returns, and that is not very prototypical for a grade crossing to slam to the top, or a switch point to slam to the other side! Therefore you might want to look into telling the servo to move to points in between the final destination at a slower pace. (Watch this space, that code will be shown).
Other servo notes, for an N Scale layout, the switch points only need to move about 1 mm and the fill 180 degree stroke with the biggest horn on a $2.69 9G servo is more than an inch ... so scale appropriately back on the angles and distances the servo moves. But again, if the servo is only moving 1 degree, it is going to happen fast, so play around with the range of the servo in your application too. When Arduinos and servos turn on, they might jump to places you did not expect, so test this all out before you glue it down, you do not want the startup routine to pull your turnout apart. And that brings us to the biggest MUST HAVE, a spring! Simply put, a Tortoise piano wire connected to a $2.69 servo motor WILL rip the throw bar out of your $20 PECO turnout if you did not mechanically limit the range of the movement OR bend the piano wire to have a spring action in it. So next time you throw that blue pen that does not write anymore, pull the spring out first, and use that between the servo and the throw bar.
More than 1 servo per Arduino, sure, but keep in mind that the servo uses some real energy to move around and the voltage regulator on the Arduino board was not picked for your 6 servo application. So when you start noticing the Arduino chip resets as the servos starts moving, it is time to devise a better plan to provide the 5Vdc power supply to the servos. Same thing happens when you have too many servos connected to an Arduino powered by your computer's USB port, you might here the servos take off followed by the same sound you here when you unplug the mouse or keyboard! Time to "servos-get-there-own-supply".
So what would make a better servo driver?
- One where we can teach each servo how far to move and save it in EEPROM to remember?
- One where we can have a push button and it changes position, without you holding the push button in?
- One that is controlled from afar, like JMRI, by iPhone or Bluetooth...think LCC or MERG CBUS, DCC accessory or WiFi?
- Solving the "I am busy writing to a servo" might be solved buy having a 2nd Arduino controlling the servos and the first one is simply doing the communications and then use pins to talk to the 2nd guy?
- One that randomly moves, without needing an input pin? See diagram below:
Part II:
Ok, no for the code that is a little bit easier to use again and again. No more direct pin numbers all over the code, all constants are #defined at the top, so it is a single place to change the input pin from like A0 to A4. Else you need to find and replace EVERY A0 with A4 throughout the whole document. Now you only need to change the INPUT1PIN line at the top.
Servo.writeMicroseconds() give a little better position control, so it is used instead of Servo.write() here
Another favorite thing I like, is to initialize the Serialport on the Arduino and spit the program name, version and date, so when you pick up an Arduino 5 years later, and don't know what is running on it, you can stick it into a computer USB port, and press Ctrl-m to open the Serial Port monitor in the Arduino IDE, and the Arduino will spit that text (VERSION) out...now you know which source file the search for in your Dropbox account or local terabyte hard drive:
#include <Servo.h>
#define INPUT1PIN A0
#define SERVO1PIN 3
#define START1ANGLE 30
#define STOP1ANGLE 150
// https://www.arduino.cc/en/Reference/ServoWriteMicroseconds:
#define START1MICRO 600
#define STOP1MICRO 2400
#define DELAY 100
#define BAUD 115200
#define VERSION "Servo002 rev 01, 2017.08.16"
Servo myServo1; // the servo object to control
void setup( ) {
Serial.begin( BAUD );
pinMode( INPUT1PIN, INPUT_PULLUP ); // using pin A0 as input pulled HIGH
// internally so all we need to do is
// short it to GND to make a change
myServo1.attach( SERVO1PIN ); // pin 3, 5, 7, 9, 10 or 11
// connected to the servo control pin
Serial.println( VERSION ); // welcome string to show what is on
// the Arduino 5 years later.
// Ctrl-M will show the SerialPort
} // setup
void loop( ) {
if ( digitalRead( INPUT1PIN ) )
myServo1.writeMicroseconds( START1MICRO ); // move to one side
else
myServo1.writeMicroseconds( STOP1MICRO ); // move to the other side
delay( DELAY ); // a small delay before the next move
} // loop
And then you want 2 servos connected, we did promise we can do more than 1 right?
// Two servos on two pins, with two inputs
#include <Servo.h>
#define INPUT1PIN A0
#define INPUT2PIN A1
// pin 3, 5, 7, 9, 10 or 11
#define SERVO1PIN 3
#define SERVO2PIN 6
// https://www.arduino.cc/en/Reference/ServoWriteMicroseconds:
#define START1MICRO 600
#define STOP1MICRO 2400
#define START2MICRO 1000
#define STOP2MICRO 2000
#define DELAY 100
#define BAUD 115200
#define VERSION "Servo003 rev 01, 2017.08.16"
Servo myServo1; // the servo object to control
Servo myServo2; // the other servo object to control
void setup( ) {
Serial.begin( BAUD );
pinMode( INPUT1PIN, INPUT_PULLUP ); // using input pin pulled HIGH
// internally so all we need to do is
// short it to GND to make a change
pinMode( INPUT2PIN, INPUT_PULLUP ); // using another input pin pulled HIGH
myServo1.attach( SERVO1PIN ); // connect to a pin
// connected to the servo control pin
myServo2.attach( SERVO2PIN ); // connect to a different pin
// connected to the servo control pin
Serial.println( VERSION ); // welcome string to show what is on
// the Arduino 5 years later.
// Ctrl-M will show the SerialPort
} // setup
void loop( ) {
if ( digitalRead( INPUT1PIN ) )
myServo1.writeMicroseconds( START1MICRO ); // move 1st servo to one side
else
myServo1.writeMicroseconds( STOP1MICRO ); // move 1st servo to the other side
if ( digitalRead( INPUT2PIN ) )
myServo2.writeMicroseconds( START2MICRO ); // move 2nd servo to one side
else
myServo2.writeMicroseconds( STOP2MICRO ); // move 2nd servo to the other side
delay( DELAY ); // a small delay before the next move
} // loop
Part III
One now for the real guys, two servos, in an OOP style where we use millis() to decide when to make the next move, as well as two output LED pins to show which way we are going. One of these outputs can also be used to change a relay to control your Frog polarity.
The MyServo class also has the object keep track of it's own Normal and Throw positions, which pin is the input pin and which pins are the outputs. You can can the have the Normal and Throw values on either side of each other, no problem. Using the Normal( ) and Throw( ) functions does not move the servo, it only sets the next position and update the LEDs. So you can dance the LEDs during power up and not worry where the servo goes. Only the Output( ) function, as called by the Update( ) function will make it move towards the commanded position. And it does so every updateTime interval, which can be different for the two servos. Also note, to add the third servo, all you need is three more lines of code:
declare:
MySlowServo myServo3( A2, 10, 4, 6, 1000, 2000 );
in setup( ):
myServo2.attach( SERVO2PIN );
and at the end of loop( ):
myServo3.Update( );
and nothing else!!!
/*
Move two servos slowly each with a toggle switch
TODO: save current state to EEPROM, to avoid any movement on power up.
TODO: add a state keeper so a single input pushbutton can be used
TODO: debouncing of the future pushbutton
Author: Gert 'Speed' Muller
2017.08.17
*/
#include <Servo.h>
#define INPUT1PIN A0
#define INPUT2PIN A1
#define NORMAL1PIN A4
#define THROW1PIN A5
#define NORMAL2PIN A6
#define THROW2PIN A7
// pin 3, 5, 7, 9, 10 or 11 for Hardware PWM
#define SERVO1PIN 3
#define SERVO2PIN 6
#define UPDATE 10
#define BAUD 115200
#define VERSION "ServoSlowerOOP rev 01, 2017.08.17"
class MySlowServo {
Servo servo; // create the servo object
int updateTime = 0;
byte servoPin = 0;
byte inputPin = 0;
byte normalOutpin = 0;
byte throwOutpin = 0;
int normalPosition = 1000;
int throwPosition = 2000;
int servoPosition = 1500;
int commandedPosition = 1500;
unsigned int now = 0;
unsigned int previousMillis = 0;
public:
// Constructor - creates a MySlowServo
// and initializes the member variables, state and pins
MySlowServo( byte theInputPin, int theUpdateTime,
byte theNormalOutpin, byte theThrowOutpin,
int theNormalPosition, int theThrowPosition ) {
updateTime = theUpdateTime;
inputPin = theInputPin;
normalOutpin = theNormalOutpin;
throwOutpin = theThrowOutpin;
normalPosition = theNormalPosition;
throwPosition = theThrowPosition;
if ( theNormalPosition < theThrowPosition )
// half the difference plus the smaller one, will get you
// to the middle of Normal and Throw
commandedPosition = servoPosition = theNormalPosition +
int( abs( theNormalPosition - theThrowPosition ) ) / 2;
else
commandedPosition = servoPosition = theThrowPosition +
int( abs( theNormalPosition - theThrowPosition ) ) / 2;
pinMode( inputPin, INPUT );
pinMode( normalOutpin, OUTPUT );
pinMode( throwOutpin, OUTPUT );
// output pins not initialized till Update, Normal or Throw is called.
// This allows for some dance on boot up to show things are working
now = previousMillis = millis( );
} // MySlowServo( byte, int, byte, byte, int, int ) constructor
void attach( byte thePin ) {
servoPin = thePin;
if ( servoPin > 0 ) {
servo.attach( servoPin );
} // if
Output( );
} // attach( )
void Output( ) {
servo.writeMicroseconds( servoPosition );
} // Output
void Set( int pos ) {
commandedPosition = pos;
} // Set( )
void Normal( ) {
commandedPosition = normalPosition;
digitalWrite( normalOutpin, HIGH );
digitalWrite( throwOutpin, LOW );
} // Normal( )
void Throw( ) {
commandedPosition = throwPosition;
digitalWrite( normalOutpin, LOW );
digitalWrite( throwOutpin, HIGH );
} // Throw( )
void Update( ) {
if ( digitalRead( inputPin ) )
Normal( );
else
Throw( );
now = millis( );
if ( now - previousMillis >= updateTime ) {
if ( servoPosition < 500 ) servoPosition = 500;
if ( servoPosition > 2500 ) servoPosition = 2500;
previousMillis = now;
if ( commandedPosition < servoPosition ) {
servoPosition--;
Output();
} else {
if ( commandedPosition > servoPosition ) {
servoPosition++;
Output();
} else {
// equal, do nothing
// Serial.println( servoPin );
} // else equal
} // not less than
} // if time to do something
} // Update( )
}; // class MySlowServo
MySlowServo myServo1( INPUT1PIN, UPDATE, NORMAL1PIN, THROW1PIN, 1000, 2000 );
MySlowServo myServo2( INPUT2PIN, 2*UPDATE, NORMAL2PIN, THROW2PIN, 1700, 1400 );
void setup( ) {
Serial.begin( BAUD );
pinMode( INPUT1PIN, INPUT_PULLUP ); // using input pin pulled HIGH
// internally so all we need to do is
// short it to GND to make a change
pinMode( INPUT2PIN, INPUT_PULLUP ); // using another input pin pulled HIGH
myServo1.Throw( ); // just set the LED
// no movement until Update( )
delay( 500 );
myServo1.Throw( ); // just set the LED
delay( 500 );
myServo1.Normal( ); // just set the LED
delay( 500 );
myServo1.Normal( ); // just set the LED
delay( 500 );
myServo1.attach( SERVO1PIN ); // connect to the servo control pin
myServo2.attach( SERVO2PIN ); // connect to a different pin
// myServo3.attach( SERVO3PIN ); // connect to a different pin
Serial.println( VERSION ); // welcome string to show what is on
// the Arduino 5 years later.
// Ctrl-m will show the Serial port
} // setup( )
// Look at this almost empy loop( ) function!!!
void loop( ) {
myServo1.Update( );
myServo2.Update( );
//myServo3.Update( );
} // loop( )
// Sketch uses 3636 bytes (11%) of program storage space. Maximum is 30720 bytes.
// Global variables use 300 bytes (14%) of dynamic memory, leaving 1748 bytes
// for local variables. Maximum is 2048 bytes.
Part IV
And then there is the VarSpeedServo library ... a quick way to make Servos turn slower than the default library does. It also do blocking and non-blocking calls, so you can queue up commands to the servo and know they will execute in order, or you can use the non-blocking default version and see if another command was received over the CAN bus or Infrared.
// Two servos on two pins, with two inputs
// https://github.com/netlabtoolkit/VarSpeedServo
#include <VarSpeedServo .h>
#define INPUTPIN1 A0
#define INPUTPIN2 A1
// pwm pin 3, 5, 7, 9, 10 or 11
#define SERVOPIN1 3
#define SERVOPIN2 6
#define OUT1 12
#define START1 80
#define STOP1 100
#define SPEED2START1 5
#define SPEED2STOP1 5
#define START2 90
#define STOP2 160
#define SPEED2START2 10
#define SPEED2STOP2 20
#define DELAY 100
#define BAUD 115200
#define VERSION "ServoVarSpeed001 rev 02, 2017.09.06"
VarSpeedServo myServo1; // the servo object to control
VarSpeedServo myServo2; // the other servo object to control
void setup( ) {
Serial.begin( BAUD );
pinMode( INPUTPIN1, INPUT_PULLUP ); // using input pin pulled HIGH
// internally so all we need to do is
// short it to GND to make a change
pinMode( INPUTPIN2, INPUT_PULLUP ); // using another input pin pulled HIGH
pinMode( OUT1, OUTPUT ); // relay or LED for servo1 position
myServo1.attach( SERVOPIN1 ); // connect to a pin
// connected to the servo control pin
myServo2.attach( SERVOPIN2 ); // connect to a different pin
// connected to the servo control pin
Serial.println( VERSION ); // welcome string to show what is on
// the Arduino 5 years later.
// Ctrl-m will show the SerialPort
} // setup
void loop( ) {
if ( digitalRead( INPUTPIN1 ) ) {
digitalWrite( OUT1, HIGH );
myServo1.write( START1, SPEED2START1 ); // move 1st servo to one side
} else {
digitalWrite( OUT1, LOW );
myServo1.write( STOP1, SPEED2STOP1 ); // move 1st servo to the other side
}
if ( digitalRead( INPUTPIN2 ) )
myServo2.write( START2, SPEED2START1 ); // move 2nd servo to one side
else
myServo2.write( STOP2, SPEED2STOP1 ); // move 2nd servo to the other side
delay( DELAY ); // a small delay before the next move
} // loop
Things you can buy:
Connecting a servo to a Nano shield or an UNO shield.
Other Links:
How does a servo work
Sparkfun's tutorial
Jameco introduction
Science Buddies also take one apart