Thursday, January 17, 2013

Simple Signal Solution

The idea for this project came to me a couple of years ago to design a simple and inexpensive solution for three color, bi-directional signaling.  I was sure that microcontrollers could implement the code and drive the LEDs; the problem with this plan was the inexpensive part - that was until the LaunchPads came along. So this is what I call the Simple Signal Solution.

In a simple block signaling system, at the eastern entrance to a block there is a westbound signal, conversely at the western entry to the block there is an eastbound signal. When the block is unoccupied both signals are green.  However if the block is occupied both signals indicate red, the signals in the blocks immediately to the east and west,    in the direction heading towards the occupied block, indicate yellow and the blocks beyond that indicate green.

Hardware Required for this project;
  
  • LaunchPads (MSP430G2553 chip) - 1 for each block
  • LEDs - (2) Red, (2) Yellow, (2) Green for each block or (2) signals with built-in LEDs for each block.
  • Resistors - (6) 270-300 ohm min. (higher to reduce LED brightness) for each block.  
A note about LEDs and resistors.  I know that some folks do not use resistors when driving LEDs with the LaunchPad. I did this too when I first programmed one.  However I left an LED flashing for several days and eventually the LED started to act flaky.  Then I saw on the LaunchPad's schematic that TI uses resistors with the on-board LEDs. If TI thinks that it's necessary, that's good enough for me. 


This project requires several of the available pins on the LaunchPad to be assigned as inputs or outputs.  I made up a table to keep them all straight.

 
Pin Port Assignment Direction Sense





2 P1.0 (and LED1) Eastbound Red Indication Output Active High
6 P1.4 Eastbound Yellow Indication Output Active High
7 P1.5 Eastbound Green Indication Output Active High
8 P2.0 Westbound Red Indication Output Active High
9 P2.1 Westbound Yellow Indication Output Active High
10 P2.2 Westbound Green Indication Output Active High






Pin Port Assignment Direction Sense










15 P1.7 THIS_BLOCK_OCCUPIED Input Active low
14 P1.6 (and LED2) OCCUPANCY_DETECTION_TO_EAST_BLOCK  (Occupancy detection TO next east block) Output Active low
13 P2.5 EAST_BLOCK_OCCUPIED (Occupancy detection FROM next east block) Input Active low
12 P2.4 OCCUPANCY_DETECTION_TO_WEST_BLOCK (Occupancy detection TO next west block) Output Active low
11 P2.3 WEST_BLOCK_OCCUPIED (Occupancy detection FROM next west block) Input Active low



That's a lot of pins to keep straight, but not too bad once you get it all into your head.                                            

The schematic diagram below shows the pins on the LaunchPad  headers and how to interconnect the LaunchPads so that the signals to and from the east and west get to their appropriate pins.


Pin 15 is brought to ground by an external circuit (e.g. a train detector) to tell the LaunchPad that the block is occupied.  Pin 14 outputs the status of it's train detection to the LaunchPad to the east, status from the east is received on pin 11.  Similarly Pin 12 outputs the current block status to the LaunchPad to the west on pin13 and status from the west is received on pin 12.

[Author's post-script.  I designed this circuit for common cathode LEDs, because that's the way I like to wire mine.  If  you are using commercial signals/signal heads they may come wired as common anode.  If they are, this code will not work.  I'll modify this code for use with common anode signals at a future date.]

You'll notice that just four wires interconnect the LaunchPads; two going east and two going west.  Compare that to the interconnects between the components of the commercial signal systems.  All of the LaunchPads may have to be powered by the same source to make sure that you have a common ground between them OR you have to tie the grounds between the supplies together.

Here's a video of the circuit in action. 






If you are not a programmer, you now have all of the data that you need to grab the code and go.  So here is a link to the code.

Simple Signal Solution


Code Walkthrough

If you are inclined to program, read on as I will walk you through the code.  The code begins with the usual declaration that this will be targeted to the MSP430G2553 processor.  Some loop counters and delay variables are defined as are some short intergers that will be used as logicals (0 or 1) to hold the status of the block occupancies.

The program enters the main program and the watchdog timer is turned off.   Then the input pins for the block occupied status are initialized to input; have resistors connected to them and finally have the resistors set as 'pull-ups'.  The MSP430G2553has internal resistors that can be set as pull-up or pull down.  Since these inputs are active low, the resistors are used to 'pull-up' (make high) the input pins until an external signal pulls them down.  Otherwise the pins would 'float' between high and low and may give false readings.

These block occupied pins are connected to either the train detector for the current block, the output of the LaunchPad to the east or the output of the LaunchPad to the west.  Signals on these pins will be captured and internal variable set based on their state below.

The the output pins are set as outputs; the six pins driving the LEDs are set to high (on) and the two outputs of the current  block's train detection status - OCCUPANCY_DETECTION_TO_EAST_BLOCK and OCCUPANCY_DETECTION_TO_WEST_BLOCK are set to high (the comments mistakenly say 1) which is inactive since these pins are active low.

A delay loop follows for a 'lamp check'.  The LED outputs have been set to the 'on' state above, and the delay loop holds that state for about 2 seconds.


Next we clear the block occupancy variables by setting them to 0, which will be interpreted as a logical false.

This finishes the set up and the program enters an infinite loop that will execute until power is removed.



Next the block occupied status for the current block, the block to the east and the block to the west is determined. If a block is occupied it is set to '1' which will be interpreted as a logical true and if the block is unoccupied; the value is set to '0' which will be interpreted as a logical false.

Next the code signals the blocks to the east and the west whether this block is occupied so that they can set their signals accordingly.

Now the signal-setting logic of the code begins. You can read the comments in the code for yourself.  All combinations of the 3 block occupied variables are tested and the signals set accordingly.

Finally, the signal is latched for a while to provide for some debounce.  The infinite loop then begins again with the clearing of the block occupied variables and the reading of the block status, etc.



Modifications & Extensions


While this code is fully functional as a signal system, it can still benefit from some modifications.  Bashing this code together with Hoffy's IR detector would make for an interesting combination.  Similarly combining this with code for a reflective IR detector would also make a better product.  If you attempt either of these think about including a time delay to latch the 'train detected' signal to account for the inter-car dropout common to all optical detection systems.

You could also include code to control the signals on sidings adjacent to the main.  In fact, the most involved signal complex that you can imagine is probably not beyond the capabilities of this relatively simple microcontroller.  Of course, you may not have enough pins to drive all of those signals!

Enjoy!

In the code listing that follows, I've given up trying to keep proper formatting.  Do you'll have to follow along as best that you can or you can download the properly formatted code from the link above.  Do not try to copy and paste this code.  It will not work!

 


//////////////////////////////////////////////////////////////////////////////

//                                                                         ////

//                                                                         ////

//            SIMPLE SIGNAL SOLUTION                   ////

//                                                                          ////

//            THREE-COLOR, BI-DIRECTIONAL SIGNAL LOGIC        

//                                                                             ////

//            COPYRIGHT 2012 T. TERRANCE               ////

//                                                                             ////

//            Provided under a Creative Commons Attribution,       
//                                 

//            Non-Commercial, Share Alike,3.0 Unported License    

//                                                                             ////

//            TARGETED TO MSP430 LANUCHPAD W/MSP430G2553 PROCESSOR            /                                                                             ////

//                                                                             ////

//////////////////////////////////////////////////////////////////////////////













/*

 * main.c

 */

#include <msp430g2553.h>







volatile long t=0;                        //Define loop counter t and set it to 0

volatile long delay=15000;                //Define delay waiting period used to latch

// the signal indication and set it to 15000

volatile long start_up_delay = 50000;         //Define delay waiting period used to flash

// all of the signals at start-up and set it to

//50000





////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            ////

//            Initialize Logical Variables for Block Occupancy                                 ////

//                                                                                           ////

////////////////////////////////////////////////////////////////////////////////////////////



short int THIS_BLOCK_OCCUPIED;              //if this block is occupied the value is set to

//true (non-zero)

short int EAST_BLOCK_OCCUPIED;              //if the next block to the east is occupied the value

//is set to true (non-zero)

short int WEST_BLOCK_OCCUPIED;              //if the next block to the west block is occupied

//the value is set to true (non-zero)









////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            ////

//                                 BEGIN MAIN PROGRAM                                        ////

//                                                                                            ////

////////////////////////////////////////////////////////////////////////////////////////////





void main(void){

















WDTCTL = WDTPW + WDTHOLD;  //Stop Watchdog Timer















///////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            ///

//                   Initialize Ports AND Signal Indications                                  ///

//                                                                                            ///

///////////////////////////////////////////////////////////////////////////////////////////









P1DIR &= ~BIT7;               // sets THIS_BLOCK_OCCUPIED pin (Port 1, bit 7) to input

P2DIR &= ~BIT5;               // sets EAST_BLOCK_OCCUPIED pin (Port 2, bit 5) to input

P2DIR &= ~BIT3;               // sets WEST_BLOCK_OCCUPIED pin (Port 2, bit 3) to input

P1REN |= BIT7;                // sets pull-up resistor on THIS_BLOCK_OCCUPIED (Port 1, bit 7) input pin

P2REN |= BIT5;                // sets pull-up resistor on EAST_BLOCK_OCCUPIED (Port 2, bit 5) input pin

P2REN |= BIT3;                // sets pull-up resistor on WEST_BLOCK_OCCUPIED (Port 2, bit 3) input pin





P1OUT |= BIT7;                // sets pull-up resistor on THIS_BLOCK_OCCUPIED (Port 1, bit 7) to pull-up

P2OUT |= BIT5;                // sets pull-up resistor on EAST_BLOCK_OCCUPIED (Port 2, bit 5) to pull-up

P2OUT |= BIT3;                // sets pull-up resistor on WEST_BLOCK_OCCUPIED (Port 2, bit 3) to pull-up









P1DIR |= BIT0;                       //sets EASTBOUND RED SIGNAL pin #2 (Port 1, Bit 0) to output and set to on

P1OUT |= BIT0;



P1DIR |= BIT4;                       //sets EASTBOUND YELLOW SIGNAL pin #6 (Port 1, Bit 4) to output and set to on

P1OUT |= BIT4;



P1DIR |= BIT5;                       //sets EASTBOUND GREEN SIGNAL pin #7 (Port 1, BIT 5) to output and set to on

P1OUT |= BIT5;



P2DIR |= BIT0;                //sets WESTBOUND RED SIGNAL pin #8 (Port 2, BIT 0) to output and set to on

P2OUT |= BIT0;



P2DIR |= BIT1;                //sets WESTBOUND YELLOW SIGNAL pin #9 (Port 2, Bit 1) to output and set to on

P2OUT |= BIT1;



P2DIR |= BIT2;                //sets WESTBOUND GREEN SIGNAL pin #10 (Port 2, BIT 2) to output and set to on

P2OUT |= BIT2;



P1DIR |= BIT6;                //sets OCCUPANCY_DETECTION_TO_EAST_BLOCK pin to output and sets it to 1 (inactive)

P1OUT |= BIT6;



P2DIR |= BIT4;                //sets OCCUPANCY_DETECTION_TO_WEST_BLOCK pin to output and sets it to 1 (inactive)

P2OUT |= BIT4;





/////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            /////

//     DELAY WITH ALL SIGNALS ILLUMINATED FOR "LAMP CHECK"                                /////

//                                                                                            /////

/////////////////////////////////////////////////////////////////////////////////////////////





for (t; t < start_up_delay; t++)  //delay loop

              {



              }

        t=0;













/////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            /////

//                   Clear Block Occupancy Variables                                           /////

//                                                                                            /////

/////////////////////////////////////////////////////////////////////////////////////////////





THIS_BLOCK_OCCUPIED=0;

EAST_BLOCK_OCCUPIED=0;

WEST_BLOCK_OCCUPIED=0;







/////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            /////

//                   BEGIN MAIN LOOP                                                         /////

//                                                                                            /////

/////////////////////////////////////////////////////////////////////////////////////////////







while (1)

{













//////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            //////

//            Read Block Occupancy in This Block, Block to the East                    //////

//                          and Block to the West                                         //////

//                                                                                            //////

//////////////////////////////////////////////////////////////////////////////////////////////





if ((P1IN & BIT7) == 0)                                      //Test Port 1 input, bit 7 and if zero (active low)

{

       THIS_BLOCK_OCCUPIED = 1;                               // set THIS_BLOCK_OCCUPIED to a non-zero value (true)

}

else

{

       THIS_BLOCK_OCCUPIED = 0;                               // set THIS_BLOCK_OCCUPIED to zero (false)

}





if ((P2IN & BIT5) == 0)                                      //Test Port 2 input, bit 5 and if zero (active low)

{

       EAST_BLOCK_OCCUPIED = 1;                               // set EAST_BLOCK_OCCUPIED to a non-zero value (true)

}

else

{

       EAST_BLOCK_OCCUPIED = 0;                               // set EAST_BLOCK_OCCUPIED to zero value (false)

}





if ((P2IN & BIT3) == 0)                                      //Test Port 2 input, bit 3 and if zero (active low)

{

       WEST_BLOCK_OCCUPIED = 1;                               // set WEST_BLOCK_OCCUPIED to a non-zero value (true)

}

else

{

       WEST_BLOCK_OCCUPIED = 0;                               // set WEST_BLOCK_OCCUPIED to zero value (false)

}















///////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                                                                                               ///

//                          SEND LOCAL BLOCK OCCUPANCY STATUS TO BLOCKS EAST AND WEST                                  ///

//                                                                                                                                                               ///

///////////////////////////////////////////////////////////////////////////////////////////////





if (THIS_BLOCK_OCCUPIED > 0)         //local block occupied

{

       P1OUT &= ~BIT6;                   //set OCCUPANCY_DETECTION_TO_EAST_BLOCK to 0 (active)

       P2OUT &= ~BIT4;                   //set OCCUPANCY_DETECTION_TO_WEST_BLOCK to 0 (active)

}

else                                      //local block not occupied

{

       P1OUT |= BIT6;                    //set OCCUPANCY_DETECTION_TO_EAST_BLOCK to 1 (inactive)

       P2OUT |= BIT4;                    //set OCCUPANCY_DETECTION_TO_WEST_BLOCK to 1 (inactive)

}







///////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                                ///

//            Begin Main Signal Logic                                                          ///

//                                                                                                ///

///////////////////////////////////////////////////////////////////////////////////////////////











       if (THIS_BLOCK_OCCUPIED > 0)         //If this block is occupied then set all signals red, clear other indications



              {

                     P1OUT |= BIT0;             //turn on EASTBOUND RED SIGNAL

                     P1OUT &= ~BIT4;            //turn off the EASTBOUND YELLOW SIGNAL

                     P1OUT &= ~BIT5;            //turn off the EASTBOUND GREEN SIGNAL

                     P2OUT |= BIT0;             //turn on WESTBOUND RED SIGNAL

                     P2OUT &= ~BIT1;            //turn off the WESTBOUND YELLOW SIGNAL

                     P2OUT &= ~BIT2;            //turn off the WESTBOUND GREEN SIGNAL

              }



       else if ( (EAST_BLOCK_OCCUPIED > 0) & (WEST_BLOCK_OCCUPIED > 0)  ) //blocks east and west of here are occupied

                                                                             //east and west signals set to yellow

                                                                             //clear other indications

              {

                     P1OUT &= ~BIT0;            //turn off EASTBOUND RED SIGNAL

                     P1OUT |= BIT4;             //turn on EASTBOUND YELLOW SIGNAL

                     P1OUT &= ~BIT5;            //turn off the EASTBOUND GREEN SIGNAL

                     P2OUT &= ~BIT0;            //turn off WESTBOUND RED SIGNAL

                     P2OUT |= BIT1;             //turn on the WESTBOUND YELLOW SIGNAL

                     P2OUT &= ~BIT2;            //turn off the WESTBOUND GREEN SIGNAL

              }





       else if (EAST_BLOCK_OCCUPIED > 0)           //only the block east of here is occupied

                                                 //eastbound is yellow, westbound is green

                                                 //clear other indications

              {

                     P1OUT &= ~BIT0;            //turn off EASTBOUND RED SIGNAL

                     P1OUT |= BIT4;             //turn on EASTBOUND YELLOW SIGNAL

                     P1OUT &= ~BIT5;            //turn off the EASTBOUND GREEN SIGNAL

                     P2OUT &= ~BIT0;            //turn off WESTBOUND RED SIGNAL

                     P2OUT &= ~BIT1;            //turn off the WESTBOUND YELLOW SIGNAL

                     P2OUT |= BIT2;             //turn on the WESTBOUND GREEN SIGNAL

              }



       else if (WEST_BLOCK_OCCUPIED > 0)       //only the block west of here is occupied

                                                 //westbound is yellow, eastbound is green

                                                 //clear other indications

              {

                     P1OUT &= ~BIT0;            //turn off EASTBOUND RED SIGNAL

                     P1OUT &= ~BIT4;            //turn off the EASTBOUND YELLOW SIGNAL

                     P1OUT |= BIT5;             //turn on the EASTBOUND GREEN SIGNAL

                     P2OUT &= ~BIT0;            //turn off WESTBOUND RED SIGNAL

                     P2OUT |= BIT1;             //turn on the WESTBOUND YELLOW SIGNAL

                     P2OUT &= ~BIT2;            //turn off the WESTBOUND GREEN SIGNAL

              }



       else                                      //all blocks clear - all signals green

                                                 //clear other indications

              {

                     P1OUT &= ~BIT0;            //turn off EASTBOUND RED SIGNAL

                     P1OUT &= ~BIT4;            //turn off the EASTBOUND YELLOW SIGNAL

                     P1OUT |= BIT5;             //turn on the EASTBOUND GREEN SIGNAL

                     P2OUT &= ~BIT0;            //turn off WESTBOUND RED SIGNAL

                     P2OUT &= ~BIT1;            //turn off the WESTBOUND YELLOW SIGNAL

                     P2OUT |= BIT2;             //turn on the WESTBOUND GREEN SIGNAL

              }





/////////////////////////////////////////////////////////////////////////////////////////////

//                                                                                            /////

//     Latch the final signal indications for a time to prevent "chatter"               /////

//                                                                                            /////

/////////////////////////////////////////////////////////////////////////////////////////////









        for (t; t < delay; t++)  //delay loop

              {



              }

        t=0;

}



//end of main



head.

8 comments:

  1. Very cool Terry! Just wanted to point out that the signal to the east and west blocks that THIS block is occupied is the same signal and can be combined onto a single pin, thus freeing up another pin in case you are going to add detection onto this same launchpad.

    ReplyDelete
  2. In fact the signal you are using to tell THIS launchpad that THIS block is occupied could go directly to the signalling to the east and west blocks, freeing up another pin!!!

    ReplyDelete
  3. Toni:

    You are absolutely right! But that's the beauty of this LaunchPad effort vs. commercial products, you can customize the functionality to suit your situation.

    I did have a couple of reasons for doing it this way. First, not every train detector is active low. I chose active low because I have a bunch of active low optical detectors (borrowed from the robotics community). By bringing the signal into the LaunchPad, you can accommodate detectors of either sense.

    Second, I wanted this code to the the foundation for more complex signaling situations (e.g. junctions, crossings and interlockings) and in these cases this detector's state will have to be combined (logically) with other detectors to arrive at the final signal indication.

    But as I said, customization is the name of the game so, as they say at Burger King - "have it your way."

    Terry

    ReplyDelete
  4. really cool Terry. I am a newbie to all this.just got a couple of launchpads recently delivered to New Zealand freight free. I have managed to load the drivers and have installed code composer studio v5. It was quite exciting when i connected the launchpads to my PC for the first time . I want to have a fairly basic signalling system ,as you described, on my NZ120 scale modular layout. still getting modules set up with track,etc. I have no train detectors as yet for blocks, but am using optical detectors for my purchased railway crossing module. They seem to work great, so will probably try and get some more for blocks. Only one three light signal so far. I purchased a few 2mm LEDs in the 3 colours i need and was planning on building some more signals.we will see how that works out in NZ120.So many questions, First one is what are you using to power the launchpads and how do you combine 2 on the same power.I have an old desktop in my shed with a usb connection on the back that i have added a type of usb tree to it with about 5 connections available .would that work? My head hurts from all the input at present. In my 50's , and electronics is fascinating to me , but very challenging. Oh , I am running a NCE powercab.thanks again John

    ReplyDelete
    Replies
    1. John:

      Easiest answer first. Your USB 'tree' (called a 'hub' here in the US) will work. It will supply power to your LaunchPads. One variation that you may want to consider is to buy a 'powered' hub. This type of hub has its own power suppoy and you can then power your LaunchPads without your computer. There is a tab at the top of the LaunchPad blog labeled "Powering your LaunchPads"; click on it and you'll be taken to a page that explains all of this.

      I have used this infrared reflective sensor under teh ties shining up to detect the train. It may be a little large for 1/120 narrow gauge.

      http://www.goodluckbuy.com/infrared-ir-reflectance-sensor-module-for-smart-car-lm393-.html

      One complication with this sensor is that it works on 5V, while the LaunchPad works on 3V, so a separate power supply has to be provided for the sensors. I noticed this other sensor that does work on 3V.

      http://www.goodluckbuy.com/dc-3v-6v-reflect-infrared-ir-sensor-barrier-photoelectric-switch-switching.html

      I will order one of these and if it works well with the LaunchPad I'll do a project with it.

      Don't be daunted by the LaunchPads and your age. I have a few years on you and I can still work with these no problem. I intend to keep this blog simple for all to use.

      Terry

      Delete
  5. Thanks Terry. i found the link to powering launchpad after i had finished my comments.duh!
    I had a look at the links above and wondered how you would mount them under installed Atlas code 55 N scale track .For the crossing module ,it has optical sensors, but the tiny light unit is about 3mm in diameter with 2 wires running to the circuit board and you just drill a slightly smaller hole between sleepers so it sits level with the sleepers. hardly noticeable. I guess the units you mention are a different design or is there a way of using wires from these units to mount sensor under track. Like i said newbie!! John

    ReplyDelete
  6. Hi Terry,Its been a while .other projects have kept me busy. I was going to order a few of the 3Vsensors you mentioned above as well as a powered hub from the same site. Is this suitable
    http://www.goodluckbuy.com/10-ports-usb-2-0-hub-high-speed-with-power-adapter.html
    I would have to provide an adapter plug to fit our electrical wall sockets. cheers John

    ReplyDelete
  7. I really enjoyed this site. This is such a Great resource that you are providing and you give it away for free. It gives in depth information. Thanks for this valuable information. Visit... cell phone detector

    ReplyDelete