Daisy-chaining SPI devices
SPI, or Serial Peripheral Interface, is a 3-wire interface standard which can transfer data at high speeds with a simple protocol, much simpler than I2C. The signals have commonly been called MOSI (Master Out Slave In), MISO (Master In Slave Out) and SCLK (Serial Clock). At the time I’m writing this, the electronics industry is renaming MOSI and MISO to some variant of SDO (Serial Data Out) and SDI (Serial Data In) respectively. Until there is agreement among manufacturers on the new labels, and because datasheets for devices need to be updated, I need to stick to MOSI and MISO for the time being.
SPI actually requires a fourth signal as well to interface an SPI controller to a peripheral device such as a sensor. This signal, currently called SS* (Slave Select) and probably being changed over to CS* (Chip Select), is an active-low signal which activates the device which is to be controlled during a data transfer.
In a standard SPI configuration where one controller talks to multiple devices, the MOSI, MISO, and SCLK signals are bussed to all the devices. Each device though requires a dedicated SS* signal so that only one device at a time responds to the other three signals. You can see that the number of available pins which can serve as SS* signals limits how many SPI devices the controller can talk to. Figure 1 shows three devices connected to a controller. MOSI carries data from the controller to the devices, while MISO flows from the devices back to the controller. Typically when the clock signal is toggling, data bits are transferred across MOSI and MISO at the same time.
For my track sensor network, I am using an alternative design, where the devices are daisy-chained, making them look like a single device to the controller. Most SPI devices transfer 8 bits at a time; daisy-chaining three 8-bit devices will operate as if they are a single 24-bit device. Figure 2 shows how this is done. You will notice that I only need one SS* signal which is connected to all of the nodes.
If you follow the flow of data from the controller’s MOSI (D11, an output), you will see it feed into Node 2’s MOSI (D11 again, but an input). As bits are clocked into Node 2’s MOSI, earlier bits are transferred from Node 2’s MISO (D12, an output) to Node 1’s MOSI (D11 again), and from Node 1’s MISO to Node 0’s MOSI. The bits stored in Node 0 then pass back to the controller’s MISO. So for the controller, MOSI is an output, while MISO is an input. For the three nodes, the reverse applies.
So if you transfer 3 bytes (24 bits) of data from the controller, the first 8 pass through Node 2, then Node 1, finally ending up in Node 0. The next 8 bits will pass through Node 2 and stop in Node 1. The final 8 bits will only make it as far as Node 2. Meanwhile, all 24 bits that were stored in the three nodes will be clocked into the controller.
Using the daisy-chained configuration, I can connect (almost) as many nodes to one controller as I’d like. In practice, due to limitations in fanning out the bussed signals from the controller (SS* and SCLK), and in keeping the number of nodes manageable for the software, I will be limiting each controller to 16 nodes.
I2C over servo cable
I2C was designed to work over relatively short distances, limited by the capacitance between the conductors in the cable. I am planning to use I2C to connect modules for several of the systems on the project, such as the switch machine controllers, the banks of relays in the block switching matrix, and trackside signals. Since some of these systems will have their modules spread out, I need to test a few different forms of I2C cabling to see what works where.
For example, on the bus connecting the switch machine controller modules I am considering using standard white/red/black servo wire for the I2C bus. Because the controller modules will receive their 5 volt power independently of the bus, the servo cable only needs to carry the SDA (data), SCL (clock), and ground signals. To keep the capacitance between the SDA/SCL wires and the ground balanced, the center wire (red) will be the ground wire. I’ve arbitrarily decided to make the white wire SDA and the black wire SCL. So far this has worked out fine in testing over 2 meters (about 6.6 ft) of servo wire. If I have problems with driving longer runs of servo wire, I have some I2C bus extender chips (P82B715) which will increase the distances I can run the bus between modules.
Because the block switching controller modules will all reside in a pair of rack-mounted units, the distances between modules will be short enough that I won’t need the bus extenders there.
UDP network communications
Most network communications over Ethernet cable is done using one of two protocols, TCP and UDP. TCP provides message acknowledgements and error-correction, while UDP has neither unless the application using UDP provides them. You can find a detailed comparison of the two protocols here. The bottom line is that UDP is more suited to real-time communications, at the expense of communications reliability.
For a steady stream of data, say from a sensor or to a display device, timing is usually more important than completeness of the data samples. An example of this is my throttle system. Sometimes though, it’s important that every message (datagram in UDP terms) be received and processed correctly. For example, dropping a command intended to position a turnout, or to switch a track block to another throttle’s control, would be very bad news for operations. For this type of application, you need either the reliable communications of TCP with its real-time performance issues, or a protocol layered on UDP to provide reliability and performance. A draft standard for a protocol named Reliable UDP or RUDP exists, but it’s more complex than I want to implement on Arduinos, so (in true Maker fashion 😁) I have rolled my own. For now I will call it UDP-E, or UDP with Echo.
The first application of UDP-E which I have tested is for the block switching system. The control console, a pi-topCEED which contains a Raspberry Pi 3, will connect to all subsystems via its Ethernet port through one or more network switches. The block switching system will have an Ethernet adapter which will connect to the network. This adapter module will be connected to an Arduino processor, which will translate commands which arrive via UDP datagrams into I2C messages for the block switching controllers.
The table below shows the format for the messages controlling the block switching relay matrix.
|0||signature||82 (ASCII code for ‘R’)|
|1||sequence||0 to 255|
|2||block||0 to highest block number|
|3||throttle||0 to 7, or 255 to deselect all throttles|
The signature byte marks this as a relay-matrix command (‘R’ is for relay). The sequence byte is incremented with each new command being sent, rolling over to 0 after 255; it is not incremented when a command is re-transmitted (more details below). The block and throttle bytes indicate which block is to be switched, and to which throttle channel it is being switched.
Each time the block switching controller receives a datagram from the central processor, it first echoes the datagram back to the central processor before acting upon it. The command is executed only if the datagram is the correct length (4 bytes) and the signature byte is the character ‘R’. The sequence byte is not used at all by the controller, only echoed back to the central processor. The throttle byte selects the throttle to which the block’s track will be connected; if the throttle byte is 255, the block is disconnected from all throttles.
Meanwhile, the central processor listens for the echoed datagram after it transmits each one. If no echo is received after a short wait, the same datagram (with the same sequence byte) is sent again. This process repeats up to five times until the exact same datagram is received. If the expected echoed datagram is still not received after five retries, this means there’s a hardware or software issue that must be resolved.