This article describes a simple networking system that supports multiple slave devices connected to a single serial port on the host system. The microcontroller side only requires a UART and can be implemented with a minimal amount of code.
Almost two years ago I designed and built a small multi purpose bench testing tool to help with fault finding my electronics projects. The goal was to make it easier to measure voltages and currents at multiple points in a circuit without a desk full of multimeters. I wound up with a workable unit (workable enough for me to use at least) but the end result was not much of an improvement over the previous process.
My original design was an all-in-one system consisting of a Raspberry Pi, a Stellaris Launchpad and an analog input board that provide the input protection and current sensors. The goal was to be able to use it as a standalone device as well as being able to interface with it using a laptop or tablet to provide data recording and analysis functionality.
My need for such a tool still exists (if anything, it has grown) but I’ve been able to narrow down the features to a ‘minimum viable product’ level. The new design is modular and moves all of the data processing and control functionality to a host laptop. Each of the measurement modules is responsible for a single type of measurement (voltage, current or anything else required) and they can be mixed together in an ad hoc fashion to build the test rig needed for the task at hand.
One of the issues I need to solve is how to provide a simple method for multiple small microcontrollers to participate in a network - this allows the master device (the laptop or desktop) to discover what sensors are connected, configure them and read the data they are producing. Ideally this should involve a minimal amount of additional hardware (wireless and Bluetooth solutions are ruled out) and be simple to implement in software on the microcontroller itself.
This post describes the solution I am planning on using - at this stage it is simply a design sketch and hasn’t been implemented.
Given that almost all microcontrollers have at least on hardware UART (and those that don’t can implement it in software it makes sense to build the network around a serial protocol. Rather than have a lot of direct serial connections to the individual sensors I needed a way to share a single serial port on the master with all of the sensors.
The method I’ve come up with ties all the slave Rx pins to the controllers Tx and all the slave Tx pins to the controllers Rx in a Multidrop Bus configuration. Unlike the MDB protocol (ccTalk) used in vending machines which requires 9 bit characters I will just use standard 8N1 serial packet framing. This will require a simple hub unit that provides an interface between a single USB port on the controller and a number of sensor units as shown in the diagram below.
Anything transmitted by the controller is received by all slave units simultaneously and the slave unit will need to inspect the content of each packet to determine if it was directed at it or whether the packet can simply be ignored. The use of a diode on the slave Tx lines is due to the electrical nature of serial communications - the idle state of the Tx pin is high (in this case 5V) which is then pulled low as needed to send the data packet.
When one sensor starts to transmit data it will pull the line low which will try to sink all the current being driven by the other sensors on the bus. To control the maximum amount of current that needs to be sunk we use a pull up resistor on the shared Tx line and use a diode to ensure the slave devices can only pull the line low, not drive it high.
There are two remaining issues to solve - we need a mechanism that allows the slave device to determine that a packet it intended for it rather than some other slave and a method to ensure that only a single slave is transmitting at any given time. Both of these issues can be solved in software by design the protocol appropriately.
Network Packet Format
As well as addressing the issues mentioned above the protocol needs to be relatively straight forward and simple so it can easily be implemented on a microcontroller with minimal overhead. My design is simple packet based protocol which is driven entirely by the master device and all packets originating from the slaves are responses to requests sent by the master - a slave cannot send an unsolicited packet. The general packet format I came up with is shown below:
The first byte of each packet gives the size of the entire packet including the header, payload and trailer. This limits the packet size to a maximum of 255 bytes and the payload to 251 bytes.
The number of slave devices in the network will always be fairly small so I have combined the command and address information into a single byte with the address occupying the low order 3 bits. This allows for 8 unique address values - the lowest (000 binary) and highest (111 binary) are reserved for the address assignment procedure (which I will describe below) which allows for up to 6 slave devices on the network. Each slave has it’s own unique address on the network and will only respond to packets that contain it. The same packet format is used for all packets on the network regardless of the sender - a packet sent by the master will use the address of the target slave device and packets sent by a slave will use it’s address to indicate the sender.
I originally considered adding a Slave Select (SS) line to identify the slave being addressed (this method is used in SPI systems). Controlling this from a single serial line on the master device would complicate the design of the hub and not simplify the design of the slaves in any meaningful way.
The address of the device is stored in it’s EEPROM but can be modified dynamically by the network. I considered using a simple DIP switch to set the address but decided to use the network protocol to assign the address instead. To assist with this each slave requires a single select push button which is used to help in the address assignment process (which will be described later), this seemed to be a simpler solution.
The final issue to overcome is a way of ensuring only a single slave is transmitting at any given time. To manage this the network uses time based multiplexing as shown in the diagram above - when a packet is sent to a particular slave it is allowed a window in which it can respond, if no response is forthcoming within that window the address is assumed to be unused and the bus is open again.
Each packet has a 16 bit CRC value as a trailer which allows invalid or corrupted packets to be detected - if all responses from a single slave address are invalid this would indicate that there are multiple devices on the network with the same address.
The most significant 5 bits of the command/address byte in the packet is used to indicate the type of packet (the command being sent). The payload format for each command will differ depending on the sender of the packet - many packets sent by the master will have an empty payload while the corresponding response from the slave will contain data.
Commands can be split into two groups - network control commands which are used to initialise, reset and query the network and application specific commands, I will only describe the network control commands here. Bear in mind that this is only an initial design at this stage, the actual commands and payloads will probably change during implementation.
Two of the address values, 0 (binary 000) and 7 (binary 111) are reserved for the unassigned and broadcast addresses respectively. A slave device should only accept packets to the unassigned address if it currently does not have an address, all slave devices (regardless of their current address) should accept packets sent to the broadcast address. In both cases the slave should not send a response as multiple slaves will be processing the packet.
The RESET Command
This command is used to clear any internal state of the slave device returning it to the same state it would be in immediately after power cycling. The intent is to allow the slave devices to be re-initialised without requiring power cycling.
This packet does not have a payload from either the slave or the master. It may be sent as a broadcast packet in which case the slave does not send a response.
The QUERY Command
The QUERY command is used to identify the type of the device. The packet from the master does not contain a payload, the response from the slave will contain a device type name in ASCII. There are no restrictions on the type name (it should be a human readable string though) and multiple devices on the network can return the same value.
The intent of the type name is to allow the master to determine how to use the device, in the bench tool system device names would be strings such as ‘VMETER’ for a voltage measurement device or ‘AMETER’ for a current sensing device.
The SETADDR Command
This is one of the main commands used to manage device addresses, it is used by the master to set or change the address of a slave unit. The payload from the server is a single byte with the new address in the least significant 3 bits. If the client is required to send a response it will have an empty payload.
This will be the most complex of the commands to implement in the server as the behaviour changes depending on the target address;
- If the target address is a single slave address (1 through 6) the slave should change it’s address to the new address specified in the payload and send a response from the newly assigned address.
- If the target address is the broadcast address and the new address specified in the payload is the unassigned address the slave should set it’s address to unassigned and not send a response.
- If the target address is the unassigned address and the select button on the device is pushed the slave should accept the new address. No response is required.
In all other cases the slave should simply ignore the packet.
This set of commands is enough to identify all devices on the network, manage the addresses of those devices and reset them to a known state if needed. With this core in place application specific commands can be implemented to read sensor values and set configuration information.
At this stage the system is little more than a proposal - there is a fair bit of work required to determine if it will work and if it will be suitable for the task I want. The first thing to verify is that the electrical characteristics of the bus are what I expect them to be. This should be relatively simple to setup and test on a breadboard with a few Arduinos as slave devices.
I will also have to verify that the protocol works as expected and that the process of address assignment works smoothly without requiring too much interaction from the end user. This can be simulated completely on a PC to verify the process and then implemented on a microcontroller.
One major concern that I do have is that the sample rate of the devices will be high enough to be useful. Because the protocol does not allow unsolicited messages from slave devices all data must be gathered through a polling mechanism. According to my initial calculations (which are very rough) I should be able to poll data from 6 active slave devices in around 40ms per cycle which is around 25Hz.
I am not attempting to replace the function of an oscilloscope so a sample rate in the 20Mhz range or higher is not required - 25Hz is enough to see relationships between values (change in current draw as a motor is activated for example) but events that have a very short period (such as a voltage drop caused by an increase in current draw) may be missed. How well this works in practice remains to be seen.
Even if this method turns out to be unsuitable for my particular application I think it is a useful design with a range of other uses. I welcome any feedback and if you notice something that I might have missed in the overall design please let me know in the comments.