// 3 way LX controller
// 1, 2 or 3 independant tracks, bidirectional
// Robert Parnell January 2021
// updated and tested 22/3/22
// work-in progress 6/4/22 - 4th output added for each group (see below)
// simulated electronically OK, to be checked in real world 6/4/22
//
// Version 7/1/21
// working as of 8/1/21
// Version 6/4/22 to be tested fully
// Inputs:
// LX1: LX2: LX3:
// A2 A5 A1 "up" approach/departure
// A3 A6 A0 LX itself
// A4 A7 D13 "down" approach/departure
//
//
// ################ 6/4/22
//
// Outputs:
// LX1: LX2: LX3:
// D1 D5 D9 group enable. Acitve LOW when crossing activated
// D2 D6 D10 Lamp 1 - active HIGH
// D3 D7 D11 Lamp 2 - active HIGH
// D4 D8 D12 constant 50% duty cycle output
//
// Nano - old bootloader
int flrate1 = 670; //set LED flash rate. 50% duty cycle.
int flrate2 = 625;
int flrate3 = 580;
int LX1osc= 4;
int LX2osc= 8;
int LX3osc= 12; // constant LED osciallating outputs
int LX1val = LOW;
int LX2val = LOW;
int LX3val = LOW; // 22/3/22
class Flasher
{
// Class Member Variables
// These are initialized at startup
int ledPin; // the number of the LED pin
long OnTime; // milliseconds of on-time
long OffTime; // milliseconds of off-time
// These maintain the current state
int ledState; // ledState used to set the LED
unsigned long previousMillis; // will store last time LED was updated
// Constructor - creates a Flasher
// and initializes the member variables and state
public:
Flasher(int pin, long on, long off)
{
ledPin = pin;
pinMode(ledPin, OUTPUT);
OnTime = on;
OffTime = off;
ledState = LOW;
previousMillis = 0;
}
void Update()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();
if ((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}
};
// Digital output #, on time, off time
Flasher led1(LX1osc, flrate1, flrate1); // 22/3/22 changed number to LX1osc variable
Flasher led2(LX2osc, flrate2, flrate2);
Flasher led3(LX3osc, flrate3, flrate3);
//Inputs:
int blockA1 = A2;
int blockA2 = A3;
int blockA3 = A4;
int blockB1 = A5;
int blockB2 = A6;
int blockB3 = A7;
int blockC1 = A1; // pins physically grouped together
int blockC2 = A0; //
int blockC3 = 13; //digitalinput D13
//Outputs:
int Astatus = 1; // output "enable" pins for each crossing
int Bstatus = 5;
int Cstatus = 9;
//pinMode(ledPin, OUTPUT);
//int LX1 = 4;
//int LX2 = 7;
//int LX3 = 10;
//Variables:
int A1val = 1000;
int A2val = 1000;
int A3val = 1000;
int B1val = 1000;
int B2val = 1000;
int B3val = 1000;
int C1val = 1000;
int C2val = 1000;
int C3val = 1000;
int A1occ = 0; // block occupancy status
int A2occ = 0; // 0 = unoccupied
int A3occ = 0; // 1 = occupied
int B1occ = 0;
int B2occ = 0;
int B3occ = 0;
int C1occ = 0;
int C2occ = 0;
int C3occ = 0;
int A1counter = 0;
int A2counter = 0;
int A3counter = 0;
int B1counter = 0;
int B2counter = 0;
int B3counter = 0;
int C1counter = 0;
int C2counter = 0;
int C3counter = 0;
int debouncetime = 17; // multiples of 10ms for debounce timer for a block input
int trackA = 0; //1 = road occupied
int trackB = 0;
int trackC = 0;
int trackADN = 0; // down: true 1, false = 0
int trackAUP = 0;
int trackBDN = 0;
int trackBUP = 0;
int trackCDN = 0;
int trackCUP = 0;
// combined LED output pins
int LX1a = 2; // 6/4/22
int LX1b = 3; // lamp A and B outputs for LX1
int LX2a = 6; //
int LX2b = 7;
int LX3a = 10;
int LX3b = 11;
void setup() {
pinMode (blockA1, INPUT);
pinMode (blockA2, INPUT);
pinMode (blockA3, INPUT);
pinMode (blockB1, INPUT);
pinMode (blockB2, INPUT);
pinMode (blockB3, INPUT);
pinMode (blockC1, INPUT);
pinMode (blockC2, INPUT);
pinMode (blockC3, INPUT);
pinMode (Astatus, OUTPUT);
pinMode (Bstatus, OUTPUT);
pinMode (Cstatus, OUTPUT);
pinMode (LX1a, OUTPUT); // individual lamp outputs
pinMode (LX2a, OUTPUT);
pinMode (LX3a, OUTPUT);
pinMode (LX1b, OUTPUT);
pinMode (LX2b, OUTPUT);
pinMode (LX3b, OUTPUT);
}
void loop() {
led1.Update();
led2.Update();
led3.Update();
readinputs();
if ((A1val > 300) && (A2val > 300) && (A3val > 300)) {
trackA = 0;
trackADN = 0;
trackAUP = 0;
AlightsOFF();
}
if ((B1val > 300) && (B2val > 300) && (B3val > 300)) {
trackB = 0;
trackBDN = 0;
trackBUP = 0;
BlightsOFF();
}
if ((C1val > 300) && (C2val > 300) && (C3val > 300)) {
trackC = 0;
trackCDN = 0;
trackCUP = 0;
ClightsOFF();
}
if ((A1val < 300) || (A2val < 300) || (A3val < 300) || (B1val < 300) || (B2val < 300) || (B3val < 300) || (C1val < 300) || (C2val < 300) || (C3val < 300)) {
train();
}
// if all inputs are inactive, so go back to the start and check again
//
//
// END OF MAINLOOP //
//
//
}
void train() {
// once in this void, can't get out until all trains have cleared their respective crossings
TrainLoop:
led1.Update();
led2.Update();
led3.Update();
//a train has activated one or more block inputs. Work out which one, see if up or down.
// track A
if (A1val < 300 ) {
A1occ=1;
A1counter = 0;
}
if (A2val < 300) {
A2occ=1;
A2counter = 0;
}
if (A3val < 300) {
A3occ=1;
A3counter = 0;
}
if (B1val < 300 ) {
B1occ=1;
B1counter= 0;
}
if (B2val < 300) {
B2occ=1;
B2counter = 0;
}
if (B3val < 300) {
B3occ=1;
B3counter = 0;
}
if (C1val < 300 ) {
C1occ=1;
C1counter = 0;
}
if (C2val < 300) {
C2occ=1;
C2counter = 0;
}
if (C3val < 300) {
C3occ=1;
C3counter = 0;
}
//
// debounce counters
//
// AAAAAAAAAAAAAAAAAAAAAA
//
if ((A1occ == 1) && (A1val > 300)){
A1counter = A1counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((A1occ == 1) && (A1val > 300) && (A1counter >= debouncetime)){
A1occ = 0;
}
// block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
if ((A2occ == 1) && (A2val > 300)){
A2counter = A2counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((A2occ == 1) && (A2val > 300) && (A2counter >= debouncetime)){
A2occ = 0;
}
if ((A3occ == 1) && (A3val > 300)){
A3counter = A3counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((A3occ == 1) && (A3val > 300) && (A3counter >= debouncetime)){
A3occ = 0;
}
//
// debounce counters
//
// BBBBBBBBBBBBBBBBB
//
if ((B1occ == 1) && (B1val > 300)){
B1counter = B1counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((B1occ == 1) && (B1val > 300) && (B1counter >= debouncetime)){
B1occ = 0;
}
// block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
if ((B2occ == 1) && (B2val > 300)){
B2counter = B2counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((B2occ == 1) && (B2val > 300) && (B2counter >= debouncetime)){
B2occ = 0;
}
if ((B3occ == 1) && (B3val > 300)){
B3counter = B3counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((B3occ == 1) && (B3val > 300) && (B3counter >= debouncetime)){
B3occ = 0;
}
//
// debounce counters
//
// CCCCCCCCCCCCCCCCC
//
if ((C1occ == 1) && (C1val > 300)){
C1counter = C1counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((C1occ == 1) && (C1val > 300) && (C1counter >= debouncetime)){
C1occ = 0;
}
// block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
if ((C2occ == 1) && (C2val > 300)){
C2counter = C2counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((C2occ == 1) && (C2val > 300) && (C2counter >= debouncetime)){
C2occ = 0;
}
if ((C3occ == 1) && (C3val > 300)){
C3counter = C3counter + 1;
}
// block has been occupied and is now unoccupied. Start counting for debounce
if ((C3occ == 1) && (C3val > 300) && (C3counter >= debouncetime)){
C3occ = 0;
}
//
// Up and down train logic
//
if ((A1occ == 1) && (trackAUP == 0)) {
trackADN = 1; //set as down train
trackA = 1; //turn the lights on
}
if ((A1occ ==1) && (trackAUP == 1)) {
trackADN = 0; //don't set as down train
trackA = 0; //ignore block input as train is departing
}
if ((A3occ == 1) && (trackADN == 0)) {
trackAUP = 1; //set as up train
trackA = 1; //turn the lights on
}
if ((A3occ == 1) && (trackADN == 1)) {
trackAUP = 0; //don't set as up train
trackA = 0; //ignore block input as train is departing
}
if (A2occ == 1) {
trackA = 1; //this is the road crossing block. Must flash lights in any case if active.
}
if ((A1occ == 0) && (A2occ == 0) && (A3occ == 0)){
trackA = 0; // turn lights off since that track is free of trains
}
if (trackA == 1) {
AlightsON();
}
if (trackA == 0) {
AlightsOFF();
}
//
// track B
if ((B1occ == 1) && (trackBUP == 0)) {
trackBDN = 1; //set as down train
trackB = 1; //turn the lights on
}
if ((B1occ ==1) && (trackBUP == 1)) {
trackBDN = 0; //don't set as down train
trackB = 0; //ignore block input as train is departing
}
if ((B3occ ==1) && (trackBDN == 0)) {
trackBUP = 1; //set as up train
trackB = 1; //turn the lights on
}
if ((B3occ == 1) && (trackBDN == 1)) {
trackBUP = 0; //don't set as up train
trackB = 0; //ignore block input as train is departing
}
if (B2occ == 1) {
trackB = 1; //this is the road crossing block. Must flash lights in any case if active.
}
if ((B1occ == 0) && (B2occ == 0) && (B3occ == 0)){
trackB = 0; // turn lights off since that track is free of trains
}
if (trackB == 1) {
BlightsON();
}
if (trackB == 0) {
BlightsOFF();
}
//
// track C
if ((C1occ == 1) && (trackCUP == 0)) {
trackCDN = 1; //set as down train
trackC = 1; //turn the lights on
}
if ((C1occ == 1) && (trackCUP == 1)) {
trackCDN = 0; //don't set as down train
trackC = 0; //ignore block input as train is departing
}
if ((C3occ ==1 ) && (trackCDN == 0)) {
trackCUP = 1; //set as up train
trackC = 1; //turn the lights on
}
if ((C3occ == 1) && (trackCDN == 1)) {
trackCUP = 0; //don't set as up train
trackC = 0; //ignore block input as train is departing
}
if (C2occ ==1) {
trackC = 1; //this is the road crossing block. Must flash lights in any case if active.
}
if ((C1occ == 0) && (C2occ == 0) && (C3occ == 0)){
trackC = 0; // turn lights off since that track is free of trains
}
if (trackC == 1) {
ClightsON();
}
if (trackC == 0) {
ClightsOFF();
}
// delay (10); // 10ms delay as part of debounce
// 8/1/21 - calculations seem to be enough to cause delay, so extra delay not required.
readinputs(); // now check if anything has changed state
// if a block has become inactive, count through debounce timer to ensure that is
// has actually cleared the block completely
// if occupied, counter is reset to zero.
// if counter is zero, then the block is occupied. If the counter is between zero and maximum, then debounce count is in progress
// counter has been reset because of a train. Check if it is now unoccupied and increment counter if so
if ((A1occ == 0) && (A2occ == 0) && (A3occ==0) && (B1occ == 0)&& (B2occ == 0)&& (B3occ == 0)&& (C1occ == 0)&& (C2occ == 0)&& (C3occ == 0)){
goto notrain;
}
// snip end 7/1/21
goto TrainLoop;
notrain:
delay (1); // do nothing
//
// END OF "train" VOID
//
}
void readinputs() {
A1val = analogRead(blockA1);
A2val = analogRead(blockA2);
A3val = analogRead(blockA3);
B1val = analogRead(blockB1);
B2val = analogRead(blockB2);
B3val = analogRead(blockB3);
C1val = analogRead(blockC1);
C2val = analogRead(blockC2);
C3val = digitalRead(blockC3);
if (C3val == 0) {
C3val = 0; //since C3 is on a digitalinput
}
if (C3val == 1) {
C3val = 1000;
}
}
void AlightsOFF() {
digitalWrite (Astatus, HIGH); // LX1 group "enable " output
digitalWrite (LX1a, LOW); // LX1a lamp
digitalWrite (LX1b, LOW); // LX1b lamp
}
void BlightsOFF() {
digitalWrite (Bstatus, HIGH);
digitalWrite (LX2a, LOW); //
digitalWrite (LX2b, LOW); //
}
void ClightsOFF() {
digitalWrite (Cstatus, HIGH);
digitalWrite (LX3a, LOW); //
digitalWrite (LX3b, LOW); //
}
void AlightsON() {
digitalWrite (Astatus, LOW);
LX1val=digitalRead (LX1osc); //
if (LX1val == HIGH){
digitalWrite (LX1a, LOW); //
digitalWrite (LX1b, HIGH);
}
if (LX1val == LOW) {
digitalWrite (LX1a, HIGH); //
digitalWrite (LX1b, LOW);
}
}
void BlightsON() {
digitalWrite (Bstatus, LOW);
LX2val=digitalRead (LX2osc); // read constant LX 50% duty cycle output
if (LX2val == HIGH){ // if it is high, one lamp on, the other off
digitalWrite (LX2a, LOW); //
digitalWrite (LX2b, HIGH); //
}
if (LX2val == LOW) {
digitalWrite (LX2b, LOW); // if it is low, the lamps swap state
digitalWrite (LX2a, HIGH); //
}
}
void ClightsON() {
digitalWrite (Cstatus, LOW);
LX3val=digitalRead (LX3osc); // 22/3/22
if (LX3val == HIGH) {
digitalWrite (LX3a, LOW); // 22/3/22
digitalWrite (LX3b, HIGH); // 22/3/22
}
if (LX3val == LOW) {
digitalWrite (LX3b, LOW); // 22/3/22
digitalWrite (LX3a, HIGH); // 22/3/22
}
}