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

Comments

Popular posts from this blog

Signal Masts in JMRI 4.14, for beginners...

Here are all the notes and files from the Plano Train Show Clinic. You need to have an internal sensor ( IMCOMPORT ) with the port in the value like COM10 or /dev/ttyUSB0 , whatever Device Manager (or /dev/ttyUSBx) shows for the connected Arduino. If you have Panels -> Script Output open, you will see the message:  !!! Please add IMCOMPORT with the port in the value!!! The basic idea here is to use the Comment in the Signal Head field to define the position of the Pixel in the string commented to the Arduino, as well as the RGB colors to show for Clear, Approach and Stop (00:00 FF 00: BBAA 00: FF 0000). With all the Heads in the Table, run the python script and now you can update the colors in the Comment "live", keeping in mind that a message is only sent when a Head changes color. We then put the Heads in Masts and use the Signal Mast Logic to do the magic. I like the original Panel Editor more and it works fine as long as you keep using Mast pairs, which require

The One Pin Signal System, using an Arduino

The One (RRRduino) Pin Signal System! Part I NeoPixels: Yes, you read that right, you can control a whole signal system with up to 200 lights by using a single Arduino pin. And this was not my idea, all the credit and the patent goes to my friend Tom, but since I already had something using the same technology up and running using only a Nano, I do not feel too bad telling you about it. Short video first! There is a tiny 8 pin chip out there called a WS2811 which is a three channel LED driver, and you might find them on the interweb as NeoPixel LED Driver Chips. They need power and ground, typically 5 Vdc, and data to be put in the three registers inside the chip, one for each LEDs it can control. So, in most cases out there today, the LEDs connected are Red, Green and Blue (RGB for short) and by setting the three registers each with an 8 bit value (which in English means a value from 0 to 255, since 2 8 = 256, but since 0 is an acceptable value too, the maximum is 2 8 - 1

Not RRRduino, unless you have an MQTT connection via Ethernet or WiFi

JMRI code to connect to an MQTT broker, to publish and subscribe to the messages for signal masts. Of course, we agree to a standard for the MQTT topics, and since you were not here, we settled on "mast.xxxx" where xxxx is a number starting at zero. This MQTT topic is used as the User Name in a JMRI signal mast. Created as a Virtual Mast with the Aspect used as the payload in the message. And, yes, the device under the real mast needs to be programmed with the same name and code for each of the "aspect" payloads to be implemented. A yellow and red LED'd dwarf signal can not do a "Clear" aspect, so make sure "Clear" is maybe set to yellow for "Approach" as well.    Virtual Masts with agreed upon User Names. Comments are for the One Wire Signals on the NeoPixel string (see prior post)   Virtual Mast The Jython (or Python) code shown here below, is to attach a Listener to each Signal Mast, and to publish a message