Part V - Microcontroller Integration & Software

revised 12-28-06

All of the things that have been described and utilized so far need to be pulled together by an intelligent device, a PICAXE microcontroller, and a program that tells it what to do. 

When  I start a project like this I like to come up with a list of things that I would like the finished device to do.  The list of objectives below are the things that the microcontroller must do to manage the proper operation of all of the individual modules.


The microcontroller that ties together the parts of this system must be able to do many things.  Among them are:

  1. Sense when a train gets to the end of a block (see part IV, sensors)
  2. Control turnout motors to direct trains into a particular siding (see part II, turnout control)
  3. Turn the power to a block on or off (see part I, controlling blocks)
  4. Display information on the system's status on an LCD readout (discussed in this section)
  5. Interact with the operator (discussed in this section)
  6. Recognize and react to error situations (discussed in this section)


A flowchart is another tool that helps in software design.  This list of tasks is a text based flowchart that shows what the software does:

  1. Reset all block relays and set counters to zero
  2. Are all three trains waiting at their sensors?
  3. If no move each train forward a bit and go back to step 2
    - if yes continue
  4. Set turnouts to their starting positions
  5. Send train 1 out by activating block 1's relay
  6. Has train 1 cleared its sensor?
  7. If no go back to step 6
    - if yes continue
  8. Set switches so train 1 returns to its proper siding
  9. Has train 1 returned?
  10. If no go back to step 9
    - if yes deactivate block 1 and continue
  11. Send train 2 out by activating block 2's relay
  12. Has train 2 cleared its sensor?
  13. if no go back to step 12
    - if yes continue
  14. Set switches so train 2 returns to its siding
  15. has train 2 returned?
  16. if no go back to step 15
    - if yes deactivate block 2 and continue
  17. send train 3 out
  18. has train 3 cleared its sensor?
  19. if no go back to step 18
    - if yes continue
  20. Set switches so train 3 returns to its siding
  21. has train 3 returned?
  22. if no go back to step 21
    - if yes deactivate block 3 and continue
  23. Report laps
  24. go to step 5 and repeat

As you can see the logic is not too complex.  In addition to what is listed above the software continuously does three other things:

  1. Displays each train's status on the LCD display (which train is out, has it cleared its sensor, how many laps completed, etc)
  2. Checks the trains that are not running to make sure that they are still at their sensors.  If it finds that a train is not where it is supposed to be all blocks are shut down and an error message is reported.
  3. Looks for activation of a radio controlled switch that tells the system to stop all trains once the currently running engine returns to its block sensor.  This is an easy for the operator to shut things down at the end of the day.

The Circuit

The schematic below should look familiar since we have used parts of it in the other articles.  In this diagram all of the pieces are connected to the PICKAXE 28X.  This device is similar to the other PICAXE chips that we have used except that it has 28 pins.  I chose to use it because of the number of devices that need to be observed and controlled.  The cryptic notes that you see on the schematic (such as bw / ow and sensor3=green) are there to help me remember what color wire I used to connect various things (bw=blue/white wire, ow=orange/white, etc)

The one thing that is not included in the schematic is the LCD display.  To simplify the project and speed its time to completion I opted to use an external LCD display controller.  This is a chip from that accepts serial input at 2400 baud and displays the information that it receives on a 4 line X 20 character backlit LCD display.  A complete description of the chip and its use is available at: .  This unit and others from Peter Anderson are highly recommended.


The software listing is below.  It is fairly well commented and, for the most part, follows the logic that is described above.  The items following SEROUT are text strings and commands that go to the LCD controller.

'd. bodnar 9-17-06
symbol relay1 = 0
symbol relay2 = 1
symbol relay3 = 2
symbol sensor1 = pin5
symbol sensor2 = pin6
symbol sensor3 = pin7
symbol EnterSw = pin2    'pin 13 on PICAXE - 1 when out, 0 when pushed
symbol MenuSw = pin3     'pin 14 on PICAXE - 1 when out, 0 when pushed
symbol EndSwitch = pin4  'pin 15 on PICAXE - 1 when out, 0 when pushed
symbol swmotor1 = 3
symbol swmotor2 = 4
symbol swdir1 = 5        'h-bridge
symbol swdir2 = 6        'h-bridge
symbol nextloop = b4
symbol temp0 = b3
symbol temp1 = b5
symbol temp2 = b2
symbol temp3 = b6
symbol tempCL = b7       'temp storage of Current Loop Engine
symbol Laps = w6
symbol EndFlag = bit8
symbol SwOneFlag = bit9
symbol SwTwoFlag = bit10
symbol SwThreeFlag = bit11
symbol versionW = 3
symbol versionD = 6
symbol dlay = 120
symbol SensorValue = b8  '= %01100000 ' %11100000 for 3 trains and %01100000 for 2 trains
symbol PinsByte = b0     'using b0 allows us to access each sensor bit with bit5, bit6 & bit7
NextLoop = 0             'use 0-2 in stead of 1-3 for branch statement
'NOTE SensorValue must be set on first run with MENU!!!!!!!!!!!!!!!!!!!
read 0, SensorValue
if SensorValue <>%11100000 and SensorValue<>%01100000 then fixSensorValue
goto NoNeedtoFix
SensorValue=%11100000    'set to 3 trains if read is bad
pause 5000               'pause while LCD wakes up and does test display
if SensorValue=%11100000 then engines3a
temp0=2:goto skipover1a:

serout 7, t2400,("?f?c0"):pause 200:serout 7,t2400,("?f?c0"):pause 200 '?c0 turns cursor off
serout 7, t2400,("?Bff"):pause 200
serout 7,T2400,("Switch Controller?nd. bodnar 9-2006?nVersion ",#versionW,".",#versionD," Trains=",#temp0)
pause 2000 'pause while LCD wakes up and does test display
serout 7, T2400,("?x00?y3 MENU FOR SETTINGS")

for temp1=1 to 100
if MenuSw=0 then goto MENU
pause 30
next temp1
serout 7, T2400,("?fResetting Switches")
gosub SwitchesThree:gosub SwitchesTwo:gosub SwitchesOne ' set for initial run
serout 7, T2400,("?f")

PWMOut 1, 25, 52 ' 38.4 kHz to IR emitters
temp1=pins & SensorValue
PWMOut 1,0,0
if temp1 <> SensorValue then ShowError
low relay1:low relay2:low relay3
goto start1:

serout 7,t2400,("?x00?y0Train1= ")
temp0=temp1 & %00100000
if temp0=0 then closed1
low relay1 'block off
serout 7,T2400,("OK "):goto show2
closed1:serout 7,T2400,("open")
if SwOneFlag=1 then SkipOver1C
gosub SwitchesOne 'switch set for train 1
high relay1:low relay2:low relay3 'block on/others off
goto Start:'recheck

serout 7,t2400,("?x00?y1Train2= ")
temp0= temp1 & %01000000
if temp0=0 then closed2
low relay2
serout 7,T2400,("OK "):goto show3
closed2:serout 7,T2400,("open")
if SwTwoFlag=1 then SkipOver2C
gosub SwitchesTwo
high relay2:low relay1:low relay3:
goto Start:

if SensorValue=%01100000 then done:
serout 7,t2400,("?x00?y2Train3= ")
temp0=temp1 & %10000000
if temp0=0 then closed3
low relay3
serout 7,T2400,("OK "):goto done
closed3:serout 7,T2400,("open")
if SwThreeFlag=1 then SkipOver3C
gosub SwitchesThree
high relay3:low relay1:low relay2
goto start:

low relay1:low relay2:low relay3
pause 500
goto start:

serout 7,T2400,("?fAll Trains Ready!?n?nPress ENTER?nto START")
if endswitch=1 and EnterSw =1 then loop:
serout 7,T2400,("?f")

if endflag=1 then Start1
Branch NextLoop, (one, two, three)
serout 7,T2400,("?fnextloop= ",#nextloop):serout 7,T2400,("?nERROR FIX ?nPausing 60 seconds"):pause 60000
nextloop=0:goto start2 'error trap - sometimes hung here

gosub SwitchesOne
high NextLoop       'start train on block # NextLoop +1
goto AddOneToNextLoop

gosub SwitchesTwo
high NextLoop       'start train on block # NextLoop +1
goto AddOneToNextLoop

gosub SwitchesThree:
high NextLoop       'start train on block # NextLoop +1
goto AddOneToNextLoop

TempCL=NextLoop     'stores Train Running for possible nudge adjust below
NextLoop=NextLoop + 1
serout 7,T2400,("?x00?y0Train Out=", #NextLoop, " Lap=",#Laps)
serout 7,T2400,("?x00?y1 ?x00?y2 ") 'clear lines 2 & 3
if nextloop= 2 and SensorValue = %01100000 then FixIt
if NextLoop = 3 and SensorValue=%11100000 then FixIt
goto Wait2Clear

temp3=NextLoop      'store for use in Wait2Clear routine
goto Wait2Clear

' wait for train to clear sensor 'may need to add a pause, too (see next note)
for temp2=1 to 255 'Was 100 - increased to insure that a car with big breaks won't show end at low speed
PWMOut 1, 25, 52 ' 38.4 kHz to IR emitters
temp1=pins & SensorValue
PWMOut 1, 0,0
if temp1 = SensorValue then Wait2Clear
next temp2
serout 7,T2400,("?x00?y1Sensor ",#temp3," Cleared")

'wait for train to return to end of block and block sensor
serout 7,T2400,("?x00?y2Waiting for Return ")
gosub CheckEndSwitch:
gosub CheckForMultipleSensorsOpen:
'if endflag=1 then Start:
PWMOut 1, 25, 52 ' 38.4 kHz to IR emitters
temp1=pins & SensorValue
PWMOut 1, 0,0
if temp1 <> SensorValue then Wait4Return
low relay1:low relay2:low relay3

pause 1000
PWMOut 1, 25, 52 ' 38.4 kHz to IR emitters
temp1=pins & SensorValue
PWMOut 1, 0,0
if temp1 <> SensorValue then NudgeForward
goto start2:
serout 7,T2400,("?x00?y2Nudging... ")
high tempCL
pause 300:low tempCL
goto DoubleCheck

high swdir1:low swdir2
high swmotor1:pause dlay
low swmotor1
serout 7,T2400,("?x00?y3Sw1=Strt")

high swdir1:low swdir2
high swmotor2:pause dlay
low swmotor2
serout 7,T2400,("?x11?y3Sw2=Strt")

high swdir2:low swdir1
high swmotor1:pause dlay
low swmotor1
serout 7,T2400,("?x00?y3Sw1=Turn")

high swdir1:low swdir2
high swmotor2:pause dlay
low swmotor2
serout 7,T2400,("?x11?y3Sw2=Strt")

high swdir2:low swdir1
high swmotor2:pause dlay
low swmotor2
serout 7,T2400,("?x11?y3Sw2=Turn")

if endswitch=0 or MenuSW=0 and EnterSW=0 then setendflag
serout 7,T2400,("?fEnd Flag Set?n STOPPING?n at End of Lap")

for temp0=1 to 5 'check 5 times to make sure reading is valid
PWMOut 1, 25, 52 ' 38.4 kHz to IR emitters
temp1=pins & SensorValue
PWMOut 1, 0,0
if SensorValue=%11100000 and temp1<>0 and temp1<>32 and temp1<>64 and temp1<>128 then NoBadSensors:
if SensorValue=%01100000 and temp1<>0 then NoBadSensors:
next temp0

serout 7,T2400,("?fBAD SENSOR?n STOPPING?n ALL")
low relay1:low relay2:low relay3
Pause 3000
goto start


serout 7,T2400,("?f MENU")
pause 1000
if SensorValue=%11100000 then engines3
temp0=2:goto skipover1:
serout 7,T2400,("?f# of Engines Now = ",#temp0)
serout 7,T2400,("?nENTER to Change?nMENU to Skip")
if MenuSW=0 then skipover2:
if EnterSW=0 then ChangeEngineNumber
goto CheckSwitch1

if SensorValue=%11100000 then EnginesTo2:
SensorValue=%11100000:write 0,SensorValue
serout 7,T2400,("?fNEW VALUE WRITTEN"):pause 1000
goto MENU:
SensorValue=%01100000:write 0,SensorValue
serout 7,T2400,("?fNEW VALUE WRITTEN"):pause 1000
goto MENU

goto start

User Interface

The user interacts with the unit through two buttons on the control panel, Menu and Enter, and a third that connects via the hand held Aristo-Craft remote control unit.  The LCD display continuously shows each engine's progress as it navigates the layout.  It also shows the switch status and number of laps that have been completed.  The three "DIODE VOLTAGE CONTROL" switches control the speed of the trains once they enter a controlled block.  The "NUDGE FORWARD" buttons allow you to manually active a block to move engines into position blocking sensors.  The circuit board in the lower left corner is for the PICAXE and switch controlling H-Bridge chip.

All of the external connections are in the lower left.  Switches connect to the left.  Under the word "Sensors" is the telephone cable that goes to the sensors.  The block power cables are under "Blocks + 1 2 3".

Accessory Receiver

The connection on the schematic that is labeled "END switch / Sound 1 or Sound 2 on receiver" is the one that goes to the radio control accessory receiver, pictured below.  This item is normally used to activate sound, lights or switches with the Train Engineer hand-held radio control unit.  When properly configured pressing one of the accessory buttons on the transmitter activates the "END" switch on the controller. In this photo it is temporarily connected to a switch motor for testing.



When first powered up the display shows copyright and version information.  It then resets all of the switches and checks that all sensors are blocked by engines.  If it finds that a sensor is not blocked it briefly applies power to the appropriate block moving the engine into position.  This repeats till all sensors are blocked.

Once all of the engines are seen to be in position the user is asked to press ENTER to begin.  At this point the engines leave and progress through the layout as previously described.  One after the other one engine exits as the other two wait their turns.


Two Trains or Three?

You may notice that the display in one photo shows "Trains = 3".  The layout that is controlled by this system is actually made up of two independent loops, one with two trains and two sidings and the other with three trains and sidings.  Rather than make up two different systems, one for two and one for three trains, I chose to make two identical units.  Either of them can be configured in software to control two trains or three.  Even though a two train controller would have been much simpler to construct, in the long run it is easier to maintain only one design and one software revision.


As you can see this was something of an epic project to build and document.  I hope that some or all of it was of interest to you and can be incorporated into your layout.

Why Not DCC?

A few LSOL readers asked me why I hadn't chosen to use DCC (Digital Command Control) to automate this layout.  There are a number of reasons but the main one is that the layout's owner didn't want DCC.  Other reasons for not using DCC for this project include:

  1. Cost - In addition to basic DCC equipment one would also need to purchase DCC units to detect train position and to control turnouts
  2. Control & software - to meet the design specifications for this system a DCC controller would need to be hooked up to a laptop or other computer and software would need to be purchased (or written).  Programming would still need to be done to automate the train operation.
  3. An individual controller would need to be installed in each engine. 

Stay tuned...

I have really gotten used to adding "Stay Tuned..." to the end of each of these articles so I think I'll do it one more time so I can give a little peek at my next train automation project.

Maybe There is Room for a Bit of DCC After All...

In spite of what I said above about DCC it is an interesting technology and there are some advantages to using it.  This is especially true if one can utilize a custom made DCC control system.  Over the last few months I have built a few DCC controllers and am close to finishing up a PIC based control unit that duplicates much of what the controller described in this series can do.


There are still a lot of things to work out but you may see an article soon.  Stay tuned...