Skip to main content

Servos by Arduino...

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:

Far left:    
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

Popular posts from this blog

Pi Pico, we smell competition in the land of the RRRduino! 

Pi Pico on an N-scale gondola Pi Pico, we smell competition in the land of the ‘duino!  Our very favorite low cost microcontroller system is seeing some fresh competition.  Everyone by now has heard about the Raspberry Pi, some fruity company in the United Kingdom, making single board computers!  They run Raspbian (or other flavors of Linux and are capable of some Windows versions) for as little money as $10 for the Pi Zero W.  The more popular Model 4, with 2 GB of RAM, retails for about $29.  Add a $5  micro SD card and you have a real computer with which you can surf the internet, write code and even program Arduinos.  It also runs our other favorite, JMRI.  Of course, plug it into a small or big screen television with an HDMI cable and you can even stream Netflix.  If you want a really cool computer built into a keyboard, also check out the brand new, Raspberry Pi 400 , you might just think you own a ZX Spectrum again. These are all “comp...

Not too important IR remote info...

Onn 4-Device Universal Remote manual... setting codes... Up Arrow TV Mode: Decoded NEC(1) : Value:20DF00FF Adrs:0 (32 bits) Raw samples(68): Gap:5484   Head: m8800  s4500  0:m550 s600     1:m500 s600            2:m500 s1700     3:m550 s550           4:m550 s600     5:m500 s600            6:m500 s600      7:m550 s550           8:m500 s1700    9:m550 s1700          10:m500 s600     11:m550 s1650          12:m550 s1700    13:m550 s1650         14:m550 s1650    15:m550 s1700          16:m500 s600     17:m500 s600       ...

Layout sound on demand!

Here we go...all the sounds that you have collected over the years can now be heard from your railroad. In this article you will wire a sound playing module to a speaker and a Nano with 6 wires.   Imagine a button to announce the next train ready to leave, a light sensor noticing a stock car at the loading chute making some cattle noises, wheels squealing around a curve, or on my layout, elephants and lions in the wild! DFPlayer Mini pinout   So, save some sounds, (or “songs” called from here on), in MP3 format, onto a micro (u) SD card using your personal computer. Reading the documentation makes it a bit confusing, from: the order matters in which you drag the files to the uSD card, to: having 100 folders with each 255 sounds and commands calling which file in a folder?!? What I did was save my sound files as 001.mp3, 002.mp3 and so forth in the root folder of the drive, and had no trouble getting them to play. Remember to properly “Eject” the card using your operati...