Controlling LEDS for my Subway Train Layout

Hello, this is my first post.

I currently have an Arduino Uno on order and wish to control signals on my train layout depending on a train's position. I have been successfully doing this with TTL NAND gates, but future interlocking logic may require over 50 ICs on a single table.

So my first attempt will be to get a simple non-interlocking section containing 4 signals to work. At a high level, there are 12 inputs and 11 outputs. The inputs are sourced via current detectors for individual track blocks on the layout. Occupied track sends a HIGH voltage (~4.0VDC) while unoccupied track sends a LOW voltage (0VDC). The outputs are used to control the LEDs. In this case, there will only be 4 LEDs active (lighted) at any given time. Active LEDs require a LOW voltage because they are wired via a common anode.

Initially, my first course of action was to use the many inputs and outputs available on a Mega, but I wanted a multibyte solution because some of my implementations could exceed the total available.

So after careful research, I decided upon using the 74HC165 and 74HC595 shift registers. There are two of each daisy-chained.

While I am very successful at troubleshooting circuits, computer programs, etc., there are some great minds on this forum that could direct this Arduino newbie.

I have attached a schematic and source code for anyone who has the time to review my design. Your feedback is greatly appreciated.

#include <SPI.h>


SPISettings shiftRegisterSettings(2000000, MSBFIRST, SPI_MODE0);


// Standard SPI pins are used used for clock and data, but slave select can be any free pin
#define ST_CP_LATCH_PIN_IN   8
#define ST_CP_LATCH_PIN_OUT  9


// Input masks for all current detectors and switches
#define DA7_01        0b10000000
#define DA7_02        0b01000000
#define DA7_03        0b00100000
#define DA3_01        0b00010000
#define DA3_05        0b00001000
#define DA6_01        0b00000100
#define DA6_03        0b10000000
#define DA8_02        0b01000000
#define DA8_03        0b00100000
#define DA9_03        0b00010000
#define A135          0b00001000
#define DIRECTION_A   0b00000100


// Signal masks.
#define A1_1_GREEN        0b01111111
#define A1_1_YELLOW       0b10111111
#define A1_1_RED          0b11011111
#define A2_49_YELLOW      0b11110111
#define A2_49_RED         0b11111011
#define A3_1_GREEN        0b01111111
#define A3_1_YELLOW       0b10111111
#define A3_1_RED          0b11011111
#define A4_49_GREEN       0b11101111
#define A4_49_YELLOW      0b11110111
#define A4_49_RED         0b11111011


void setup()
{
    pinMode(ST_CP_LATCH_PIN_IN, OUTPUT);
    pinMode(ST_CP_LATCH_PIN_OUT, OUTPUT);
}


void loop()
{
    // Read (2) bytes from 74HC165 shift registers.
    byte bInputMask[2];
    SPI.beginTransaction(shiftRegisterSettings);
    digitalWrite(ST_CP_LATCH_PIN_IN, LOW);
    digitalWrite(ST_CP_LATCH_PIN_OUT, HIGH);
    bInputMask[0] = SPI.transfer(0);
    bInputMask[1] = SPI.transfer(0);
    SPI.endTransaction();
    
    // Initialize bytes to send to send to shift registers for LED control.
    // LEDS are wired using a common anode, so an active LED must have
    // zero voltage.  High voltage means an LED is not active.
    byte bSignalMask[2] = { 0b11111111, 0b11111111 };
    
    // Update all signals in the section.
    bSignalMask[1] &= Signal_A1_1(bInputMask);
    bSignalMask[1] &= Signal_A2_49(bInputMask);
    bSignalMask[0] &= Signal_A3_1(bInputMask);
    bSignalMask[0] &= Signal_A4_49(bInputMask);


    // Send bytes to (2) 74HC595 shift registers.
    SPI.beginTransaction(shiftRegisterSettings);
    digitalWrite(ST_CP_LATCH_PIN_OUT, LOW);
    SPI.transfer(bSignalMask[0]);
    SPI.transfer(bSignalMask[1]);
    digitalWrite(ST_CP_LATCH_PIN_OUT, HIGH);
    SPI.endTransaction();
}


// Update signal A1-1.
byte Signal_A1_1(byte bInputMask[])
{
    // Read inputs.
    int iDA3_01 = bInputMask[1] & DA3_01 ? HIGH : LOW;
    int iDA6_01 = bInputMask[1] & DA6_01 ? HIGH : LOW;
    int iDA7_01 = bInputMask[1] & DA7_01 ? HIGH : LOW;
    int iA135 = bInputMask[0] & A135 ? HIGH : LOW;


    // Combinations.
    boolean bCombo_1 = (iDA3_01 == LOW && A135 == HIGH);


    // Determine signal color.
    byte outByte;
    if (iDA7_01 == LOW && iDA6_01 == LOW && bCombo_1) outByte = A1_1_GREEN;
    if (iDA7_01 == LOW && iDA6_01 == LOW && !bCombo_1) outByte = A1_1_YELLOW;
    if (iDA7_01 == HIGH || iDA6_01 == HIGH) outByte = A1_1_RED;


    return outByte;
}


// Update signal A2-49.
byte Signal_A2_49(byte bInputMask[])
{
    // Read inputs.
    int iDA7_02 = bInputMask[1] & DA7_02 ? HIGH : LOW;
    int iDA8_02 = bInputMask[0] & DA8_02 ? HIGH : LOW;


    // Determine signal color.
    return (iDA7_02 == LOW && iDA8_02 == LOW) ? A2_49_YELLOW : A2_49_RED;
}


// Update signal A3-1.
byte Signal_A3_1(byte bInputMask[])
{
    // Read inputs.
    int iDA7_03 = bInputMask[1] & DA7_03 ? HIGH : LOW;
    int iDA3_05 = bInputMask[1] & DA3_05 ? HIGH : LOW;
    int iDA6_03 = bInputMask[0] & DA6_03 ? HIGH : LOW;
    int iA135 = bInputMask[0] & A135 ? HIGH : LOW;
    int iDIRECTION_A = bInputMask[0] & DIRECTION_A ? HIGH : LOW;


    // Combinations.
    boolean bCombo_1 = (iDIRECTION_A == LOW && iDA7_03 == LOW && iDA6_03 == LOW);
    boolean bCombo_2 = (iDA3_05 == LOW && A135 == HIGH);


    // Determine signal color.
    byte outByte;
    if (bCombo_1 && bCombo_2) outByte = A3_1_GREEN;
    if (bCombo_1 && !bCombo_2) outByte = A3_1_YELLOW;
    if (!bCombo_1) outByte = A3_1_RED;


    return outByte;
}


// Update signal A4-49.
byte Signal_A4_49(byte bInputMask[])
{
    // Read inputs.
    int iDA7_03 = bInputMask[1] & DA7_03 ? HIGH : LOW;
    int iDA8_03 = bInputMask[0] & DA8_03 ? HIGH : LOW;
    int iDA9_03 = bInputMask[0] & DA9_03 ? HIGH : LOW;
    int iDIRECTION_A = bInputMask[0] & DIRECTION_A ? HIGH : LOW;


    // Combinations.
    boolean bCombo_1 = (iDIRECTION_A == HIGH && iDA7_03 == LOW && iDA8_03 == LOW);


    // Determine signal color.
    byte outByte;
    if (bCombo_1 && iDA9_03 == LOW) outByte = A4_49_GREEN;
    if (bCombo_1 && iDA9_03 == HIGH) outByte = A4_49_YELLOW;
    if (!bCombo_1) outByte = A4_49_RED;


    return outByte;
}

First read the advice given by the first topics like "How to use this Forum" etc. They tell You how to attache pictures, code etc. Helpers don't want to download files from everyware. Some helpers use tablets, smartphones etc. and can't download and read files like .INO.

Yes, I was about to address this after reviewing other posts.

this is my first post

Please never post like that again.

Break your posts into paragraphs rather than putting everything into a single monolithic block which makes it very difficult to read and understand

Know that every experienced reader is a newbie regarding Your text.

Hopefully this is what is acceptable. Thank you for your feedback.

joep465w:
Hopefully this is what is acceptable. Thank you for your feedback.

Much better. Now I can actually read what you wrote and understand the questions which I will try to find time to look at later

If this was my project I would use a second Mega rather than all the complexity of shift registers.

...R

I saw recently that this thread is specifically aimed at model railroaders; there may be something specific for you there?

A couple of things that might or might not be mistakes:
On line 89, you define a local variable iA135, then never use it. The same occurs at line 127 (same local variable name, different function). Both functions later use A135, which was defined globally, not sure if you intended to use the local variable or the global one.

Not going to affect anything except the size of the code, but you define a lot of the local variables in the functions as int, then use them as boolean.

When building something like this, the Nano or Pro Mini would probably be a better choice than the Uno because of the smaller size, and the rather non-standard layout of the Uno where the connectors will not all fit a standard 0.1" grid.

Do you have a working prototype?

I do not have working prototype yet.

As far as my code, the A135 in my expression should be iA135. Thank you for noticing that error.

I will investigate the other Arduino products and see if they are a better fit for my project, although I have an Uno on order.

My primary concern is the correct use of the shift registers and the corresponding code associated with reading the data.

joep465w:
My primary concern is the correct use of the shift registers and the corresponding code associated with reading the data.

If you have not already built the circuit board with the shift registers I strongly suggest that you consider how much time that will take you and compare that to the relatively small cost of a Mega clone.

...R

Google this :

Open-Smart PCF8575 IO Expander Board Module I2C to 16IO For Arduino

These are easy to use and do inputs or outputs

Here is another schematic that eliminates the input shift registers.

Railway enthusiast here. How many lights are on a single mast, for subways? The worst case is North American railways, that can have as many as 3 red/green/amber and in rare cases, a lunar in addition for a total of 10 lights. But the abbreviated version uses at most 2 r/g/a for a total of 6. I have been studying the set of basic connections that a signal head module would need. For basic ABS signalling, there are enough pins on an Arduino to drive the 6 mentioned lamps twice, and monitor the track occupancy inputs also. There are commercially available modules that handle one mast, but I have concluded that it can be doubled up so that one processor can easily handle the entry signals on both ends of a block. That is all without any resort to external I/O multiplexing such as with shift registers. I already have working prototypes that feature a tungsten thermal lag simulation as well as LED brightness trimming in software (because the different LED types often have different luminous efficiencies and voltage drops). I consider those features more than worth the expenditure in processors.

Does Your calendar say april the first already?
Just saw a 6 light railroad signal at home. 1 white, 3 green and one green was blinking. How to interpret that?

Railroader:
Does Your calendar say april the first already?
Just saw a 6 light railroad signal at home. 1 white, 3 green and one green was blinking. How to interpret that?

Is it St. Patricks day yet? What have you been drinking? :slight_smile:

There's no St. Patrick in my country. April first is the closest….

I noticed in your schematic you're using an arduino UNO, which has far less I/O than a mega. I counted 12 Inputs and 11 Outputs. (23 I/O)

A mega has 54 I/O pins which seems to be plenty for what you might want in the future. You're the designer so you'd know better though.

Also, are these lights low power LED's? The board is going to have a maximum amount of current it can supply that will limit the number of LED's you can light up at a time. If they're signals you would just count the most number of LED's on at a time and multiply by the current consumption of one LED. (Assuming they are all basically the same)