Coding the Raspberry Pi for the N Gauge Layout

By Andy Joel

The layout uses a Raspberry Pi (RPi) 4 B, 2 Gb. Be aware that there may be some variation between models.

This page describes how to set up a Raspberry Pi and how to use the software. The code itself is discussed elsewhere.

Hardware Set-up

The RPi is a basic computer, and as such has most of the features of a computer. It can be connected to a monitor, mouse and keyboard, and it stores data on the equivalent of a hard-drive. This model requires a micro-HDMI connection to a monitor; I have purchased an adapter to a VGA connection, so an older monitor can be used.

It requires an SD Card, which acts the hard-drive. The SD Card must have the operating system installed on it before the RPi can be used. You can download an installer here. Once downloaded, run the software to install Raspberry Pi Imager on your computer.

In the imager, select the Raspberry Pi Device, Raspberry Pi 4 in this case; the Operating System, Raspberry Pi OS (64 bit); and Storage, your SD card. It will asking if you want to customise setting, just say No, then it will say it will delete everything on the SD card, say Yes. It will take a few minutes. You may get warnings about the drive being empty or needing formatting; just ignore them. At the end it will say the card can be removed. Do so, and put in the RPi.

It also has a battery backup (UPS hat) , which is connected via the I2C bus. The manual for it is here. The UPS hat has an on-off switch. Once the batteries are in place, it can be turned on, and the RPi will fire up. On initial boot up, there is some installation to be done, but thereafter booting up takes less than a minute. The username is “pdmrs”.

The operating system is a version of Linux; it looks a bit like Windows but is not the same.

The system uses I2C to communicate with servos, etc. and you will have to turn this on in the preferences on the Raspberry Pi.


I2C Bus

Components on the bus (address):

PCA 9685 Servo controller (0x40+)

Each one of these controls up to 16 servos. There will be one for each cluster of points.


LCD Display (0x20)

To display basic diagnostics.


PCF8575 I/O Expander (0x20+)

For switches and lights on the control panel.

I/O pins are labelled from 0 to 7 on one side, and 11 to 17 on the other, but the latter is actually 8 to 15, with the last two pins on each side being VDD and GND.

Pins for input should be connected via a switch to GND; when polled the device will say if the switch is open or closed. This is ‘light pull-up input’ – and I think this needs setting in the software.

For output, connect an LED, via a resistor, to VDD.

Note that the logic is the reverse of what you would expect. A switch closed is False, and open is True. Set an LED to False to turn it on. LEDs are latching – you only need to send a message once to set their state, they will stay the same until told not to.



Multiplexer TCA9548A

This can be used to expand the number of servo controllers if required



The battery back-up also communicates across the I2C bus.


Software Set-up

You program the RPi using Python. It comes with two editors with little to pick between them – I went with Geany.

Virtual Environment

All modern code uses libraries – code that has been written by someone else to do the basics that you can build on top of. These are often called packages. You will need some to communicate with devices from the RPi. Before we get to that, we need to discuss virtual environments.

Different projects require different packages, and there are potential conflicts between them, especially when packages use other packages that mismatch the version. That is not an issue here as we have just one project, but we do need to take account of the solution.

Recent versions of Python have “virtual environments” built in with the idea that you set up one environment for one project with its set of packages and another for your other project. And Python will not let you download packages unless you are in a virtual environment. Sometimes…

Further confusing arises from the fact that this is a very recent change, so much of the documentation does not mention dealing with virtual environments because it predates the change.

If you try to install a package when not in a virtual environment, you will get this message:

This environment is externally managed
To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install.

So we need a virtual environment, in this example called “pdmrs”. You can create one like this:

python -m venv pdmrs

It then needs to be turned on (and you need to do this each time you open a command prompt):

source pdmrs/bin/activate

You also need to keep all your files inside the pdmrs folder.


Once you have a virtual environment created and activated, you can install the packages you need. There are two commands for downloading Python packages,, “pip” and “apt-get”; “pip” seems to be the prefered.

You will often see the command preceded by “sudo”.  This “stands for super user do” and allows you to run a command as the administrator. That should not be necessary when using pip in a virtual environment.

I am using a set of packages known collectively as CircuitPython. CircuitPython is designed for microprocessors, rather than RPi, and so we use Adafruit Blinka to emulate a microprocessor. Hopefully this is transparent to the coder – it is only mentioned to give context to the names.

pip install adafruit-circuitpython-pca9685

pip install adafruit-circuitpython-servokit

pip install adafruit-circuitpython-charlcd

pip install adafruit-circuitpython-pcf8575

pip install guizero


Instructions often use pip3 rather than pip – this is just an older version. Ignore them and use “pip”.

Further Random Notes

An introduction to CircuitPython is here. The first program is worth running as it reports I2C devices on the bus, confirming there is a connection, and the basics are working.

The Python Code





Other notes to myself that may not make sense to anyone else…


pip3 install adafruit-circuitpython-tca9548a


# This example shows using TCA9548A to perform a simple scan for connected devices
import board
import adafruit_tca9548a

# Create I2C bus as normal
i2c = board.I2C() # uses board.SCL and board.SDA

# Create the TCA9548A object and give it the I2C bus
tca = adafruit_tca9548a.TCA9548A(i2c)

for channel in range(8):
if tca[channel].try_lock():
print(“Channel {}:”.format(channel), end=””)
addresses = tca[channel].scan()
print([hex(address) for address in addresses if address != 0x70])