Approach Island Approach RR Crossing

@jensenjosht Sry sry sry, I don't know what part of my brain appears to have been disabled, nor what caused it.

I am now reading your code, which already has applied much of my subsequently suggested advice.

Since you have an enum type, there is no need to

int last = 0;     //Previous state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.
  int current = 0;  //Current state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.

make a comment explaining the use of numbers for last and current, you can use the symbols directly, viz:

  current = ST_APPROACH_N;

It looks like you could declare last and current to be CROSSINGSTATES or, in an old fashioned turn, jettison the beauty of CROSSINGSTATES and just use int or byte types for anything that is holding a state value.

In that case, switch (see what I did there?) to an "anonymous enum"

enum {
  ST_OFF = 0,
  ST_APPROACH_N,
  ST_APPROACH_S,
  ST_ISLAND,
  ST_CLEARING_N,
  ST_CLEARING_S,
};

But what is slowing me down (!) and is stretching the FSM idea, is that your use of last and current makes it look like the state of the machine depends on how it got there. You shouldn't need to know the previous state. In a traffic lights situation, the state is manifest - just look at the lights. There are timers, yes, which technically go into the true full state of affairs, so to speak, but timers are something sorta understood.

Which just means to me that you need different states or more of them, perhaps states that are transitional in nature.

Code like this

  if (north == LOW && island == LOW && south == HIGH && last == 1) {  //Keep the light and bell sequence going if one of the approach circuits and the island circuit are activated.

looks like binary coding of the three inputs being discriminated by logical expressions. It may be easier to form a number 0..7 of these three bits. Perhaps the 8 possibilities have meaningful names.

This

  digitalWrite(LIGHT_LEFT, LOW);  //Alternating flashing lights at 120 flashes per minute. Code block repeats until a meet break condition is satisfied.
  digitalWrite(LIGHT_RIGHT, HIGH);
  digitalWrite(BELL_RELAY, LOW);
  delay(500);
  digitalWrite(LIGHT_LEFT, HIGH);
  digitalWrite(LIGHT_RIGHT, LOW);
  digitalWrite(BELL_RELAY, LOW);
  delay(500);

might not be killing you at all, but in a "real" system this activity would be controlled by... an FSM (you can have as many as you need), albeit a simple one that is either ON or OFF, and which is always called at the loop() level to do (or not do) the blinking and beeping.

Then in three places instead of the literal activity of the above lines you would just

  bellsAndLightsState = ACTIVE;

and presumably find a reason elsewhere to

  bellsAndLightsState = IDLE;

Now I hope you haven't already so my next request will be new information that might help... I'm a bit confused. In general and always, but here I refer to your description of the A-I-A track layout, which I gather is not symmetrical.

  pinMode(NORTH_APPROACH, INPUT_PULLUP);  //Track circuit one. 60ft/39.5m long. Primary circuit the train activates 90% of the time.
  pinMode(ISLAND_CIRCUIT, INPUT_PULLUP);  //Island circuit. 30ft/9m long. Circuit travels through a #5 Right hand Turnout that diverges.
  pinMode(SOUTH_APPROACH, INPUT_PULLUP);  //Track circuit two. Same length as number one, but needs to be able to activate the crossing from the other direction.

I am guessing a track circuit is a lenght of rails that can, as you described, say whether any train parts are on it.

What happens the other 10% of the time? Why does Track circuit two differ from Track circuit 2, and what does being activated from the other direction even mean?

I think you are on the right track (again, see what I did there?), and that you are way closer to being able to program this that I am to being able to figure out what's going on.

But so far it is still fun, so.

a7

Interesting, the code in #11 compiles successfully here.
@jensenjosht your image makes me think we're no longer looking at the same code, and I'm not trying to figure out what's wrong with an image. Please remember to post current code when you post updates, it's only fair to those who would try to help.
Thanks

Ah! I was wrong, but "current" and "last" parameters are not visible on my phone, and anyway, they're passed by value, and not by reference.

Edit. Nor is the variable "last" qualified as "static".

Much simpler to make the variables global, then there'd be no need to pass them.

I think you know this, @anon56112670, but for the OP, a picky clarification; generally, in the "pro" programming world, variables should be given sufficient scope to be visible where needed, and nothing more. This does at least two beneficial things. Primarily, it guards against inadvertent use of a variable that is declared elsewhere, but not intended to be used in a local context. Particularly a danger when variables are given mysterious names like x, a, m, index, etc. then later used indiscriminately for multiple purposes. I've seen comments in code like "x is the loop index", followed later by "x is the horizontal axis", then "x is the volume of water". Huh? Nice commenting, and essential in this case, but LoopIndex, HAxis, and VolH2Oml might have been much more helpful, and could each have been local variables. I've even seen floats recycled as array indices; very confusing.

Secondly, (a minor issue), with limited memory in processors such as those used in a lot of the Arduino products, it's useful to limit memory usage. A large variable (think 80 character string buffer), used in one location to receive a message once, shouldn't be global because it's context is likely only relevant when that message is being received. Why waste 80 bytes of precious memory for the life of the program? This is, of course, up to the program design.

...in which case, the variables should be passed by reference into the functions that modify them.
And, because the loop function is called repeatedly, the variables should be qualified "static"

2 Likes

Ping

Pong

Ping


OK, we've been off making the code our own, and I have to go with using global variables in this case.

The last code @jensenjosht would certainly not do much as you have pointed out (way sooner that I noticed, guess I just trusted before, um, verifying)

All the passing is just lotsa work, as the OP makes no apparent special use of the arguments or the fact that they are then local in the functions.

Passing by reference is useful, but given the OP's misuse or misunderstanding of variable scope rules and so forth a needless complication.

The code looks so much better without all the parameters and arguments! And I bet just now would be the same functionally as fixing the huge problems that all the locally declared variables are causing.

Global variables also makes easier the use of Serial.print statements anywhere there is a need to check the values of key variables and determine that they are properly informing the flow through the code and through the state machine.

@jensenjosht it doesn't appear that you have availed yourself yet of any use of the serial monitor. I don't think I've ever gotten any significant sketch working, and doing things the way I think and hope they are being done, without feedback (printing or other more sophisticate tools and techniques) beyond simple observation of the real outputs.

a7

3 Likes

I was planning on using the serial monitor for exactly the same purpose (IE. sending a message to an LCD display to additionally confirm states). Didn’t get to placing them in the code since they were randomly causing me compilation errors.

Hello jensenjosht

Post a track plan showing all components used and a picture of your model.

Have a nice day and enjoy coding in C++.

Well, I think I know what you meant to say, maybe, but you'll get precious little from an LCD using serial monitor.

Sounds like fun. But until the core functionality is operating well, stick to the simplicity and convenience which is using the serial monitor.

Get aquatinted with how to work an LCD in simple sketches that have nothing to do with trains or circuits or relays or lights. Possibly starting with example code you haven't messed with.

Then adding whizzy a LCD info screen or whatever will be simple, as any new issues will relate to why what worked one place doesn't in the other.

Divide and conquer.

a7

Gotcha. I hope to be able to get around to implementing everything suggested by tomorrow evening at the earliest. I’m going down to south eastern Iowa to help work on a 1/8 scale railroad that I’m a member of. So instead of maintaining my track, I’m helping out with the club. I love all of the new invested help and plan to put in my share. (I guess this is my beach weekend per say). Although it’s probably more physically straining work.

Don't rush on our account. We here.

I saw this in your code whilst untangling the input logic and converting to the anonymous enum:

 if (south == LOW && island == HIGH && south == HIGH && current == 0) {

which is obvsly not going to work out very well...

And

  int north = digitalRead(NORTH_APPROACH);
  int island = digitalRead(ISLAND_CIRCUIT);
  int south = digitalRead(NORTH_APPROACH);

besides the global / local thing is also not quite right yet.

I am finding these things as I "adjust" your code either by reading or observation when running it.

Post anything new that compiles and runs and is, well, coherent as far as any large changes.

a7

Not ready for prime time, but this is @jensenjosht's last posted code with some reporting and other changes.

The big obstacle I face reading it is lines like

  if (north == LOW && island == LOW && south == HIGH && last == 1) {  //Keep the light and bell 

which can be made to look like

  if (north && island && !south && last == ST_APPROACH_N) {  

and all the LOW HIGH comparisons jettisoned. This also makes changing the sense of the inputs easier.

I'm out of whatever one runs out of for now. I'll go forward, if I do, with a copy so this will always have the original logic (I hope and believe) but with some of the adjustments that might make further development smoother.


Wokwi_badge Approach Island Approach WIP


I put slide switches in parallel with pushbuttons. So you can use either. Obvsly if the slide switch is grounding the input, the pushbutton won't be able to say anything else.


// https://wokwi.com/projects/366388639343894529
// https://forum.arduino.cc/t/approach-island-approach-rr-crossing/1121055

// inputs

#define NORTH_APPROACH 5
#define ISLAND_CIRCUIT 6
#define SOUTH_APPROACH 7

// outputs

#define LIGHT_RIGHT 10
#define LIGHT_LEFT 9
#define BELL_RELAY 11

unsigned long now;    // current time for all

enum {
  ST_OFF = 0,
  ST_APPROACH_N,
  ST_APPROACH_S,
  ST_ISLAND,
  ST_CLEARING_N,
  ST_CLEARING_S,
};

byte crossingState, current, last;

enum {OFF, ON};

byte flashQQ;  // are we flashing or no?

# define ACTIVE true // my switches pulled high. OMG tangle of sense and == LOW

bool north, island, south;

void setup() {
  Serial.begin(115200);
  Serial.println("approach islan approach world!\n");

  pinMode(NORTH_APPROACH, INPUT_PULLUP);  //Track circuit one. 60ft/39.5m long. Primary circuit the train activates 90% of the time.
  pinMode(ISLAND_CIRCUIT, INPUT_PULLUP);  //Island circuit. 30ft/9m long. Circuit travels through a #5 Right hand Turnout that diverges.
  pinMode(SOUTH_APPROACH, INPUT_PULLUP);  //Track circuit two. Same length as number one, but needs to be able to activate the crossing from the other direction.

  pinMode(BELL_RELAY, OUTPUT);            //RR Bell Coil/SoundByte Speaker.
  pinMode(LIGHT_LEFT, OUTPUT);            //Left Flashing Light.
  pinMode(LIGHT_RIGHT, OUTPUT);           //Right Flashing Light.

  report();   // first report
}
  
void loop() {

// just to the output function switch it on and off with the island value

  flashQQ = digitalRead(ISLAND_CIRCUIT) ? ON : OFF;
  outputFSM();


  static unsigned long lastTime;
  now = millis();

  if (now - lastTime < 100) return;   // loop throttle
  lastTime = now;

  north = digitalRead(NORTH_APPROACH) == ACTIVE;
  island = digitalRead(ISLAND_CIRCUIT) == ACTIVE;
  south = digitalRead(SOUTH_APPROACH) == ACTIVE;  // not NORTH!

  report();

  int last = 0;     //Previous state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.
  int current = 0;  //Current state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.

  switch (crossingState) {
    case ST_OFF:
      crossingoff();
      break;

    case ST_APPROACH_N:
      appnorth();
      break;

    case ST_APPROACH_S:
      appsouth();
      break;

    case ST_ISLAND:
      crossingblocked();
      break;

    case ST_CLEARING_N:
      clearingnorth();
      break;

    case ST_CLEARING_S:
      clearingsouth();
      break;

  }
}

char *stateTags[] = {
  "OFF         ",
  "N_APPROACH_N",
  "S_APPROACH_S",
  "ISLAND      ",
  "N_CLEARING_N",
  "S_CLEARING_S",
};

void report()
{
  byte needTo = 0;

  static byte printedInputs;
  static byte printedState;

  byte inputs = 4 * north + 2 * island + south;

  if (crossingState != printedState) {
    needTo = 1;
    printedState = crossingState;
  }
  
  if (inputs != printedInputs) {
    needTo = 1;
    printedInputs = inputs;
  }

  if (needTo) {
    Serial.print(stateTags[crossingState]); Serial.print("  ");
    Serial.print(!north ? "NORTH" : "     "); Serial.print("   ");
    Serial.print(!island ? "ISLAND" : "      "); Serial.print("   ");
    Serial.print(!south ? "SOUTH" : "     "); Serial.print("   ");

    Serial.println();
  }
}

// change for wiring differences
# define LED_OFF    LOW
# define LED_ON     HIGH
# define RELAY_ON   HIGH
# define RELAY_OFF  LOW

void outputFSM()
{
  static int phase;
  static unsigned long lastTime;

  if (now - lastTime < 500) return;

  lastTime = now;

  if (flashQQ == OFF) {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_OFF);
 
    phase = 0;
    return;
  }

  if (!phase) {
    digitalWrite(LIGHT_LEFT, LED_ON);
    digitalWrite(LIGHT_RIGHT,LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_ON);
  }
  else {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_ON);
    digitalWrite(BELL_RELAY, RELAY_OFF);
  }

  phase = !phase;
}
/*
void crossingoff() {}
void appnorth() {}
void appsouth() {}
void crossingblocked() {}
void clearingnorth() {}
void clearingsouth() {}
*/


void crossingoff() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (north == LOW && island == HIGH && south == HIGH && current == 0) {  //If the north circuit activates first whule the remaining two are empty. Start the light and bell sequence.
    current = 1;
    last = current;
    crossingState = ST_APPROACH_N;
  } else if (south == LOW && island == HIGH && south == HIGH && current == 0) {  //If the south circuit activates first whuke the remaining two are empty. Start the light and bell sequence.
    current = 2;
    last = current;
    crossingState = ST_APPROACH_S;
  }
}

void appnorth() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (north == LOW && island == LOW && south == HIGH && last == 1) {  //Keep the light and bell sequence going if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == LOW && last == 1) {  //Keep the light and bell sequence going if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == HIGH && last == 1) {  //Keep the light and bell sequence going if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == HIGH && south == LOW && last == 1) {  //Keep the light and bell sequence going if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == HIGH && south == LOW && last == 1) {  //Stop the light and bell sequence if the train leaves the approach circuit.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  }
}

void appsouth() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (north == LOW && island == LOW && south == HIGH && last == 2) {  //Keep the light and bell sequence going if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == LOW && last == 2) {  //Keep the light and bell sequence going if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == HIGH && last == 2) {  //Keep the light and bell sequence going if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == HIGH && south == LOW && last == 2) {  //Keep the light and bell sequence going if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == HIGH && south == LOW && last ==2) {  //Stop the light and bell sequence if the train leaves the approach circuit.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  }
}

void crossingblocked() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (north == LOW && south == HIGH && island == HIGH && last == 3) {  //If the train has reached the island and either crosses over it completely or backs off, stop the bell and light sequence even if the train is on an approach circuit.
    current = 4;
    last = current;
    crossingState = ST_CLEARING_N;
  } else if (south == LOW && island == HIGH && north == HIGH && last == 3) {  //If the train has reached the island and either crosses over it completely or backs off, stop the bell and light sequence even if the train is on an approach circuit.
    current = 5;
    last = current;
    crossingState = ST_CLEARING_S;
  }
}

void clearingnorth() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (north == HIGH && island == HIGH && south == HIGH && last == 4) {  //If all of the circuits are empty, return to the default state.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  } else if (north == LOW && island == HIGH && south == LOW && last == 4) {  //Reactivate the light and bell sequence if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == LOW && last == 4) {  //Reactivate the light and bell sequence if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == HIGH && last == 4) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == LOW && last == 4) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == HIGH && last == 4) {  //Reactivate the light and bell sequence if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  }
}

void clearingsouth() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (north == HIGH && island == HIGH && south == HIGH && last == 5) {  //If all of the circuits are empty, return to the default state.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  } else if (north == LOW && island == HIGH && south == LOW && last == 5) {  //Reactivate the light and bell sequence if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == LOW && last == 5) {  //Reactivate the light and bell sequence if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == LOW && island == LOW && south == HIGH && last == 5) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == LOW && last == 5) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (north == HIGH && island == LOW && south == HIGH && last == 5) {  //Reactivate the light and bell sequence if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  }
}

Smoke in the air but otherwise a fine day soon for the beach.

a7

File under I have no life.

I was working with @jensenjosht's logic, unchanged, that is to say still with obvious and perhaps subtle errors.

Using the pushbuttons led me to add slide switches in parallel, as it finally dawned on me that trains may be long enough to activate multiple circuits.

Then I wondered how hard would it be to make a little train that could be moved along an LED track. The Umbrella Academy and I hacked the essence of it last yesterday and I finished coding it, details upon details but mostly hidden in a separate *.cpp file.

The track/train/circuit_switches simulation benefits from the non-blocking nature of the OP's code. It needs to be initialised and serviced. The interface is two functions along with wires directly from its outputs to the OP's switch inputs.


Wokwi_badge Neopixel Train Simulator


This still has some trouble with the logic of the bells and lights, but you can select with one switch whether to inform the OP's logic with pushbutton and switches OR by sliding a train of variable size past the switching circuits. I used the wokwi multiplexer for that.

Select manual (pushbuttons and switches) or auto (track/train/circuit_switches sim) with the slide switch. Dial in the train length. Use the other slide fader to move the train back and forth.

Please report any errors in the train/track/circuit switches part. Errors in the functionality the OP seeks are of lesser importance. I have managed to get a train through with plausible outputs on the bell and lights - going from right to left carefully, haven't tested it too much and...

... I certainly haven't started to think about how the OP's logic is intened to work, what to do to fix it or whether starting over within this development framework provided is wiser.

You know where I'll be for the rest of what's left of this day...

a7

2 Likes

@jensenjosht

The Umbrella Academy have taken a turn off the track, so to speak.

If the three inputs are binary weighted and thereby encoded into one number 0..7 and we use the island activity as '4' and the two approach circuits as '1' and '2' respectively and …

… we assume that any train is long enough to stretch over any gap between an approach circuit and the island circuit …

then a short train going south would show that number going

    0  1  5  4  6  2  0

and backwards going north, whilst a long train would show that number going

    0  1  5  7  6  2  0

again backwards going north.

An 8 x 8 table of previous input encoding along one dimension and current encoding along the other dimension would inform the necessary output of flashing v. not flashing.

In fact half the table has the '4' bit set on current input encoding these do not need to be stored as the lights are always flashing if the island circuit is active.

Column 3 can be eliminated as it is impossible for both the north and south circuits to be active without the island circuit also being active.

So we are down to a 3 x 8 table. Many of the entries are impossible transitions as they do not appear in the sequences written above. I placed an error code in those spots to catch up any flaw that might otherwise go unnoticed.

I wish I could claim I noticed/thought of this. But I can code. Here's the essence of something I was able to drop directly into my train/track simulator in lieu of a state machine:

byte table[8][3] = {
  {OFF_,  ON_,   ON_},    // was 0
  {OFF_,  xx,    xx},     // was 1
  {OFF_,  xx,    xx},     // was 2
  {xx,    xx,    xx},     // 3 is impossible
  {xx,    xx,    xx},     // was 4, won't use these entries
  {xx,    OFF_,  xx},     // was 5
  {xx,    xx,    OFF_},   // was 6
  {xx,    xx,    xx},     // was 7, won't use these entries
};

void tableAdvance()
{
  if (iAm == iWas) return;

  Serial.print(iWas);
  Serial.print(" --> "); Serial.print(iAm);

// any 4 bit set means lights on!
  if (iAm >= 4) {
    flashQQ = ON_;
    Serial.print(" so ON");
    Serial.println();
  }
  else {
    byte action = table[iWas][iAm];

    // this transition is not possible!
    if (action == xx) Serial.println("Rotten Denmark!");

    Serial.print(" therefore "); Serial.print(action);
    Serial.println();

    flashQQ = action;
  }
}

As far as I can test, this develops outputs identical to my simpler version of your state machine.

Still having fun.

a7

// https://wokwi.com/projects/366388639343894529
// https://forum.arduino.cc/t/approach-island-approach-rr-crossing/1121055

// inputs

#define NORTH_APPROACH 5
#define ISLAND_CIRCUIT 6
#define SOUTH_APPROACH 7

// outputs

#define LIGHT_RIGHT 10
#define LIGHT_LEFT 9
#define BELL_RELAY 11

unsigned long now;    // current time for all

enum {
  ST_OFF = 0,
  ST_APPROACH_N,
  ST_APPROACH_S,
  ST_ISLAND,
  ST_CLEARING_N,
  ST_CLEARING_S,
};

byte crossingState, current, last;

enum {OFF, ON};

byte flashQQ;  // are we flashing or no?

# define ACTIVE LOW // my switches pulled high. OMG tangle of sense and == LOW ARGH! LED sense FIX

byte northActive, islandActive, southActive;

void setup() {
  Serial.begin(115200);
  Serial.println("\napproach - island - approach world!\n");

  initTrack();

  pinMode(NORTH_APPROACH, INPUT_PULLUP);  //Track circuit one. 60ft/39.5m long. Primary circuit the train activates 90% of the time.
  pinMode(ISLAND_CIRCUIT, INPUT_PULLUP);  //Island circuit. 30ft/9m long. Circuit travels through a #5 Right hand Turnout that diverges.
  pinMode(SOUTH_APPROACH, INPUT_PULLUP);  //Track circuit two. Same length as number one, but needs to be able to activate the crossing from the other direction.

  pinMode(BELL_RELAY, OUTPUT);            //RR Bell Coil/SoundByte Speaker.
  pinMode(LIGHT_LEFT, OUTPUT);            //Left Flashing Light.
  pinMode(LIGHT_RIGHT, OUTPUT);           //Right Flashing Light.

  report();   // first report
}
  
void loop() {

// just to test the output function switch it on and off with the island value
//  flashQQ = digitalRead(ISLAND_CIRCUIT) ? ON : OFF;

  outputFSM();

  static unsigned long lastTime;
  now = millis();

  if (now - lastTime < 100) return;   // loop throttle
  lastTime = now;

  updateTrain();

/*
  Serial.print(crossingState); Serial.print(" ");
  Serial.print(current); Serial.print(" ");
  Serial.print(last); Serial.print(" ");
  Serial.println();
*/

  northActive = digitalRead(NORTH_APPROACH) == ACTIVE;
  islandActive = digitalRead(ISLAND_CIRCUIT) == ACTIVE;
  southActive = digitalRead(SOUTH_APPROACH) == ACTIVE;  // not NORTH!

  report();

  int last = 0;     //Previous state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.
  int current = 0;  //Current state case. 0=ST_OFF, 1=ST_APPROACH_N, 2=ST_APPROACH_S, 3=ST_ISLAND, 4=ST_CLEARING_N, 5=ST_CLEARING_S.

  switch (crossingState) {
    case ST_OFF:
      crossingoff();
      break;

    case ST_APPROACH_N:
      appnorth();
      break;

    case ST_APPROACH_S:
      appsouth();
      break;

    case ST_ISLAND:
      crossingblocked();
      break;

    case ST_CLEARING_N:
      clearingnorth();
      break;

    case ST_CLEARING_S:
      clearingsouth();
      break;

  }
}

char *stateTags[] = {
  "OFF         ",
  "N_APPROACH_N",
  "S_APPROACH_S",
  "ISLAND      ",
  "N_CLEARING_N",
  "S_CLEARING_S",
};

void report()
{
  byte needTo = 0;

  static byte printedInputs;
  static byte printedState;

  byte inputs = 4 * northActive + 2 * islandActive + southActive;

  if (crossingState != printedState) {
    needTo = 1;
    printedState = crossingState;
  }
  
  if (inputs != printedInputs) {
    needTo = 1;
    printedInputs = inputs;
  }

  if (needTo) {
    Serial.print(stateTags[crossingState]); Serial.print("  ");
    Serial.print(northActive ? "NORTH" : "     "); Serial.print("   ");
    Serial.print(islandActive ? "ISLAND" : "      "); Serial.print("   ");
    Serial.print(southActive ? "SOUTH" : "     "); Serial.print("   ");

    Serial.println();
  }
}

// change for wiring differences
# define LED_OFF    LOW
# define LED_ON     HIGH
# define RELAY_ON   HIGH
# define RELAY_OFF  LOW

void outputFSM()
{
  static int phase;
  static unsigned long lastTime;

  if (now - lastTime < 500) return;

  lastTime = now;

  if (flashQQ == OFF) {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_OFF);
 
    phase = 0;
    return;
  }

  if (!phase) {
    digitalWrite(LIGHT_LEFT, LED_ON);
    digitalWrite(LIGHT_RIGHT,LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_ON);
  }
  else {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_ON);
    digitalWrite(BELL_RELAY, RELAY_OFF);
  }

  phase = !phase;
}

/*
void crossingoff() {}
void appnorth() {}
void appsouth() {}
void crossingblocked() {}
void clearingnorth() {}
void clearingsouth() {}
*/


void crossingoff() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (northActive && !islandActive && !southActive && current == 0) {  //If the north circuit activates first while the remaining two are empty. Start the light and bell sequence.
    current = 1;
    last = current;
    crossingState = ST_APPROACH_N;
  // fixed one thing here:
  } else if (southActive && !islandActive && !northActive && current == 0) {  //If the south circuit activates first while the remaining two are empty. Start the light and bell sequence.
    current = 2;
    last = current;
    crossingState = ST_APPROACH_S;
  } else if (!southActive && islandActive && !northActive && current == 0) {  //If the island circuit activates first while the remaining two are empty. Start the light and bell sequence. Added due to possibility of track configuration.
    current = 2;
    last = current;
    crossingState = ST_APPROACH_S;
  }
}

void appnorth() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (northActive && islandActive && !southActive && last == 1) {  //Keep the light and bell sequence going if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && southActive && last == 1) {  //Keep the light and bell sequence going if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && !southActive && last == 1) {  //Keep the light and bell sequence going if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && !islandActive && southActive && last == 1) {  //Keep the light and bell sequence going if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && !islandActive && !southActive && last == 1) {  //Stop the light and bell sequence if the train leaves the approach circuit. Fixed due to forgeting to check all circuits were off.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  }
}

void appsouth() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (northActive && islandActive && !southActive && last == 2) {  //Keep the light and bell sequence going if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && southActive && last == 2) {  //Keep the light and bell sequence going if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && !southActive && last == 2) {  //Keep the light and bell sequence going if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && !islandActive && southActive && last == 2) {  //Keep the light and bell sequence going if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && !islandActive && !southActive && last ==2) {  //Stop the light and bell sequence if the train leaves the approach circuit. Fixed due to forgeting to check all circuits were off.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  }
}

void crossingblocked() {
  flashQQ = ON;  // turns on all lights and bells until a meet break condition is satisfied.

  if (northActive && !southActive && !islandActive && last == 3) {  //If the train has reached the island and either crosses over it completely or backs off, stop the bell and light sequence even if the train is on an approach circuit.
    current = 4;
    last = current;
    crossingState = ST_CLEARING_N;
  } else if (southActive && !islandActive && !northActive && last == 3) {  //If the train has reached the island and either crosses over it completely or backs off, stop the bell and light sequence even if the train is on an approach circuit.
    current = 5;
    last = current;
    crossingState = ST_CLEARING_S;
  } else if (!southActive && !islandActive && !northActive && last == 3) {  //If the train leaves the island circuit without hitting the approaches, stop the bell and light sequence even if the train is on an approach circuit. Added sice this condition is possible due to the track configuration.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  }
}

void clearingnorth() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (!northActive && !islandActive && !southActive && last == 4) {  //If all of the circuits are empty, return to the default state.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  } else if (northActive && !islandActive && southActive && last == 4) {  //Reactivate the light and bell sequence if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && southActive && last == 4) {  //Reactivate the light and bell sequence if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && !southActive && last == 4) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && southActive && last == 4) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && !southActive && last == 4) {  //Reactivate the light and bell sequence if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  }
}

void clearingsouth() {
  flashQQ = OFF;  // turns off all lights and bells until a meet break condition is satisfied.

  if (!northActive && !islandActive && !southActive && last == 5) {  //If all of the circuits are empty, return to the default state.
    current = 0;
    last = current;
    crossingState = ST_OFF;
  } else if (northActive && !islandActive && southActive && last == 5) {  //Reactivate the light and bell sequence if both approach circuits are activated even if the island isn't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && southActive && last == 5) {  //Reactivate the light and bell sequence if all of the circuits are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (northActive && islandActive && !southActive && last == 5) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && southActive && last == 5) {  //Reactivate the light and bell sequence if one of the approach circuits and the island circuit are activated.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  } else if (!northActive && islandActive && !southActive && last == 5) {  //Reactivate the light and bell sequence if the island is activated even if the approaches aren't.
    current = 3;
    last = current;
    crossingState = ST_ISLAND;
  }
}

I appreciate all of the help on this project so far and can't thank everyone enough. Using the code from the simulation I made a modified copy to try and see if it would fix some state transition cases I noticed in the simulation.

When going from North Approach/South Approach to off the lights continue to flash. I looked at the code and realized I forgot to set one of the input readings to empty meaning that it'd never transition.

Additionally, I've also added an extra condition to off that would start the sequence if the island circuit was activated first. This was done to start and stop the crossing circuit if the train entered and left from a dead-end siding which I've attached in the pdf. This is the current layout of the track and where the circuits would be roughly located on the right of way.

Hope this helps.

JSL_Circuit_Diagram.pdf (37.1 KB)

for our mobile members:

Thank you. Additionally, When I compile the code on my Arduino Mega 2560, All three relay outputs turn on but won't run any of the code, even when holding the inputs and ground wires together.

When I did compile it run on my mega, I had to get rid of two sub routine calls and an include that I had to remove because they weren't called out according to the compiler. That being initTrack(); updateTrain(); and # include "track.h" Even still, I believe the files were primarily for the simulator and shouldn't have affected anything? Probably did and thanks for working with me.

If you are talking about my train/track, you have to make tabs for, and copy, the *.h file and the *.cpp file.

Without those, the # include can't find the header, and the compiler can't find the train/track simulator code.

Yes, you should be able to do without them in real life.

I can only suggest to you that you remain in the simulated world until you get anything to function properly either according to your original requirements or your enhancements.

When I am back at the lab I will post a link to a simulation of my version of your logic, with a number of simplifications that do not change the functionality and a number of obvious and not-so-obvious errors of a typographical or other accidental nature.

I was able to complete remove all references to last and current, after convincing myself that they are not only oddly implemented, but redundant.

Just for fun I will also post the table-driven solution, which is much less flexible in that things like magically appearing on the island circuit are simply not accounted for, and cannot be. At least I don't see how.

I have to repeat that it would make the most sense to me to get the original functions working, however you do, so you have a good understanding of what's going on and why, before you start adding new features.

a7

This is the simplified FSM approach to the origianl specification. I think.

Wokwi_badge FSM approach-island-approach

and this is the table driven implementation, just for fun.

Wokwi_badge Table driven approach-island-approach

In the FSM version I used two island states to "know" which way a train is going. This is probably unnecessary, given that a train seems to be able to go back and forth and so on with or without reaching the island, or if it did the clearing on the "other" side.

Indeed these neither may be what @jensenjosht is getting after, but it does show two ways of doing things like what she wants to accomplish.

FSM:

// https://wokwi.com/projects/366606203975163905
// https://forum.arduino.cc/t/approach-island-approach-rr-crossing/1121055

# include "track.h"

// inputs
#define NORTH_APPROACH 5
#define ISLAND_CIRCUIT 6
#define SOUTH_APPROACH 7

// outputs
#define LIGHT_RIGHT 10
#define LIGHT_LEFT 9
#define BELL_RELAY 11

unsigned long now;    // current time for all

enum {
  OFF = 0,
  NAP,
  SAP,
  NISL,
  SISL,
  NCLR,
  SCLR,
};

byte xState;

enum {OFF_, ON_};

byte flashQQ;  // are we flashing or no?

# define ACTIVE LOW // my switches pulled high. OMG tangle of sense and == LOW ARGH! LED sense FIX

byte northActive, islandActive, southActive;

void setup() {
  Serial.begin(115200);
  Serial.println("\napproach - island - approach world!\n");

  initTrack();

  pinMode(NORTH_APPROACH, INPUT_PULLUP);  //Track circuit one. 60ft/39.5m long. Primary circuit the train activates 90% of the time.
  pinMode(ISLAND_CIRCUIT, INPUT_PULLUP);  //Island circuit. 30ft/9m long. Circuit travels through a #5 Right hand Turnout that diverges.
  pinMode(SOUTH_APPROACH, INPUT_PULLUP);  //Track circuit two. Same length as number one, but needs to be able to activate the crossing from the other direction.

  pinMode(BELL_RELAY, OUTPUT);            //RR Bell Coil/SoundByte Speaker.
  pinMode(LIGHT_LEFT, OUTPUT);            //Left Flashing Light.
  pinMode(LIGHT_RIGHT, OUTPUT);           //Right Flashing Light.

  report();   // first report
}
  
void loop() {

// just to test the output function switch it on and off with the island value
//  flashQQ = digitalRead(ISLAND_CIRCUIT) ? ON_ : OFF_;

  outputFSM();

  static unsigned long lastTime;
  now = millis();

  if (now - lastTime < 50) return;   // loop throttle 20 Hz also will debounce switches automatically 
  lastTime = now;

  updateTrain();

  northActive = digitalRead(NORTH_APPROACH) == ACTIVE;
  islandActive = digitalRead(ISLAND_CIRCUIT) == ACTIVE;
  southActive = digitalRead(SOUTH_APPROACH) == ACTIVE;  // not NORTH!

  report();

  theFSM();
}

void theFSM()
{
  switch (xState) {

  case OFF :
    if (northActive) xState = NAP;
    if (southActive) xState = SAP;
    flashQQ = xState == OFF ? OFF_ : ON_;
    break;

  case NAP :
    if (islandActive) xState = NISL;
    if (!northActive) xState = OFF;
    break;

  case SAP :
    if (islandActive) xState = SISL;
    if (!southActive) xState = OFF;
    break;

  case NISL :
    flashQQ = ON_;
    if (islandActive)
      break;        // island is active! stay in state

    if (northActive) {
      flashQQ = OFF_;
      xState = NAP;
    }
    if (southActive) xState = SCLR;
    break;

  case SISL :
    flashQQ = ON_;
    if (islandActive)
      break;        // island is active! stay in state

    if (southActive) {
      flashQQ = OFF_;
      xState = SAP;
    }
    if (northActive) xState = NCLR;
    break;

  case NCLR :
    if (islandActive) xState = SISL;
    if (!islandActive) flashQQ = OFF_;
    if (!northActive) xState = OFF;
    break;

  case SCLR :
    if (islandActive) xState = NISL;
    if (!islandActive) flashQQ = OFF_;
    if (!southActive) xState = OFF;
    break;
  }
}

char *stateTags[] = {
  "OFF_",
  "NAP_",
  "SAP_",
  "NISL",
  "SISL",
  "NCLR",
  "SCLR",
};

void report()
{
  byte needTo = 0;

  static byte printedInputs;
  static byte printedState;

  byte inputs = 4 * northActive + 2 * islandActive + southActive;

  if (xState != printedState) {
    needTo = 1;
    printedState = xState;
  }
  
  if (inputs != printedInputs) {
    needTo = 1;
    printedInputs = inputs;
  }

  if (needTo) {
    Serial.print(stateTags[xState]); Serial.print("  ");
    Serial.print(northActive ? "NORTH" : "     "); Serial.print("   ");
    Serial.print(islandActive ? "ISLAND" : "      "); Serial.print("   ");
    Serial.print(southActive ? "SOUTH" : "     "); Serial.print("   ");

    Serial.println();
  }
}

// change for wiring differences
# define LED_OFF    LOW
# define LED_ON     HIGH
# define RELAY_ON   HIGH
# define RELAY_OFF  LOW

void outputFSM()
{
  static int phase;
  static unsigned long lastTime;

  if (now - lastTime < 777) return;

  lastTime = now;

  if (flashQQ == OFF_) {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_OFF);
 
    phase = 0;
    return;
  }

// for now, just a level. comment and change the if/else
  digitalWrite(BELL_RELAY, RELAY_ON);

  if (!phase) {
    digitalWrite(LIGHT_LEFT, LED_ON);
    digitalWrite(LIGHT_RIGHT,LED_OFF);
 //   digitalWrite(BELL_RELAY, RELAY_ON);
  }
  else {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_ON);
//    digitalWrite(BELL_RELAY, RELAY_OFF);
  }

  phase = !phase;
}


Table code:

// https://wokwi.com/projects/366646774211372033
// https://forum.arduino.cc/t/approach-island-approach-rr-crossing/1121055

# include "track.h"

// inputs
#define NORTH_APPROACH 5
#define ISLAND_CIRCUIT 6
#define SOUTH_APPROACH 7

// outputs
#define LIGHT_RIGHT 10
#define LIGHT_LEFT 9
#define BELL_RELAY 11

unsigned long now;    // current time for all

enum {OFF_, ON_, xx};

byte flashQQ;  // are we flashing or no?

# define ACTIVE LOW // my switches pulled high. OMG tangle of sense and == LOW ARGH! LED sense FIX

byte northActive, islandActive, southActive;

void setup() {
  Serial.begin(115200);
  Serial.println("\napproach - island - approach world!\n");

  initTrack();

  pinMode(NORTH_APPROACH, INPUT_PULLUP);  //Track circuit one. 60ft/39.5m long. Primary circuit the train activates 90% of the time.
  pinMode(ISLAND_CIRCUIT, INPUT_PULLUP);  //Island circuit. 30ft/9m long. Circuit travels through a #5 Right hand Turnout that diverges.
  pinMode(SOUTH_APPROACH, INPUT_PULLUP);  //Track circuit two. Same length as number one, but needs to be able to activate the crossing from the other direction.

  pinMode(BELL_RELAY, OUTPUT);            //RR Bell Coil/SoundByte Speaker.
  pinMode(LIGHT_LEFT, OUTPUT);            //Left Flashing Light.
  pinMode(LIGHT_RIGHT, OUTPUT);           //Right Flashing Light.
}

byte iWas, iAm;  // previous inputs compare to current inputs informs turning on or off the lights
  
void loop() {

// just to test the output function switch it on and off with the island value
//  flashQQ = digitalRead(ISLAND_CIRCUIT) ? ON_ : OFF_;

  outputFSM();

  static unsigned long lastTime;
  now = millis();

  if (now - lastTime < 50) return;   // loop throttle 20 Hz also will debounce switches automatically 
  lastTime = now;

  updateTrain();

  northActive = digitalRead(NORTH_APPROACH) == ACTIVE;
  islandActive = digitalRead(ISLAND_CIRCUIT) == ACTIVE;
  southActive = digitalRead(SOUTH_APPROACH) == ACTIVE;  // not NORTH!

// soon to be previous inputs
  iWas = iAm;
// current inputs encoded
  iAm = 4 * islandActive + 2 * northActive + southActive;

  tableAdvance();
}

byte table[8][3] = {
  {OFF_,  ON_,   ON_},  // was 0
  {OFF_,  xx,    xx},  // was 1
  {OFF_,  xx,    xx},  // was 2
  {xx,    xx,    xx},  // 3 is impossible
  {xx,    xx,    xx},  // was 4, won't use these entries
  {xx,    OFF_,  xx},  // was 5
  {xx,    xx,    OFF_},  // was 6
  {xx,    xx,    xx},  // was 7, won't use these entries
};

void tableAdvance()
{
  if (iAm == iWas) return;

  Serial.print(iWas);
  Serial.print(" --> "); Serial.print(iAm);

  if (iAm >= 4) {
    flashQQ = ON_;
    Serial.print(" so ON");
    Serial.println();
  }
  else {
    byte action = table[iWas][iAm];

    // this transition is not possible!
    if (action == xx) Serial.println("Rotten Denmark!");

    Serial.print(" therefore "); Serial.print(action);
    Serial.println();

    flashQQ = action ? ON_ : OFF_;
  }
}

// change for wiring differences
# define LED_OFF    LOW
# define LED_ON     HIGH
# define RELAY_ON   HIGH
# define RELAY_OFF  LOW

void outputFSM()
{
  static int phase;
  static unsigned long lastTime;

  if (now - lastTime < 777) return;
  lastTime = now;

  if (flashQQ == OFF_) {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_OFF);
    digitalWrite(BELL_RELAY, RELAY_OFF);
 
    phase = 0;
    return;
  }

// for now, just a level. comment and change the if/else
  digitalWrite(BELL_RELAY, RELAY_ON);

  if (!phase) {
    digitalWrite(LIGHT_LEFT, LED_ON);
    digitalWrite(LIGHT_RIGHT,LED_OFF);
 //   digitalWrite(BELL_RELAY, RELAY_ON);
  }
  else {
    digitalWrite(LIGHT_LEFT, LED_OFF);
    digitalWrite(LIGHT_RIGHT, LED_ON);
//    digitalWrite(BELL_RELAY, RELAY_OFF);
  }

  phase = !phase;
}

I'm tapping out here. If I don't. I'll be up all day coding a dead-end sidebar addition to the LED train/track... :expressionless:

Good luck!

a7