ML2016-1

An Arduino controlling a Crossing Gate with Lights

So, now that we can control a servo with no trouble, we can build an animated crossing gate. First we detect that a train is in the block and then we start flashing the two LEDs and move the servo so that it “pulls” the gate down. As shown in the photo, almost all crossings have two gates and if you can manage to keep the hardware pulling the gate down to be the same, you can get away with a simple piece of code to control both servos with the same output from the Arduino. Yes, the Arduino can control more than one servo, but you might want the gates to be at the top, halfway and at the bottom, at the same time, and that would require some more math, if one servo needs to travel further.

Crossing gates on the Colorado and Silver River, Chip Romig, Plano, Texas

Most crossing gate controllers that you can buy and install allow you to change the start and end points of the servo's travel, in other words, how far the gate goes up and down, and some the speed of the servo too. Since we have our computer plugged into the Arduino when we build this, all we need is to change a number on the screen and click “Upload”. So, even though it is possible to add a “training” function to the Arduino and store the values in its EEPROM (or flash memory, if you prefer), we are going to save that for a future time.

The warning again, if you only provide power to the Arduino and servo with your USB cable, you might find that once the servo starts moving, weird and random things might happen. This is not because you wrote and uploaded weird or random code. This is very likely that the servo is using too much current to maintain or move to the next position, that the voltage drop, due to the lack of current, is resetting the Arduino. Simple fix, use an external power source, like you would use eventually, to supply the Arduino instead of relying on the USB cable for power. If you can go one step further and give the servo its own 5Vdc supply, you would do even better when driving 3 or 4 servos from the same Arduino. Your computer is not going to stay with the crossing anyway, so think of the USB cable as a “data” cable and not a “power” cable.

Servo and gate linkage

We need to know how far the servo needs to move, so before we mechanically link the servo to the gate and have a wrecked gate, we want to get the servo to move by itself the same distance from and to the start and stop positions. We are assuming the length of the linkage is already fixed. (The best hole to use on the horn of the servo is the one that is the same distance away from the servo's axis, as the distance from the axis of the gate to the spot where the linkage will connect.) Now you move the gate up and down as far as you would want it to go and then move it to halfway. By using the

myServo1.write( midWay ); //pick a value for midWay

command, you want to get the servo horn to be parallel with the gate at halfway (or at least the imaginary line between the servo's axis and the hole). Then you pick two new values above and below "midWay" and use them in the code from last time to move the servo up and down to almost the same angle the gate would move (go a little less, so we can increase them later). We use these values in a #define at the top of the code and use them instead of the numbers 90 and 120 in the code.

// furthest up position

#define SERVOMAXUP 63

// furthest down position

#define SERVOMAXDOWN 21

Now you can link the servo to the gate's arm and turn the power back on. You might find that SERVOMAXDOWN needs to be less than 0, in which case you would need to mechanically rotate the servo's horn. Or do the same if the SERVOMAXUP needs to be bigger than 180.

If you want to use the same pin for driving the servo motor for the gate on the other side of the track, I suggest you assemble and try that out now, before the code grows into something harder to test with.

I promised that we would flash some lights in this issue, but we have the range of the servos under control now, all whilst they are spending no time getting there, so at the same time determining how long and LED stays on, we will also slow the servos down!

The loop() function loops through as fast as it can and when we want to do bunch of things in it, that needs to run independent from each other, we do not want to stop the loop. For example, when a train shows up, we need to move the servo motor a little bit and the turn the left (a random choice by me, sorry) light on, and then we need to move the servo a little further. Or a few times a little further, and then we want to turn the first light off and turn the other one on, after which, we need to move the servo again, a few times. Now, you can see that we can copy lines and lines of code to make this happen with only one run through the loop! But, when you change your mind about how fast the lights need to flash, or how fast or far the servo needs to move, you have to delete or add more lines of code. Not good. So, a better plan would be to decide how often the servo should move to the next position and how long the light(s) should stay on, without doing something and then pause and then do something else and pause.

// milliseconds, which are 1 second divide by a thousand, so that 2000 ms equal 2 seconds

#define LIGHTCHANGETIME 2000

unsigned long int lastTime = 0;

void setup() {

pinMode( 13, OUTPUT ); // Make the LED an output

digitalWrite( 13, HIGH ); // Turn it on

} // setup


void loop() {

unsigned long int now = millis(); // Give us the milliseconds since

// the Arduino board began running

if ( now - lastTime > LIGHTCHANGETIME ) { // Is the time now further away from

// lastTime than LIGHTCHANGETIME?

digitalWrite( 13, !digitalRead( 13 ) ); // Read what the pin is and invert it

lastTime = now; // Of course, time moved on.

} // if

} // loop

So, when you look back at what you have done, you have recreated the same Blink program that was running on the Arduino when you bought it. But with one big difference, you never used the delay( ms ) command to block the loop() function. It checks if enough time has gone by, and then move along, doing something or not, versus stopping for time to go by and then run again.

Now it is easy to add the other light (lets say on pin 12) with

digitalWrite( 12, LOW ); // Turn it off

in setup(), and toggle it too under pin 13:

digitalWrite( 13, !digitalRead( 13 ) ); // Read what the pin is and invert it

digitalWrite( 12, !digitalRead( 12 ) ); // Read what the pin is and invert it

(yes I know, the lights should both be off on power up, but we are not there yet, servo first!)

So, if we want to check if enough time has expired to do a servo move, and inside that, we decide to increase or decrease the position based on one of the 4 states we could be in, 3: going down, 2: at the bottom, 1: going back up and 0: at the top, we could add this code before the end of loop():

if ( now - lastServoTime > SERVOTIME ) { // Is the time now further away from

// lastServoTime than SERVOCHANGETIME?

if ( whereAreWe == 1 ) { // Are we going up?

if ( position < SERVOMAXUP ) // Are we are the top already?

position += 1; // No, Increase position by 1

// ( or add 2 if you like )

else // Yes, so lets stop

whereAreWe = 0; // Done, do nothing

} else if ( whereAreWe == 3 ) { // Are we going down?

if ( position > SERVOMAXDOWN ) // Are we are the top already?

position -= 1; // No, Increase position by 1

// ( or add 2 if you like )

else // Yes, so lets wait here

whereAreWe = 2; // Wait at the bottom...

} // if whereAreWe == 1

myServo1.write( position ); // Move the servo motor to new position

// in one place in the code.

lastServoTime = now; // Of course, time moved on.

} // if

and in setup()we have (replace what you have in setup with this):

void setup() {

pinMode( 13, OUTPUT ); // Make the LED an output

pinMode( 12, OUTPUT ); // Make the LED an output

digitalWrite( 13, LOW ); // Turn it off

digitalWrite( 12, LOW ); // Turn it off

myServo1.attach ( SERVOPIN01 ); // Attach servo to a PWM pin

myServo1.write( position ); // Move Servo to initial 3/4 up spot

whereAreWe = 1; // We start going up, a short demo at start-up,

// that's why we picked 66%!

lastTime = millis();

lastServoTime = lastTime;

} // setup

All we need is the #include, #defines and variable declarations at the very top (replace what you have there too):

#include <Servo.h>

// how often to change a light

#define LIGHTCHANGETIME 500

// how often to update the servo

#define SERVOTIME 150

// furthest up position

#define SERVOMAXUP 63

// furthest down position

#define SERVOMAXDOWN 21

#define SERVOPIN01 3

unsigned long int lastTime;

unsigned long int lastServoTime;

Servo myServo1;

// Make the first position 66% or 2/3 of the full range, above the bottom

int position = ( ( SERVOMAXUP - SERVOMAXDOWN ) * 2/3 ) + SERVOMAXDOWN;

char whereAreWe; // Keeping track of our state... 0: top, 1: going up,

// 2: bottom, 3: going down

Now you can easily modify the lights to only flash when we are not in the top state:

unsigned long int now = millis(); // Give us the milliseconds since

// the Arduino board began running

if ( now - lastTime > LIGHTCHANGETIME ) { // Is the time now further away from

// lastTime than LIGHTCHANGETIME?

if ( whereAreWe != 0 ) { // Are we not at the top?

digitalWrite( 13, !digitalRead( 13 ) ); // Read what the pin is and invert it

digitalWrite( 12, !digitalRead( 13 ) ); // Change in code here, since both

// lights were never off in the previous code, now: Do the opposite

// of the other light.

// There are also ways to keep a state for left on, right on, both off;

// cycle through the states and then write an output to the lights.

// but now you see how to check what an output was set to.

// Of course, 12 and 13 should be #define LEFTLIGHT 13, etc...

} else {

digitalWrite( 13, LOW ); // Lights out

digitalWrite( 12, LOW ); // Lights out

} // if not at the top

lastTime = now; // Of course, time moved on.

} // if time to update lights

So only one part is missing, and that is to decide when to go down and when to come back up! The .ino file on my website, link shown below will have the answer, but imagine that all you had to do for homework, was an

isTrainThere = digitalRead( some magic sensor input pin );

and if you are in the top state (0), you would change to the going down state (3).

if ( isTrainThere == HIGH )

if ( whereAreWe == 0 ) whereAreWe = 3;

And if you were in the down state (2) and you notice that that block is not so much filled with engine(s), rail-cars and/or a caboose any more, you might just want to switch to the going up state (1). Of course, you might want to think what happens when you boom is going up (in state 1) and another train comes in? You also want to think about “debouncing” that input, since many things might cause your train to “disappear” for a millisecond and then the booms are going up? You can either delay or postpone the reading of the sensor or take the best out of three readings, many ways to solve it, if you can't add a capacitor to the input!

Well, you could now still claim you are an Embedded Software Model Railroad Engineer. Could that become an MMR certificate?

(.ino file coming, just need to find it!!!)