This note introduces my experiences of using the Raspberry Pi to automate a model railway.

Two aspects are covered:


Schematic showing the Pi's USB and I2C interfaces

Sending commands to a DCC controller

The RPi is connected to a Hornby Elite DCC controller by a USB cable. A python program sends XPressNet commands to the controller using the python serial package (python-serial).

The XPressNet protocol (developed by Lenz) allows DCC control devices such as hand-held controllers to be connected to the command station . Such connections are high-speed RS-485 links. Lenz and probably others make devices that allow PCs to be connected to the XPressNet bus via a slow speed (e.g. 9600 or 19200 Baud) serial connection. The Hornby Elite has this slow speed interface built in allowing it to be connected directly to a PC’s (or RPi’s) USB port. For further information about XPressNet including the full specification see the Lenz website

I’m only using commands that control the trains throttle, functions (e.g.sound) and accessories (e.g signals). I don’t know how fully the protocol is implemented by the Elite. There appear to be discrepancies between the documented responses and what I get back from Elite after a command has been sent. Currently, I only look for a x’05‘. If I get it, I assume the command has been executed successfully, if I don’t, I resend the command.

One problem I had, was getting Linux to correctly recognise the Elite when it was connected to the RPi. It appears that Hornby incorrectly uses product and device ids in their firmware. This results in the wrong drivers being associated with the Elite. This problem can be resolved by creating a file called :


containing the following command (all one line):

ATTR{idVendor}=="04d8", ATTR{idProduct}=="000a", RUN+="/sbin/modprobe -q ftdi_sio vendor=0x04d8 product=0x000a"

You should see a message similar to the following when Linux is restarted:

[    7.319030] usb 1-1.3.1: FTDI USB Serial Device converter now attached to ttyUSB1
[    7.356074] usbcore: registered new interface driver ftdi_sio

The application program running on the RPi should now be able to communicate with the Elite using the file id ttyUSB1.

Before python can communicate with the serial port you need to install the python serial package e.g. :

sudo apt-get install python-serial


The following snippets are examples or sending commands to the controller from a python program.

To use the python serial interface import the serial package:

import serial

The following will open the serial port ttyUSB1 with a speed of 9600 Baud, no parity and one stop bit:

ser = serial.Serial('/dev/ttyUSB1',9600)

To send a command, use the write method (‘message‘ could be a string, but I’ve found easier to build a command as a bytearray):


To close the serial port, call the close method:


The following is an example of sending a command and waiting for a response. If a x’05’ is not received, the command will be resent up to a maximum of 5 times:

def send(message):
    ok = False
    trys = 1
    while (not ok and trys < 5) :
        print 'trys = %d send:' % (trys) , 
        for byte in message:print(hex(byte)) ,
        print ' receive: ',     
        while ser.inWaiting() > 0 :
            enq = ser.read()
            print enq.encode('hex') ,  
            if enq == '05'.decode('hex') : 
                ok = True
        trys += 1

The XPressNet protocol states that the last byte of a command is a ‘Error detection byte’. It is formed using an X-Or linkage of the proceeding bytes of the command. The following function will determine the error detection byte:

def parity(message):
    edb = 0
    for byte in message:
        edb ^= byte

The following example shows how a function could be coded to implement the XPressNet throttle command:

def setThrottle(address,direction,speed):
    message = bytearray('E400000000'.decode('hex'))
    message[1] = 0x13
    message[3] = address
    message[4] = speed
    if direction  == 'f' : message[4] |= int(b'10000000',2)
    elif direction =='b' : message[4] &= int(b'01111111',2)
    send  (message) 

Detecting a train’s position

For railway automation to be effective, some sort of positional feedback is required. There are a number of techniques for achieving this, ranging from sophisticated RFID readers that can read information stored on tags fitted to the train to simple mechanical switches such as reed relays activated by magnets fitted under the train.

The approach adopted with this project, is the use of hall-effect switches fitted to the track and a magnet fitted to the underside of each train. It has the following advantages:

This approach does, however, lack the ability for a computer to distinguish between different trains. A complex layout with a number of trains concurrently running will struggle to achieve full automation, however, even with a reasonable size layout, it should be possible to manage a degree of automation by building algorithms into the automation program that will deduce which train has triggered the sensor by the sequence of sensors activated.

The positional feedback system comprises of two components:

The Hall-effect switch

The hall-effect switch is a small device that fits on the railway track. The A1121LUA -T is just the right size to fit between the sleepers of a Hornby track.

hall-effect switch

The hall-effect switch fitted between the sleepers

The flat side of the device should be facing upwards towards the underside of the train. Care must be taken to ensure the three leads that protrude from the device do not touch each other or the metal rail.

For detailed information of the device see the device data sheet and the applications guide

The switch is activated when the south-pole of a magnet passes over the hall-effect device.

magnet fitted to the underside of the train

Picture showing a magnet located beneath the train secured with Blutack

The Hall-effect switch interface

The output of the switch is capable of directly driving CMOS or TTL logic. In it’s normal state the output is HIGH. When the south-pole of the magnet passes overhead, the output pin is driven LOW.

The Hall-effect switch output is connected to a 16-Bit I/O Expander (MCP23017).

hall-effect sensor interface

Circuit diagram of the Hall-effect sensor interface

For further information regarding the MCP23017 see the Microchip Data sheet

It is assumed that the automation program will enable the internal pull-up resistors on the inputs to the MCP23017. This eliminates the need for external resistors between input pins and the positive rail (for example pin 21 and +5V).

The MCP23017 has 2 groups of I/O ports:

The circuit diagram shows only 4 sensors connected GPA0 - GPA3. It is possible to connect a further 4 sensors to GPA4 - GPA7 (pins 24 - 28).

In addition, a further 8 sensors could be connected to GPB0 - GPB7. The circuit diagram shows GPB0 - GPB3 (pins 1 - 4) being used as outputs to drive LEDs used as indicators driven by the automation program. R1 - R4 are resistors of suitable value to act as limiting resistors to protect the LEDs. The project is currently using 330 ohm resistors.

The circuit diagram also shows an LED connected to the interrupt output for I/O port A.

The MCP23017 is connected to the RPi via it’s I2C interface.

For further information about I2C, see the I2C website

The IC2 bus consists of 2 data connections to the RPi

In the current version of the project the 5V supply of the RPi is used to power the sensor interface and the sensors. Care must be taken that this supply is not over-loaded. If the 5V supply of the RPi drops to below 4.9V it is advisable to use an independent power supply for the interface and sensors.

The interface is connected to the RPI through its GPIO port, 26 pin male plug fitted on the edge of the RPi. Refer to RPi documentation for the correct pin layout of this plug.

Up to 8 MCP23017 devices could be connected to the RPi’s I2C bus. The address of each device is determined by the 3 address select pins (pins 15, 16, and 17). In the current project only one device is on the bus, so all address pins are taken to LOW.

You will need to enable I2C on the RPi. SK Pang Electronics have a very good step by step guide to I2C installation


The following snippets are examples of managing the sensor interface from a python program.

To start using the I2c interface you must import smbus:

import smbus

Instantiate an instance of smbus:

bus = smbus.SMBus(0)

Define the address of the I/O expander. If all 3 address pins are LOW then the device address is 0x20"

address = 0x20 # I2C address of MCP23017

Define constants for the registers in the MCP23017. For information about the registers and their purpose see the Microchip Data sheet

IODIRA =  0x00 
IODIRB =  0x01
GPPUA  =  0x0c
GPPUB  =  0x0d
INTCAPA=  0x10
INTCAPB=  0x11
GPIOA  =  0x12
GPIOB  =  0x13

Initialize the I/O ports. In this example the sensors are connected to Port A, therefore all pins are set as inputs. LEDs are connected to port B, so they are set as outputs:

bus.write_byte_data(address,IODIRA,0xff) # Set all of port A to inputs
bus.write_byte_data(address,IODIRB,0x00) # Set all of port B to outputs

The MCP23017 has internal pull-up resistors that remove the necessity to use external resistors between the sensor output and the positive rail (+5V):

bus.write_byte_data(address,GPPUA,0xff)  # Enable pull-up resistors on port A

If the interrupt mechanism on the MCP23017 is enabled, then once a sensor is activated, the state of the associated I/O register is stored in the interrupt capture register(INTCAP). This state will be ‘frozen‘ until the MCP23017 generates another interrupt, i.e. a sensor is activated:

bus.write_byte_data(address,GPINTENA,0xff) #Enable interrupts on port A

Read the interrupt capture register:


The following example assumes 4 sensors are on a piece of track. The function will print the name of the last sensor that was activated:

def read_INTCAPA():
    if (not x & 0x08) : print 'Sensor 4'
    if (not x & 0x04) : print 'Sensor 3'
    if (not x & 0x02) : print 'Sensor 2'
    if (not x & 0x01) : print 'Sensor 1’

Python Scripts

The following python scripts demonstrate controlling and detecting a train.