D&B RPi Code



Status: As of 10-28-2018, the RPi perl code is test bench operational. Servos move and signal LEDs flash colors using the DnB embedded test software. The layout interfacing hardware is still under procurement and construction.

Language and Environment

The control software for the Raspberry Pi version of the D&B model railroad is written in perl. This language has a large body of free downloadable support code on the internet. A number of advanced perl programming techniques, primarily forks and child processes, are used to implement the necessary code functionality. The perl language is included in the raspbian OS distribution. The website opensource.com details perl support for other Raspberry Pi operating systems.

Update your Raspberry Pi raspbian operating system to the latest version using the console command line.
      sudo apt-get update
      sudo apt-get dist-upgrade

The DnB code needs perl version 5.24.1 or later. To check the perl version installed on your RPi enter,
      perl -v

The latest perl version can be installed using the console command line.
      sudo apt-get install perl

In addition to perl, the WiringPI and Forks::Super perl packages are also required.

An observational note. In some respects, the DnB perl code sharing the RPi resources is more complex than the previous Basic Stamp version which used multiple independent CPUs. The RPi version however, provides a far greater level of extensibility for the future needs of the model railroad. A few interesting statistics.

Line count comparison, Perl and Basic source code.
LanguageCode linesComment linesTest linesTotal
Basic2057 (56.1%)1449 (39.1%)152 (4.1%)3658
Perl2669 (49.1%)2276 (41.1%)498 (9.1%)5443

The D&B model railroad control program consists of the following files.

DnB.plMain program file.
DnB_GradeCrossing.pmFunctions related to grade crossing operation.
DnB_Mainline.pmFunctions related to mainline track section operation.
DnB_Sensor.pmFunctions related to acquisition of sensor input.
DnB_Signal.pmFunctions related to driving the trackside signals.
DnB_Turnout.pmFunctions related to driving the track turnouts.
DnB_Yard.pmFunctions related to setting yard turnouts.
DnB_Message.pmCommon functions used by all code modules.

Change History

Code VersionComment
DnB_code_20181110.zip• Code changes for holdover 1x4 keypad; DnB.pl, Mainline.pm, and Sensor.pm.
• Changed CLI option used to direct console output to USB terminal; now uses -r.
• CLI options -o, -m, and -c now set the specified turnout (or all if 0) to Open, Middle, or Close respectively.
• CLI -f option does a backup of existing TurnoutData file and creates a new file with default data.
DnB_code_20181105.zip• Signal.pm test code fix, grade crossing command, 'start' to 'start:apr'.
• DnB.pl -i option fix, sudo added to i2cdetect system call.
DnB_code_20181103.zipUpdated Turnout.pm to correct servo board lockup following console ctrl+c entry.
DnB_code_20181028.zipInitial code release.


General Description

The DnB.pl main program file contains a large amount of description that will not be repeated here. Please refer to this documentation for the essential details regarding how the program operates and a clearer understanding of its code structures and design.

The DnB.pl file contains the program configuration and working data variables. A turnout configuration file is created during program shutdown from the working TurnoutData hash. This file is loaded during the next startup, if present, to persist the turnout positions from the previous session. This file can also be user edited between starts to change Open, Close, and Rate values for each turnout. This is typically needed when turnout servos are installed or replaced on the layout.

Hash structures in the main program are referenced by pointer to the called subroutines as needed. Subroutines directly update hashes and pass status codes using return values. Forked child processes pass data to the parent using functionality provided by the Forks::Super module. This module essentially overloads the default perl fork function and provides a wealth of additional functionality. It handles the tasks related to reaping of child processes and the passage of data between parent and child processes. This module has helped to simplify the design of the DnB program code.

The Main Program Loop , running about ten cycles per second, orchestrates the overall program flow and sequence of operations. In general, it reads the sensor inputs and then calls the various turnout, signal, and track section processing routines. Many of the called subroutines are coded as state machines which rely on being periodically called; though not for strict timing. Functions that require more precise timing utilize the perl 'time', and where subsecond intervals are required, 'gettimeofday' functions. Essentially, subroutines store a needed future activation time in a working hash. With each main loop call, the subroutine compares the current time with the stored future time and performs its functions based on the result.

The Grade Crossing code utilizes a state machine and child process. The child process is responsible for flashing the signal lamps and is discussed more later. The state machine logic is called once per main program loop iteracton. State data that is used for grade crossing control is persisted in the GradeCrossingData hash. Each grade crossing is in one of the following states; 'idle', 'gateLower', 'approach', 'road', 'gateRaise' or 'depart'. The persisted values in the GradeCrossingData hash, current sensor bit values, and state logic transition the signal through these states. Refer to the DnB_GradeCrossing.pm file for more details.

The Mainline code coordinates automated and user input functions related to the 'Holdover', 'Midway', and 'Wye' track sections. Automated functions include train presence detection using block detectors and across-the-track infrared sensors. This input is used to position turnout points that direct a train onto a specific track. User input via keypad button can be used to override these turnout positioning behaviors in the 'Holdover', 'Midway' and 'Wye' track sections.

The Sensor code is used to initialize the I2C GPIO extender boards and get user keypad related input during program operation.

The Signal Color code, using block detector input, sets each signal to the required color. A track block is 'occupied' when a power drawing locomotive or car is present. The block protecting trackside signals have LEDs that are two lead red/green devices. Each LED is wired to two consecutive shift register bits. Red is illuminated with one current flow direction and green is illuminated with the opposite current flow direction. The current flow direction is determined by which of the two register bits is set high/low. The semaphore color is set by its flag board position which is servo actuated using a TurnoutData hash entry. Once in position, the signal lamp is illuminated by the signal color code.

The Turnout code provides functionality for positioning the servos that are physically attached to the turnout points. The open/close movement is accomplished over a 1-2 second time period. The actual movement is performed using a forked process. After fork, the pid of the child process is stored in the TurnoutData hash. The operation is 'inprogress' until this pid is reset. The child code completes the peration independent of other turnout positionings or software activities. Upon completion, the child updates the turnout position in the parent TurnoutData hash and resets the pid. This is accomplished using Forks::Super stdout/stderr related functionality.

The Yard code sets the yard turnouts based on user keypad input. Two keypad button presses are required which specify the desired 'from' and 'to' tracks. If the input corresponds to a valid yard route, the appropriate turnout points are set for the route. A keypad indicator and audio tones are used to confirm button input. Three special route inputs are handled involving tracks 4 and 5.

The Message module contains console messaging functions, general program support functions, and the raspbian sound player (omxplayer) interface function. The various beeps that are sounded during operations were created using the tone generate feature in Audacity and saved as MP3 files.

The DnB program code displays operational messages on the console or optionally to the RPi serial port. For headless operation, all console messages can be suppressed by specifying the -q option on the startup command line. Output of built in program debug messaging is enabled by a command line specified debug option which includes a numeric value to control message verbosity. There are some sections of code, primarily child processes that use the STDOUT and STDERR filehandles, where debug code is commented out. This code can be uncommented for program debug if needed. This code will interfere with normal program operation and should be commented out when debug is completed.

The following summarizes the startup program sequence.

1. Process these command line options, if specified, and then exit program.
      • Program help (-h).
      • I2C address display (-i).
      • Creation of new TurnoutData file (-f).

2. Check for and terminate any DnB associated orphan child processes.

3. Open serial port (-r) for redirect of console message output.

4. Display 'DnB program start' message on the console. $MainRun variable set to 1.

5. Initialize GPIO pins and I2C devices.
      • RPi GPIO pins.
      • I2C I/O Plus board 1; MCP23017 chips 1 and 2 (sensor inputs).
      • I2C I/O Plus board 2; MCP23017 chips 3 and 4 (keypad scan and button inputs).

6. Start child processes.
      • KeypadChildProcess
      • ButtonChildProcess
      • SignalChildProcess
      • GcChildProcess 1
      • GcChildProcess 2

7. Initialize turnout servos.
      • Load previous TurnoutData file if available.
      • Initialize servo boards 1 and 2; PCA9685 chip.
      • Set servo channels to CLI (-o|-m|-c) or TurnoutData specified PWM value.

8. Perform command line specified testing. Test termination results in program exit.
      • Sensor test (-b)
      • Keypad test (-k)
      • Grade crossing test (-g)
      • Signal test (-s)
      • Turnout servo test (-t)
      • Sound player test (-p)

9. Display 'DnB main loop start message' on the console. $MainRun variable set to 2.

10. Run main loop until aborted by ctrl+c or shutdown button.
       A. Top of loop.
       B. Read sensors (32 bits).
       C. Process holdover track section sensors. Set turnouts as needed.
       D. Process midway track section sensors. Set turnouts as needed.
       E. Process wye track section sensors. Set turnouts as needed.
       F. Process both grade crossing track sections.
       G. Process signals. Set colors based on block detector inputs.
       H. Set next turnout for an inprogress yard route request.
       I. If no inprogress yard route, get new yard route input.
       J. Process holdover track section route button input.
       K. Process midway track section turnout button input.
       L. Process wye track section turnout button input.
       M. Check shutdown button. Initiate shutdown sequence if set.
       N. 100 msec loop delay.

11. User initiated program termination; ctrl+c or shutdown button.
      a. Ctrl+c input: Stop child processes. Program exit.
      b. Shutdown button: Continue main loop processing and
            • Sound 5 countdown tones, one each second.
            • Cancle if shutdown button pressed again.
            • Save turnout position data to file. Shutdown raspbian OS.


Child Processes

GcChildProcess

A grade crossing child process is launched during main program startup for each grade crossing. This child process is used to start and stop grade crossing signal lamp flash operation. Crossing gates, if present, can't be driven by this child process due to a Forks::Super restriction. A child process is not allowed to spawn a child process. Gate movement servos are handled by the grade crossing state logic.

The child pid value is stored in the GradeCrossingData hash. This pid value is used by the parent state logic to send messages to the child process. Forks::Super 'child_fh' functionality is used for communication between the parent and child processes. The parent sends a start/stop signal message to the child's stdin. The child process checks its STDIN file handle for new messages and acts on them accordingly. The following messages are used.

start:apr     - Start flashing lamps with bell sound 1.
start:road   - Start flashing lamps with bell sound 2.
stop                - Stop lamp flash and bell sound.
exit                - Terminate GcChildProcess.

KeypadChildProcess

The keypad child process is launched using Forks::Super and processes user input from a Storm 4x4 keypad. When a keypad button is pressed, a single row and a column are electrically connected. For example, in the following diagram, pressed button [5] connects row B to column 2.

      col1234
row||||
A-- 0 --- 1 --- 2 --- 3 --
||||
B-- 4 --- [5] --- 6 --- 7 --
||||
C-- 8 --- 9 --- A --- B --
||||
D-- C --- D --- E --- F --
||||

The keypad is connected to eight pins of a MCP23017 port. Four of these pins are configured as output and drive the keypad columns. The other four are configured as input with pullup and connect to the keypad rows. By sequentially driving a single column pin low and then sequentially sampling the row pins one at a time, a pressed button can be identified. With no button press, all button coordinates will read high (1) due too the pullup. A pressed button will read low (0). For the case of multiple buttons being pressed, the first button detected is used.

The keypad child process scans the keypad 10 times a second. When a button press is detected, the child process sends the button number (0-F) to its STDERR filehandle. The button press value can then be read by parent code using Forks::Super::read_stderr($KeypadChildPid).

ButtonChildProcess

The button child process functions in a manner similar to the keypad child process. It is launched using Forks::Super and processes user input from a Storm 1x4 keypad. When a keypad button is pressed, a column is electrically connected to common. For example, in the following diagram, pressed button [2] connects column 3 to common.

      col1234
||||
common-- 0 --- 1 --- [2] --- 3 --
(gnd)||||

The keypad button columns are connected to a MCP23017 port. These pins are configured as input with pullup. The keypad common is connected to ground. With no button press, all button coordinates will read high (1) due too the pullup. A pressed button will read low (0). For the case of multiple buttons being pressed, the first button detected is used.

The button child process scans the keypad 20 times a second and differentiates between single and double button presses. A double button press is reported if the same button is pressed within a one second time period. When a button press is detected, the child process sends a string, s(0-3) or d(0-3), to its STDERR filehandle. The button press string can then be read by parent code using Forks::Super::read_stderr($ButtonChildPid).

SignalChildProcess

The signal child process is launched during main program startup and is used to drive the 74HC595 shift register. This frees the main code from the constant need to toggle the yellow signals between red and green. This is accomplished by using two 32 bit variables. In one, the yellow signal color is set to red. In the other, the yellow signal color is set to green. The two variables are alternately sent to the shift register. For non-yellow signal positions, the bits are set to the same color in each variable.

Two time delays (perl select statements) are used to balance the red and green 'on' times. This provides for a coarse adjustment of the yellow color for all signal positions. The variable resistors on the driver board are then used for fine yellow color adjustment of each signal.

The combined time delays further control the repetition rate of the signal child's while loop. This rate should be just high enough to eliminate flicker when the yellow color is displayed; about 25-30 cycles per second. The lowest possible cycle rate is desired to minimize CPU loading by the signal child's while loop. To further minimize CPU load, the signal child optimizes itself by checking for any yellow signal indications. When no yellow signals are being displayed, the loop repetition rate is reduced to 4 cycles per second.

Forks::Super 'child_fh' functionality is used for communication between the parent and child processes. New signal settings are sent to the child's stdin. The new data is read by the signal child process and used until subsequently updated. To minimize signal child input processing, the data message is formatted as follows.

SignalMask         - 32 bit mask, all 1's. Changed signal two bits set to 0.
SignalColor1     - 32 bit mask, all 1's. Changed signal set to color value; red (0b01), green (0b10), yellow (0b01), or off (0b00).
SignalColor2     - 32 bit mask, all 1's. Changed signal set to color value; red (0b01), green (0b10), yellow (0b10), or off (0b00).
Terminator         - "-\n". Used to remove processed messages from the child's stdin.


Test Code

A number of tests are available for use with code verification and operational adjustments. They can invoked when needed using the appropriate DnB.pl command line option. Tests are run individually and do not involve the DnB main program loop. This helps to focus testing to specific functions and features. Each test runs until terminated by ctrl+c console input.

Sensor test (-b option)

This test reads the user specified sensor chip(s) and displays the value(s) on the console. This continues once per second until terminated by ctrl+c. While running, each sensor bit can be manually activated to verify the associated circuitry is working as expected. The MCP23017 chip(s) to be tested are specified using a comma separated list or range. For example:
        DnB.pl -b 1,3     Read and display chips 1 and 3.
        DnB.pl -b 1:4     Read and display chips 1 through 4.

Keypad test (-k option)

This test reads the stderr messages generated by KeypadChildProcess and ButtonChildProcess and displays the results on the console. Reads are performed at a two second interval until terminated by ctrl+c. For the 4x4 keypad, the 1st entry LED is toggled between on and off with each button press. For the 1x4 keypad, single and double press buttons are reported.
        DnB.pl -k     Read and display keypad inputs.

Grade crossing test (-g option)

This test bypasses the grade crossing sensors and sends the 'start:apr', 'start:road', and 'stop' commands directly to the grade crossing child process. The gate servos are positioned if present on the grade crossing. The sound effects circuits are also activated.
        DnB.pl -g 1        Test grade crossing 1.
        DnB.pl -g 1,2     Test grade crossings 1 and 2.

Signal test (-s option)

This test exercises the functions associated with the block protection signals and SignalData hash. The test is started with the -s option and runs until terminated by ctrl+c. The signals to be tested are specified using a comma separated list or range. For example:
        DnB.pl -s 1,5     Cycle signals 1 and 5; red, green, yellow, and off.
        DnB.pl -s 1:12      Cycle all signals; red, green, yellow, and off.

Prefacing the range with a r causes random signal selection and color setting.
        DnB.pl -s r4:9.     Random signal 4 through 9; set random color red, green, yellow, or off.

Prefacing the sequential or random range with a g causes the the grade crossing signals to be included in the test run. Grade crossing 1 and 2 are started for color green and yellow respectively. Grade crossing 1 and 2 are stopped for color off and red respectively. Crossing gates are not positioned.
        DnB.pl -s g1:12     Cycle all signals; red, green, yellow, and off. Include grade crossings.

Turnout servo test (-t option)

This test exercises the functions associated with the turnout servos and TurnoutData hash. The test is started with the -t option and runs until terminated by ctrl+c. The servos to be tested are specified using a comma separated list or range. For example:
        DnB.pl -t 1,3,5     Exercise servos 1, 3, and 5; sequential positions Open, Middle, Close.
        DnB.pl -t 1:10      Exercise servos 1 through 10; sequential positions Open, Middle, Close.

Prefacing the range with a r causes random servo selection and Open/Close positioning.
        DnB.pl -t r1:10.     Exercise servos 1 through 10; random order, positions Open and Close.

In the above commands, about 10 servo position changes are performed per second. This verifies the functionality of multiple concurrent servo positionings. This rate exceeds the maximum expected number of concurrent position changes (the longest yard route is 8 turnouts) during normal layout operation. The console output shows the inprogress operations. This can also be viewed in a seperate command console using the linux top command. The number of DnB.pl processes shown, minus 1 for the test code, is the concurrent inprogress operations. The CPU utilization can also be seen in the top command output.

Prefacing the sequential or random range with a w causes the positioning to be done one at a time. This verifies the child/Forks::Super pid reset functionality.
        DnB.pl -t w1,3,5     Exercise servos 1, 3, and 5; wait for each operation to complete before next.
        DnB.pl -t wr1:10     Exercise servos 1 through 10; wait for each operations to complete before next.

Demo clips on YouTube:
Sequential 1:32       Random 1:32

Sound player test (-p option)

This test is used to verify the omxplayer configurations and audition the available sound files.
        DnB.pl -p

The test lists the MP3 files that exist in the DnB mp3 directory along with an associated number. When a number is entered by the user, the sound file is played. Entry of sound number zero (0) terminates the test.

Demo clip on YouTube: Audio Player Test


Raspbian OS Configurations

The following raspbian OS configuration changes are needed for proper operation with the selected components.

DnB.pl Program Start

This change uses raspbian SYSTEMD to set automatic start of the DnB.pl program following completion of raspbian OS boot. If not done, it is necessary to manually start the DnB.pl program using the raspbian command line following each power up. See Other Startup Methods for alternatives.

1. Use a text editor run as sudo to create the the file /lib/systemd/system/DnB.service with the following content.
[Unit]
Description=DnB Model Railroad Service
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/perl /home/pi/DnB.pl

[Install]
WantedBy=multi-user.target
2. Set access permissions of the DnB.service file.
sudo chmod 644 /lib/systemd/system/DnB.service
3. Enable the DnB.service file.
sudo systemctl daemon-reload
sudo systemctl enable DnB.service

Following raspbian reboot, the DnB.pl program will automatically start. A press and hold of the shutdown button during boot will terminate the DnB.pl autostart unless the -x option is used. Once configured, auto DnB start can also be set to 'disable' using the above commands.

RPi Sound Player Modification

A modification is needed to the RPi sound player script, /usr/bin/omxplayer. The omxplayer is used by DnB to play mp3 sound files which contain warning and keypad input confirmation tones. The changes suppress default messages that the omxplayer outputs. This enables the omxplayer and DnB programs to run asynchronously and non-blocking. The $SoundPlayer variable in DnB.pl has been set to use this name and path.
1. cd to directory containing DnB.pl.
2. cp /usr/bin/omxplayer omxplayer_quiet
3. edit omxplayer_quiet
change: OMXPLAYER_DIR=`dirname $0`
         to: OMXPLAYER_DIR=/usr/bin
change: LD_LIBRARY_PATH="$OMXPLAYER_LIBS${LD_LIBRARY_PATH:+ :$LD_LIBRARY_PATH}" $OMXPLAYER_BIN "$@"
         to: LD_LIBRARY_PATH="$OMXPLAYER_LIBS${LD_LIBRARY_PATH:+ :$LD_LIBRARY_PATH}" $OMXPLAYER_BIN "$@" > /dev/null 2>&1
4. Save file and exit editor.
The RPi has two audio output devices, HDMI and headphone jack (local). The command string in DnB.pl that is used to launch the omxplayer sound player ($SoundPlayer) specifies that both audio devices should be used (-o both). The audio devices local, hdmi, both and alsa are supported by the omxplayer if a different device is desired. The $SoundPlayer variable also specifies the omxplayer volume level (--vol -2000) to be used.

DnB.pl Mainloop Timing

The following shows the timing of the main processing loop when the -z option is used to enable toggle of the GPIO20_TEST pin. The code revision used for this measurement is 10-28-2018. Total time for one loop is approximately 107 milliseconds. This helps to identify code areas that can be investigated/improved in future versions.

RpiMainloopTiming.jpg

Color Bar

Planning and Construction:   Design Goals   Track Plan   Photos   Scenery Base
Basic Stamp Control Electronics:   Main line   Yard   Grade Crossing   Block Signal   Turntable
Circuit Description:   Main line   Yard   Grade Crossing   Block Signal   Block Detector   Turntable   Power Supply   Schematics
Photos and Video clips:   Photo 1   Photo 2   Photo 3   Photo 4   Photo 5   Photo 6   Video Clips   Ride The D&B
Propeller Control Electronics:   Overview   Flow Charts   Program Code   Schematics   Photos
Raspberry Pi Control Electronics:   Overview   Program Code   Schematics   Photos
Navigation:  D&B Home  Buczynski.com Index

Copyright © 2018 Don Buczynski
San Diego, California