Model Railway Level Crossing

Hi Everyone.

I'm new to the world of programming and have bought myself an Arduino UNO. So far I have done pretty good on my own (with a little help from a friend) but I am having trouble.

What I want to do is build a fully functional Level Crossing for my model railway layout.

At the moment once the testing button is pressed it runs through once but I want to have the train trip a miniature reed switch hidden under the track on one side or the crossing which will then set off the flashing lights and lower the boom gates (which will be done with hobby servos) and then on the other side of the crossing trip another switch to raise the gate and stop the lights.

I'm totally stumped and would very much appreciate any help or advice you guys can give me on how to add in the extra switch and servos to my sketch/project.

Thanks for your time Guys :slight_smile:

// set pin numbers:
int buttonPin = 2;     // the number of the pushbutton pin

// variables will change:
int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(12, OUTPUT);  // crossing inactive RED
  pinMode(11, OUTPUT);  // crossing active GREEN
  
  pinMode(10, OUTPUT);  // crossing lights RED
  pinMode(9, OUTPUT);   // crossing lights RED
  
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);     
  }
  
  // the loop routine runs and stops after last command
void loop() {
  
  int buttonState = digitalRead(buttonPin);
  
    // the switch has been activated and the program can run 
  if (buttonState == HIGH) {
     tone(8, 550, 750);      // crossing activated alarm
     digitalWrite(11, HIGH); // traffic stopped
     delay(1000);
     digitalWrite(11, LOW);  
     digitalWrite(10, LOW);  // crossing LEDs off
     digitalWrite(9, LOW);    
     delay(150);
     digitalWrite(9, HIGH);  // turn all crossing LEDs on for 2.5 seconds
     digitalWrite(10, HIGH); 
     delay(2500);
     digitalWrite(9, HIGH);  // left flash
     digitalWrite(10, LOW);  // right off
     delay(500);
     digitalWrite(10, HIGH); // right flash
     digitalWrite(9, LOW);   // left off
     delay(500);
     digitalWrite(9, HIGH);  // crossing LEDs on
     digitalWrite(10, HIGH); 
     delay(2500);
     digitalWrite(10, LOW);  // crossing LEDs off
     digitalWrite(9, LOW);   
     tone(8, 550, 750);      // crossing inactive alarm
     digitalWrite(12, HIGH); // crossing inactive
     delay(1500);
     digitalWrite(12, LOW);  // traffice can cross
  }

  
}

What does your sketch do that it should not, and what does it not do that it should?

At the moment the sketch I added to previous post works fine. When I push the test button a green led lights up and a piezo alarm sounds which tells me the program is active then the lights start flashing (I shortened the sketch cause there are so many delays) then they stop flashing and the piezo alarm sounds again and the red led lights up telling me the program has finished.

That part of the program I had no problem with as I modified the blinking LED sketch and added the "if" statement to allow the button to be read.

My problem is adding the second switch and the servo commands in the right places to make the whole thing fully functional.

I'm sorry if I seem hard to understand or aren't giving you enough info

The first thing is all those calls to "delay()" will have to go, or you're never going to detect the second switch in a timely manner.
Look at the blink without delay example to get a feel for the techniques you will need to do this.
Break down the light and servo sequence into a sequence of transitions and states.

Break down the light and servo sequence into a sequence of transitions and states.

Sorry if I sound dumb but what is meant by this? Am I to do the two separately then integrate them into the one?

I've been doing this for 2 weeks and I've learnt alot but there are some things I just can't get my head around.

On the arduino, your setup() function runs once and your loop() function runs continuously, for ever, until you turn of the power or upload a new sketch or reset the arduino.

Suppose you want your level crossing to close for four seconds.
You could close the gates, ring the bell, delay for four seconds, and then open the gates.
Actually, the above sentence is what YOU SHOULD NOT DO.

What you should do is this:

When the button or reed switch or whatever gets triggered, you close the gate, ring the bell,
and remember somehow what the time will be, four seconds from now ( there are several ways
of doing this ). Then your loop function runs, every few milliseconds, and one of the things
it does each cycle, is check whether the four seconds is finished yet. When the four seconds
is finished, it opens the gate and stops ringing the bell. It can also check other things and do
other things while this is happening.

Look at the "blink without delay" example sketch for the recommended way of doing this,
there are other ways of doing this also.

What michinyon is describing is called a Finite State Machine.

I wrote a tutorial a while back about them: http://hacking.majenko.co.uk/finite-state-machine

Thanks for the tips guys.. I've nad a look at the link you posted majenko and it was very good.. I now have to work out how to integrate my coding with the one you created.

The more tips I get the better i'll become.

I know I'm going to sound like a real dumba** but I don't understand how the blink without delay works. Can someone explain it to me in dumba** terms?

Sorry to be a pain

You check the time and store it in a variable.
Later on you check time again and compare it with the time stored in the variable. If the predetermined period has not yet elapsed, you go about your business, do something else.
You then check again. If the predetermined period elapsed you do what you are waiting to do and store current time in the variable for later usage.

Well its been almost 3 weeks since I started this little project and I have almost got it down pat but there is one part I need a little help with.

#include <Servo.h>

Servo myservo;
   
const int R1 = 9;  // 1st Red LED
const int R2 = 10;  // 2nd Red LED
const int G = 11;  // Green Active LED
const int R = 12;  // Red inactive LED
const int buttonPin = 2; // pushbutton pin


int buttonState = 0;     // variable for reading the pushbutton status
int running = 0;
int long interval = 500;
long previousMillis = 0;
int R1State = HIGH;
int pos = 0;

void setup() {
  pinMode(R1, OUTPUT);
  pinMode(R2, OUTPUT);
  pinMode(G, OUTPUT);
  pinMode(R, OUTPUT);
  pinMode(buttonPin, INPUT);     
  myservo.attach(7);
  digitalWrite(buttonPin, HIGH);

}
  
  // the loop routine runs and stops after last command
void loop() {
  
  int buttonState = digitalRead(buttonPin);
  
       
    // the switch has been activated and the program can run 
  if (buttonState == HIGH && running == 0) {
    int running = 1;
     tone(8, 550, 750); // play SOUND
     digitalWrite(G, HIGH); // turn on GREEN LED
     myservo.write(45);
     delay(1500);
     digitalWrite(G, LOW); // turn off GREEN LED
     digitalWrite(R2, LOW); // turn the LED off
     digitalWrite(R1, LOW); // turn the LED off
     delay(250);
     digitalWrite(R1, HIGH); // turn the LED on
     digitalWrite(R2, HIGH); // turn the LED on
     delay(2500);
           buttonState = digitalRead(buttonPin);
        while(buttonState == LOW && running == 1)  {

// Timer used instead of delay to allow input to be read at the same time
          unsigned long currentMillis = millis(); 
          
          if(currentMillis - previousMillis > interval) {
            previousMillis = currentMillis;
            
            if (R1State == HIGH){ // Switches lights over
              digitalWrite(R1, LOW);
              digitalWrite(R2, HIGH);
              R1State = LOW;
            }
            else {
              digitalWrite(R1, HIGH); // Switches lights over
              digitalWrite(R2, LOW);
              R1State = HIGH;
            }
          }
          else {
 // Terminates program and switches off lights if the trigger input is detected
            buttonState = digitalRead(buttonPin); 
            if (buttonState == HIGH){
              digitalWrite(10, LOW); // turn the LED off
              digitalWrite(9, LOW); // turn the LED off 
              tone(8, 550, 1000); // play SOUND 
              digitalWrite(12, HIGH); // turn on RED LED
              delay(1500);
              myservo.write(0);
              digitalWrite(12, LOW); // turn off RED LED
              int R1State = LOW;
              delay(5000);
              int running = 0;
              break;
            }
          }
        }
  }
}

My problem is that I have tried to incorporate servo code into the sketch to make the boom gates on the level crossing lower and raise slowly as with real ones but I can only get them to go up to 45' and down to 0' in one hit instead of in increments.

I have tried to insert the servo code where I need it but it won't work how can I fix it?

You basically need to have a variable in which you store the current angle of the boom. Then you slowly (by comparing another variable to millis()) increase / decrease that variable until you have the desired final angle. Every time you increase / decrease the angle, you do the servo.write call.

A little function such as (untested):

void moveTo(unsigned char angle)
{
  static unsigned char currentAngle = 0;
  if (currentAngle < angle) {
    currentAngle++;
    myservo.write(currentAngle);
    return;
  }
  if (currentAngle > angle) {
    currentAngle--;
    myservo.write(currentAngle);
    return;
  }
}

will only move the boom by one degree per call, if it needs to move it at all. In your main loop, you can do something like:

static unsigned long boomMoveTime = millis(); // These are either local static variables, or global non-static variables.
static unsigned char desiredBoomAngle = 0;
...
if (millis() - boomMoveTime > 10) {
  boomMoveTime = millis();
  moveTo(desiredBoomAngle);
}
...
if (whatever) {
  desiredBoomAngle = 45;
}

So, every 10 milliseconds you make a call to get the boom closer to its target angle, if it's not already there. At any time in your main loop, you can then set the variable desiredBoomAngle, and it will instantly start to move the boom slowly up or down, to whatever the desiredBoomAngle variable is set to.

will that work using a trigger to set then again to reset?

aussiefantom:
will that work using a trigger to set then again to reset?

A trigger? Not sure what you mean.

As long as the moveTo function is being called it will try to move the boom to a specific angle. How you set what that angle should be (desiredBoomAngle) is entirely up to you. Where you would normally do myservo.write(45), do desierdBoomAngle=45 instead. The repeated, timed, call to moveTo() will do the actual moving, if moving is needed.

Sorry. By trigger I mean as the train passes over the switch ( miniature reed ) it starts the program running then when the train passes over the second switch it resets or terminates the program

Looking through the latest iteration of your program I see it is still full of delay() calls. You will never be able to do two things at once (like change lights, and move a boom) with those there. Every single change of state that should happen after a period of time (such as a light going on, or an alarm sounding, etc) should be keyed to the millis() function, and not paused with delay().

Am I at least on the right track? I'm still trying to understand how all the different functions work. Is it possible to do the different stages as case functions?

Take a step back and look at the overall picture.

Please list for me the exact sequence that you want to happen, in intricate detail. Not just "the train passes, the lights flash, the boom closes", but exactly what you want each light to do, when you want the boom to start moving, stop moving etc, and all in chronological sequence.

As I said I am still learning but here goes.

The train passes over the reed switch which activates the program
The Green active LED (which will be at the control board) comes on breifly and a tone is sounded
The boom gate lowers and the Crossing LEDs start flashing
The train passes over second switch which starts end sequence
Boom gate rises
Crossing lights stop flashing
RED inactive LED comes on and a tone is sounded for end of program

Looking over the sketch and writing it down i have found a few flaws with my sequence in the code but I've written it how I want it to run.

aussiefantom:
The train passes over the reed switch which activates the program
The Green active LED (which will be at the control board) comes on breifly and a tone is sounded
The boom gate lowers and the Crossing LEDs start flashing
The train passes over second switch which starts end sequence
Boom gate rises
Crossing lights stop flashing
RED inactive LED comes on and a tone is sounded for end of program

Ok, let's see about categorizing this, so it's in a form that's easy to make a state machine from:

State: Idle.
Trigger: Train passes over reed switch
Transition: Light green LED, start tone, Set boom target angle to 0°, start flashing lights.
State: Waiting for time to pass
Trigger: Time has passed.
Transition: Turn off green LED, stop tone.
State: Waiting for train to pass second reed switch
Trigger: Train passes second reed switch
Transition: Set boom target angle to 45°
State: Waiting for boom to fully raise
Trigger: Boom has raised
Transition: Stop flashing lights, turn on red light, start making tone
State: Waiting for time to pass
Trigger: Time has passed
Transition: Stop making tone. Turn off red light. Go to idle state.

All the time: Move boom by 1° if not at target angle.
All the time: Flash lights if flashing is enabled.

You see how a trigger causes a transition to a new state? Triggers are external influences - reed switch, value from millis(), etc. States are static "waiting", where not much is happening until the next trigger. Transitions are where the actual work is done - the turning on of lights, the setting of boom angles, etc. The "All the time" functions happen always regardless of the state of the state machine, but can be controlled by the state machine by setting variables. These are "asynchronous" actions, in that they have their own timing that is outside the scope of the state machine.