Thomasville Double Train Control (aka Two Two Train)

by Charles R. Hentz

In order to control two trains on the same track with only one DC speed controller, a system must be devised that can sense the movement of the trains and keep them separated as they circle the loop. The loop shall be divided into eight sections of track with gaps on the left side going forward so only the negative rail will be controlled. Photo resistor sensors shall be placed in each section to give the location of each train. The sections will be labeled A through H on all panels and software programs. The system will cause the section immediately behind each train to go dead and the following section to have its speed reduced. Speed reduction should be variable to ensure an even speed and allow for different motor characteristics of the two locomotives.
Initially sensors may not show where each train is located. To start this system, all rail will be electrified until one sensor is dark. This will indicate a train in that section and a status of occupied will remain for that section until the next sensor goes dark. If a second sensor becomes dark, then that section will have a status of occupied until the next sensor goes dark. After the second sensor becomes dark, the system will assume two trains are present and will resume normal control.
This hardware will be near the Thomasville area mounted on a board accessible from the aisle. Since the original design used relays which had the contacts worn out in two years, this system will use TIP120 transistors which can handle five amperes of current.
In case of failure of the entire system or individual sections, a row of bypass switches will allow direct contact to negative rail. The Arduino Mega 2560 rev.3 microprocessor board will provide ample memory and in/out line pins. These included the available 14 PWM (pulse width modulation) pins which are used to vary the slow speed when needed. In order to protect this microprocessor from the track power supply, an optical coupler isolation module TLP281 will be used between the microprocessor board and the train power supply.
The Adafruit LEDBackpack was added to show the status of each of the eight sections of track using its 24 separate LEDs divided into sets of three which can be red, yellow, green or off. All of this bar can be controlled with only four connecting wires to the Arduino microprocessor.
Successful test operation of the above was completed on July 18, 2019 by Charley Hentz for a cost of about $90. New code was uploaded and wiring completed on Sept. 9, 2019. This an original circuit and code except for the required included code from Adafruit for the LEDBAR.

Program Code Used for Arduino Mega

// MegaTwoTwoTransTip120:
//by C. R. Hentz September 9, 2019 5pm:
/*This is a library for our I2C LED Backpacks
Designed specifically to work with the Adafruit LED 24 Bargraph Backpack
—-> http://www.adafruit.com/products/721
These displays use I2C to communicate, 2 pins are required to
interface. There are multiple selectable I2C addresses. For backpacks
with 2 Address Select pins: 0x70, 0x71, 0x72 or 0x73. For backpacks
with 3 Address Select pins: 0x70 thru 0x77
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, all text above must be included in any redistribution*/
//bargraph24crh4
#include <Wire.h>
#include <Adafruit_GFX.h>
#include “Adafruit_LEDBackpack.h”
Adafruit_24bargraph bar = Adafruit_24bargraph();
int b = 0;
int k = 0;
int i = 0;
int n = 0;
int x = 0;
void LEDTEST();
void writeLEDBAR(int x);
int ReadSens(int i);//function to read sensor at analog inputs and return 1 if dark;
int sensorPin[] = {A9, A1, A2, A3, A4, A5, A6, A7, A8};//A9 is index 0 as in all arrays:
int sensorCount = 9;//number of sensor pins in the array:
volatile boolean sensordark[10] = {false}; //an array for reading sensors true or 1 means dark:
volatile boolean occupied[10] = {false}; //an array for remembering if occupied or not:
//An array of PWM pin numbers to declare as outputs
int motorPin[] = {2, 3, 5, 6, 9, 10, 11, 12, 4};//2 is index 0:
int PWMCount = 9;//number of pins in the array:
// PWM pin connected to base of transistor
//An array of pin numbers that LEDS are attached;*/
int outputpin[] = {25, 27, 29, 31, 33, 35, 37, 39, 41};//25 is used as index 0
int pincount = 9;//number of pins or length of the array
int potPin = A0; // analog input connected to the potentiometer
int motorStop[] = {7 , 4, 3, 5, 6, 9, 10, 11, 12}; //7 is index 0
int motorSlow[] = {7 , 12 , 4, 3, 5, 6, 9, 10, 11}; //7 is index 0
int motorGo[] = {7 , 11, 12, 4, 3, 5, 6, 9, 10 };//7 is index 0
float voltage = 0;
volatile int result;
int sensorValue = 0;
int sectioncount = 8;
int val = 0;
void setup() {
Serial.begin(9600);//inialize serial communication at 9600 bits per second:
Serial.println(“HT16K33 Bi-Color Bargraph test”);
bar.begin(0x70); // pass in the address
for (uint8_t b = 0; b < 24; b++ ) {
if ((b % 3) == 0) bar.setBar(b, LED_RED);
if ((b % 3) == 1) bar.setBar(b, LED_YELLOW);
if ((b % 3) == 2) bar.setBar(b, LED_GREEN);
volatile boolean sensordark[9] = {false};
volatile boolean occupied[9] = {false}; //an array to show location is occupied or not:
float voltage = 0;
volatile int result;
int sensorValue = 0;
int sectioncount = 0;
//declare all digital pins as output
for (int i = 1; i < 54; i++) {
pinMode (i, OUTPUT);
}
void LEDTEST();
//sets led to red until initialization finished
digitalWrite(25, HIGH);
digitalWrite (26, LOW);
//sets all leds to off (high)
for (int i = 27; i < 42; i++) {
digitalWrite (i, HIGH);
}
//set led on only if sensor dark:
//continuous loop to count the dark sensors to 2 and set occupied[i]:
while (sectioncount < 2) {
sectioncount = 0;
for (int i = 1; i < 9; i++) {
result = ReadSens(i);
if (result == 1) {
sensordark[i] = true;
occupied[i] = true;
digitalWrite((outputpin[i]), LOW); //led on
sectioncount = sectioncount + 1;
} else {
sensordark[i] = false;
digitalWrite((outputpin[i]), HIGH); //led off
}
//sets led to red until initialization finished
digitalWrite(51, HIGH);
digitalWrite(53, LOW);
}
//to assign sequential numbers to PWM output pins:
//also declare all PWM pins as outputs:
for (int thisPin = 0; thisPin < PWMCount – 1; thisPin++) {
pinMode (motorPin[thisPin], OUTPUT);
}
//to assign sequential numbers to the sensor pins:
for (int thisPin = 0; thisPin < sensorCount – 1; thisPin++) {
pinMode (sensorPin[thisPin], INPUT);
}
pinMode(potPin, INPUT);//declares pot pin as input
//sets all motorPin[] on full(255):
for (int i = 1; i < 9; i++) {
analogWrite(motorPin[i], 255);
}
//sets led bar to all green:
for (uint8_t b = 0; b < 24; b++ ) {
bar.setBar(b, LED_GREEN);
bar.writeDisplay();
}
}
//sets led to green since initialization finished
digitalWrite (51, LOW);
digitalWrite (53, HIGH);
}
}
// the void loop routine runs over and over again forever:
void loop() {
// Use potentiometer input to set the speed of a DC motor:
// Output to the motor is PWM:
int PWMoutput, potReading;
potReading = analogRead(potPin);
PWMoutput = map(potReading, 0, 1023, 0, 255 );
//scales the pot reading to high of 255 for full on:
//analogWrite(motorPin, PWMoutput);
//reads sensors, sets sensordark[] and occupied[] to current condition:
//sets led on if sensor dark or off if sensor light and sets sensordark[]:
for (int i = 1; i < 9; i++) {
result = ReadSens(i);
if (result == 1) {
sensordark[i] = true;
digitalWrite((outputpin[i]), LOW); //led on
occupied[i] = true;
} else {
sensordark[i] = false;
digitalWrite((outputpin[i]), HIGH); //led off
}
}
/*Because a sensor is at the beginning of each section and
may not stay dark, a memory file is used to keep the section occupied even
after the sensor is not dark anymore. This will Test if two sections
in a row are occupied which will mean that the train has moved forward and
therefore needs to have the section behind returned to unoccupied*/
for (int i = 1; i < 9; i++) {
if (occupied[i] == true && occupied[i + 1] == true) {
occupied[i + 1] = true;
occupied[i] = false;
}
if (occupied[9] == true && occupied[1] == true) {
occupied[9] = false;
}
if (occupied[8] == true && occupied[1] == true) {
occupied[8] = false;
}
/*If section occupied, puts stop on previous section, slow on second section back, and
restores full go to third section back using arrays with arranged PWMpin numbers*/
for (int i = 1; i < 9; i++) {
if (occupied[i] == 1) {
x = i;
writeLEDBAR(x);
analogWrite(motorStop[i], 0);
analogWrite(motorSlow[i], PWMoutput);
analogWrite(motorGo[i], 255);
//Serial.println(“motorStop[i]”);
//Serial.println(motorStop[i]);
//delay(100):
}
}
}
}
// Serial.println (“end of loop3”);
//END OF VOID LOOP
//Function to read the voltage on analog pin i if low or dark then set occupied[i] as true or 1:
int ReadSens(int i) {
int sensorValue = analogRead(i);
// Convert the analog reading (which goes from 0 – 1023) to a voltage (0 – 5V):
float voltage = sensorValue * (5.0 / 1023.0);
if (voltage > 4) {
return 0;
//if voltage <4 then it is dark and true:
} else
return 1;
Serial.println (“ReadSens”);
}
//FLOW DOWN DIRECTION function for LEDBAR:
// i is dark section number:
void LEDTEST() {
for (int X = 1; i < 9; i++) {
writeLEDBAR(x);
}
}
//function writes color to LEDBAR for any of 8 occuppied section:
void writeLEDBAR(int x) {
n = x – 1;
k = 8 – n;
b = 3 * k;
if (k == 8) b = 0;
bar.setBar(b, LED_RED);
bar.setBar(b + 1, LED_RED);
bar.setBar(b + 2, LED_RED);
if (k == 7) b = -3;
bar.setBar(b + 3, LED_YELLOW);
bar.setBar(b + 4, LED_YELLOW);
bar.setBar(b + 5, LED_YELLOW);
if (k == 6) b = -6;
bar.setBar(b + 6, LED_GREEN);
bar.setBar(b + 7, LED_GREEN);
bar.setBar(b + 8, LED_GREEN);
bar.writeDisplay();
bar.writeDisplay();
//Serial.println (“writeLEDBAR”):
//Serial.println (x):
}