Notes on Interfaces

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. This means 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, labeled as nodes, which are connected to a controller. MOSI carries data from the controller to the devices, while MISO flows from the devices back to the controller. When the clock signal SCLK is toggling, data bits are transferred across MOSI and MISO at the same time. As you can see, using three nodes requires three separate SS* signals from the controller.

Figure 1: SPI in typical configuration

The code running on each processor sets whether its SPI interface will act as a controller or a node. This will determine the directions of the SPI signals for each device.

SignalControllerNode
MOSIoutputinput
MISOinputoutput
SCLKoutputinput
SS*outputinput

For my track sensor network, I am using an alternative design, where the devices are daisy-chained, making them look like a single peripheral 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.

Figure 2: SPI in daisy-chained configuration

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.

NOTE:

The following section has been updated to describe an improved design of my UDP-E protocol. The main changes are:

  • Messages are now divided into two sections: a 6-byte header followed by one or more bytes of payload data.
  • The signature is now the first 2 bytes of the header.
  • The sequence number is now the next 2 bytes following the signature in the header.
  • Following the sequence number in the header is a 16-bit (2 byte) checksum which is used to verify correctness of the header in received messages.

The UDP-E protocol, like UDP in general, can be used to carry different types of messages, using the signature to distinguish the message types. The sequence will usually increment with each message transmitted, except when a message must be repeated because the sender failed to receive an echoed copy of the message before a timeout period expired. This is an improvement over using UDP alone, which has no built-in mechanism for a sender to detect that a message did not arrive at the receiver.

The checksum is computed using ones’ complement arithmetic by adding the other header fields as 16-bit values, and setting the checksum field to the negative of the sum. The checksum is verified by adding all three header fields (signature, sequence, checksum), again using ones’ complement addition. A correct sum will be 16 bits of all 1’s (1111 1111 1111 1111 in binary). If the sum is anything else, then there is one or more bits in the header that are wrong, and the message will be discarded.

I have been writing a test implementation of UDP-E where 10-byte payload data is sent from a Raspberry Pi 400 over an Ethernet connection to an Arduino UNO microcontroller with an Ethernet interface installed on it. Both devices connect to each other with Ethernet cables and a network switch in between.

The table below shows the format for the messages being transmitted by the test program, written in C++, which runs on the Raspberry Pi 400.

byte offsetsnamedescription
0-1signature16-bit value to identify message type
2-3sequence0 to 65535
4-5checksum16-bit negative sum of above fields using ones’ complement math
6 and uppayloadspecific to each message type, 10 bytes in test program

Each time the Arduino receives a message (datagram in UDP speak) from the central processor (Pi 400), it first echoes the exact message back to the central processor before acting upon it. In an actual application, the payload would contain a command for the Arduino to act upon. The command is executed only if the message is the correct length (16 bytes for test version) and the signature bytes are correct (base-16 value 424C for this test). The checksum must also be correct so the ones’ complement sum of the header fields is a 16-bit value of all one-bits (in base 16, FFFF).

Meanwhile, the central processor listens for the echoed message after it transmits each one. If no echo is received after a short wait, the same message (with the same sequence byte) is sent again. This process repeats up to five times until the exact same message that was sent is received. If the expected echoed message is still not received after five retries, this means there’s a hardware or software issue that must be resolved.

When I get a chance, I plan to upload to my GitHub repository copies of both the controller code running on the Raspberry Pi, and the Arduino code which receives and echoes back the messages sent by the controller.