Quick link to GitHub repository
The key to creating a model railroad setup which can be operated by a computer is knowing where the trains are on the track. It would be nice if we could sense each train’s position anywhere along the track at any time, but until we start incorporating time-domain reflectometers into our layouts (which is not happening anytime soon), we have to settle for either of two options:
- Sense the presence of a powered train car somewhere within an electrically isolated track block.
- Sense the train with some form of sensor at a specific point on the track.
For this project, I’m going with option 2 exclusively, although one could mix both options on the same layout. The sensors I’m using are infrared reflectance sensors mounted below the tracks and looking up between the rails. Up to 8 of these sensors are connected to a sensor node, a small Arduino-class microcontroller board. Up to 16 of these sensor nodes can be connected to a sensor controller, another Arduino board. So a single sensor controller can monitor the state of as many as 128 different track sensors. The entire system will support multiple sensor controllers as well, but I doubt I’ll need to have more than one for this first project.
The sensor I’m using is a QRE1113 line sensor breakout from SparkFun Electronics of Niwot, Colorado. It’s a tiny little red circuit board with the sensor element mounted near the center on one side. It’s just the right size to fit under a section of KATO Unitrack with a hole drilled and filed square for the sensor element to poke up between the rail ties (sleepers in some parts of the world), with the board’s mounting hole fitting over one of the blind screw holes molded into the underside of the track sections.
The sensor element is an analog device, with the infrared (IR) emitter shining upwards and the IR detector watching for reflected IR light. When there’s no light detected, the IR detector returns a high signal, around 5 volts. If an object passes close enough in front of the sensor element, the detector returns a lower voltage; the more light detected, the lower the voltage goes. It turns out the dark gray parts of the PORTRAM undersides don’t reflect a lot of light back to the sensors, so I will probably put small pieces of white tape on the bottom of the trams to compensate.
Controllers and nodes
Each sensor node is a small Arduino-class microcontroller board with multiple analog input pins. Each analog input pin can be connected to a track sensor; the number of sensors supported by the node depends on which board is used. For testing, I’m using Arduino Micro boards which have 12 analog input pins; for the actual layout, I’ll be using Adafruit Pro Trinkets (5 volt variety) as the nodes, with 8 analog inputs each. I chose the Pro Trinkets because:
- 8 sensors per node was enough, and made communications between the nodes and the sensor controller simpler.
- The Pro Trinkets are cheaper per unit (around $10 versus $25 for the Arduino Micro).
- At the time I made the choice, I was having trouble finding online vendors who had the Arduino Micro in stock, whereas Adafruit had Pro Trinkets available.
The sensor nodes communicate with a sensor controller using a Serial Peripheral Interface (SPI) on each Arduino. The nodes are daisy-chained so that multiple nodes share the sensor controller’s single SPI port. More detail is given in the Sensor Network section below.
Each sensor controller is connected via the SPI daisy-chain to up to 16 sensor nodes. The controller takes the role of the SPI master, while the nodes are all SPI slave devices. This means that all data transfers between the controller and its nodes are initiated by the controller.
One or more sensor controllers will be connected to the central control processor (probably a Raspberry Pi Zero) via an I2C bus. Each sensor controller will have a unique I2C address, and will act as an I2C slave device, while the central processor will be the I2C master. Again, this means the master will initiate all data transfers, but the slaves (the sensor controllers) will be able to interrupt the central processor to request a data transfer, which will usually be a single data byte that contains:
- a sensor number on a node (0 to 7)
- the node number on the controller (0 to 15)
- whether the sensor turned on (detection) or off (no detection)
Just about any Arduino device (except for the smallest ones with very few I/O pins) can serve as a sensor controller. For testing, I’m using an Arduino Uno, but for the actual layout I’ll probably use another Adafruit Pro Trinket or an Arduino Micro, both of which are much more compact than the Uno and still have sufficient I/O pins.
Below is a block diagram of the track sensor network, showing the various processors and the communication paths between them.
And here’s what it actually looks like on my workbench.
You can see to the left the first two sensor track sections with a PORTRAM “test article”. The three Arduino Micros with the colorful USB cables are the sensor nodes, with node 0 to the front. Node 0 is connected to the two sensors on its channels 0 and 1. The two wooden cases contain Arduino Unos; the one with its own protoboard is the sensor controller, and the other is acting as the central processor, handling the role of I2C master. (The wooden cases are a Pocket Case and Pocket Case Plus from MakerShed; sadly, both appear to be discontinued items now. I’m glad I stocked up a few weeks ago when I had the chance.)
Below are diagrams of the track sensor network, first in the workspace edition using Arduino Unos and Micros, and then in the planned configuration using Adafruit Pro Trinket 5v boards as a sensor controller with sensor nodes.
This section describes the Arduino sketches which run on a sensor controller and the sensor nodes attached to it.
The Arduino sketch which runs on each sensor node can be found in my GitHub repository at https://github.com/twrackers/TrackSensor-sketches/tree/master/sketches/TrackSensor_Slave.
The TrackSensor_Slave sketch starts by calibrating the outputs from the sensors when they are not detecting a reflected signal. It then sets the detection threshold for each sensor to a fixed fraction of that signal (currently 90%). Remember that infrared light reflected back to the sensor causes the sensor output to dip lower, so when the output drops below the sensor’s calibrated threshold, it is considered a detection event.
Once the sensors are calibrated, the sketch enters a fast loop (around 20 Hz) where it samples its analog inputs, masks off unused channels (more on that below), and saves the current sense/no-sense state of all channels.
The sensor node also has an interrupt handler which takes control briefly when the SPI interface’s SS (Slave Select) pin, driven by the sensor controller, changes state to start or complete an SPI data transfer.
The Arduino sketch which runs on each sensor controller can be found in my GitHub repository at https://github.com/twrackers/TrackSensor-sketches/tree/master/sketches/TrackSensor_Master.
Each sensor node is connected via its SPI interface, possibly with additional sensor nodes, to a sensor controller. The sensor controller periodically performs a data transfer of mask bytes into the sensor nodes, and at the same time receives the sensor status bytes from the nodes. It then determines which node channels changed state between sense and no-sense, and queues up event bytes which will be sent to the central control processor via the controller’s I2C interface. Each event byte contains the node number (0 to 15), its channel number (0 to 7), and whether the sensor’s new state is sense or no-sense.
The purpose of the mask byte passed to each node is to set which of the node’s channels will be monitored. A channel that is masked off will always return the no-sense state. Through some coding “magic”, the TrackSensor_Master sketch uses these mask bytes at startup to count how many nodes are connected to it. The central processor can then read the node count for each sensor controller to verify that the track sensor network has started up correctly. You can read an explanation of how the node-counting is done in the source files in the GitHub repository above.