Many of Iowa Scaled Engineering’s products interface with something called MRBus. I’ve been meaning to write this post for some time, but turns out it’s a rather complex narrative to tell.
So let’s get right down to it: What the heck is an MRBus, and why would somebody want to use it? In this rather long blog post, I’ll introduce you to our microcontroller data network and walk through a simple example of how it can be used. In future installments of this series, we’ll step into more advanced use cases.
MRBus – initially meant to be short for Model Railroad Bus – is a low speed, packetized, distributed data network designed to allow microcontrollers to exchange small amounts of data. Based on time-tested RS-485 technology, the idea behind MRBus is to distribute intelligence and processing throughout the network so that the system is modular, easily reconfigured, and resilient against individual node failures – as opposed to a single master controlling a number of slave units, where if the master goes, nothing works. (As a note, it’s pronounced EM – ARE – Bus, although even its creators get lazy and call it “murbus” from time to time.)
Despite its name, MRBus is not limited to being just a model railroad control network. Nothing about the protocol is specific to model railroads – in fact, it was originally derived from a solar race vehicle telemetry and control network, which in turn was derived from a large truck sensor network standard. At its core, it’s a generic way for small microcontrollers to send small, simple messages to each other. (In an OSI network model view of the world, it describes layers 1-3 – physical, data link, and network.) Since MRBus was created thirteen years ago, it has been adapted to such applications as scientific experiment instrumentation, home automation, weather stations, and security systems, in addition to still serving as the foundation for a variety for model railroad uses such as signaling, turnout control, and fast clocks. There’s also now a wireless version – called MRBee and based on Digi XBee radios – to connect where it’s impractical or impossible to run twisted pair.
MRBus networks consist of functional blocks (called nodes) joined by two pairs of wires. A node is simply a microcontroller attached to the bus that performs a function that has a unique address. (Any given MRBus segment can have up to 253 devices – address 0 and 255 are reserved for special purposes.) Example node functions include:
- Controlling 64 indicator lights (the MRB-GIM2)
- Monitoring track occupancy for up to 12 blocks (the MRB-BD42 and MRB-BD4X)
- Monitoring temperature/humidity sensors (the MRBW-TH, MRBW-RTS)
- Controlling/displaying time (the MRB-FCS and MRB-FCM)
- Controlling garden irrigation zones (the MRB-IAS combined with an I2C-RELAY16, relay board, and obviously a garden)
- Controlling model railroad signals and switches for a CTC control point (the MRB-IAS combined with a MRB-BD4X and I2C-XIO)
- Many, many more things that we haven’t even thought of yet…
A Simple Example
The easiest way to grasp what MRBus can do is to jump right into a very simple example. We’ll skip over all the technical details of making this work, and instead jump right to a working example. We’ll start with something very simple and not overly interesting – mapping six switches to six lights over the bus. It’ll help familiarize us with the bus, and give a solid foundation before moving on to more advanced uses.
We’ll be using two MRB-GIO (generic input/output) nodes with the stock firmware, one as our sender and one as our receiver. With the stock firmware, each can serve as 16 inputs, 16 outputs, half-and-half inputs and outputs, or any combination thereof in more complex configurations. We’re going to use very basic, simple configurations to start with – one node will be configured as 16 inputs, and one will be configured as 16 outputs that mirror the 16 inputs on the other node. We’ll call the sending node address 0x03, and the receiving node address 0x04.
The nodes will be connected together with standard Cat 5e network cable. The orange pair provides power, and the blue pair carries data. (Nodes can either provide four terminal connectors for individual wires, or a standardized RJ45 8p8c connector.) In addition, we need a way to provide power to the bus and provide bias voltage (more about that later, but you’ve got to have it – trust me…), so we’ll add an MRB-RJ45-PWR to bias things and provide an easy connection to a mains power adapter.
Since I’m lazy by default, I’m only going to hook up 6 of the channels on each – IO0-IO5. So, on the sender, we’ve got six switches to ground, taking advantage of the pull-ups on the input pins to pull them high when the switches are open. On the receiver, we’ll take six green LEDs and connect them to the same numbered I/O pins (IO0-IO5) through 360 ohm resistors.
We also need to do some simple configuration in each node’s configuration memory. We need to give each of the two nodes a unique address, tell each node that it’s either an output or an input, and then tell the output module who to listen to for inputs. We’ll discuss this is more detail later, but it’s as easy as putting a few numbers into a few memory locations using an AVR programmer.
Ta da! Now when I throw a switch, almost instantaneously the corresponding light turns on at the corresponding output. The bus lights blink intermittently, showing that data is flowing between the two nodes.
Looking Behind The Curtain
Okay, now we’ve seen that we can send up to sixteen inputs across a single pair of wires, but how exactly did that just work? How exactly do nodes inter-operate over MRBus?
The short version is that nodes typically do four things – send statuses, listen to statuses, send commands, and react to commands. Let’s look at the idea of status packets first, since that’s the most common way nodes communicate with each other.
MRBus status packets are based around the idea of producers and consumers. An example producer would be a node that monitors inputs, such as node 0x03 in our example. Every time one changes or a set time period has elapsed without a change, that node would broadcast a status packet saying, “Here’s the state of my inputs right now.” It doesn’t know – or care – who might be interested. It just sends it to the world.
Somewhere on the other end of the bus might be another node, such as 0x04 in our example network, whose job is to turn on/off status lights. If you wanted it to display the status of an input on node 0x03, you’d configure it to consume status packets from 0x03, and make your lights mirror the inputs. On most nodes, that can be configured by setting a couple of values in their configuration memory.
Let’s see how these abstract concepts apply to our example now. To understand better what’s actually going on under the covers, we’ll add an MRB-CI2 computer interface to the bus. Like all nodes, it’s as easy as connecting one pair for power and one pair for data. I’m going to plug it into the pins extending from our MRB-RJ45-PWR. We’ll then connect it to a PC using a mini-USB cable. The MRB-CI2 is an MRBus to computer interface, allowing you to use a PC to interact with MRBus.
The CI2 is based on an FTDI USB-to-serial converter, and as such it will appear on your computer as a serial port when plugged in. (The port will be COMx: on Windows machines, /dev/ttyUSBx on Linux boxes, and honestly we have no idea on a Mac, but it will work.) We’ll need a terminal program to communicate with it. Since we’re Linux folk around here, we’ll use CuteCom as it’s readily available in many Linux distros (and apparently on Macs!) and provides some features that make it well suited for MRBus communications.
Once you’ve opened CuteCom, set it for the correct serial port, a baud rate of 115200, 8 data bits, 1 stop bit, and no handshaking.
If everything is working, we’ll start seeing output in the terminal that looks vaguely like:
P:FF 03 08 16 F2 53 FF C1 P:FF 04 08 DB 8C 53 FF C1
Note that each line on the screen corresponds with the bus activity lights blinking – that’s right, each one of these lines is an MRBus packet. The “P:” tells us that it was a good packet (meaning the checksums and length tests pass), and then the rest is the actual packet bytes, expressed as two hexadecimal characters. (Why hex? Because we’re engineers and software developers, and after a while you just start thinking in hex… Makes balancing your checkbook a bit of challenge, however, when you realize that 107 + 9 doesn’t equal 110 in real life.)
Each packet consists of up to 20 bytes. It has a source address (1 byte), a destination address (1 byte, 0x00 = ignore, 0xFF = broadcast to everybody), a byte describing the length of the packet, a byte of packet type, and two bytes of checksum to assure the packet hasn’t been corrupted.
Let’s break down one of our sample packets from our input node and see what’s in it.
- 03 – Byte 1 – Packet source address. This is who is sending the packet.
- FF – Byte 2 – Packet destination address. This is who is intended to listen to this packet, or in this case “0xFF”, which means “everybody”
- 08 – Byte 3 – Length. The length in bytes of the packet , in this case eight.
- 16 F2 – Byte 4 & 5 – 16-bit CRC checksum. This ensures that the packet arrived at the destination uncorrupted.
- 53 – Byte 6 – Packet type. This is the primary way of determining what kind of packet you’re looking at. It’s typically an upper-case ASCII character, and ‘S’ (0x53)
- FF C1 – Byte 7 and beyond – packet data. On some packet types (such as ‘C’ for “command”), the first byte is the type of command. On others, like status packets, this will be the start of actual data. In this case, this is the data of the 16 bits of input data, expressed as a hex number. In this case, it shows us that the unconnected inputs are all high (caused by the pullup resistors), and that all of our switched inputs are grounded except the one on IO0. (0xFF = 0b11111111, 0xC1 = 0b11000001, with the lowest order bit being on the right)
If we look at the packets our transmitter (0x03) is sending, we see that they’re ‘S’, or “status” packets, and they’re transmitting the state of our sixteen input lines as two bytes. If we change the state of various inputs, you’ll see the transmitter send out another packet with updated bits. Otherwise, a status packet will pop out every few seconds. MRBus nodes typically retransmit their status based on time, since the assumption is that a node may have been offline or missed the last packet.
In addition, we’ll see our receiver (0x04) transmitting status packets as well. The status there relates to the output values of the pins, as that node is configured as all outputs. This highlights a key concept in MRBus – producing nodes typically will transmit their status without any regard for who (if anybody) is listening. Anybody can then be configured (such as our receiver, node 0x04) to receive types of data they understand. For our use here, we’ll see that the outputs being sent by node 0x04 match the inputs being sent by node 0x03, within a packet cycle. Obviously the packet from 0x03 must reach 0x04 and be processed before the outputs change.
We’ve covered status packets, which are great for continual updates of data between nodes. However, sometimes there’s a need to tell a node to do something – NOW! That type of packet is called a command packet. Rather than a status that’s intermittently transmitted, commands are transmitted as a result of an event or to specifically instruct a node to take an action. The most basic of these is a “ping” – basically an “are you there” packet. You could “ping” node 0x03 through the computer interface, and it would respond with a ping response if it was there.
Let’s try it. A ping is command type ‘A’, with no arguments.
To send a command through the CI2, you format it like this:
:SA->DA CC D0 D1 D2 D3;
- A colon starts a packet to be transmitted
- SA is the source address
- DA is the destination address (or FF for “everybody”)
- CC is the command type
- D0 – Dx are optional data byes
- A semicolon and carriage return terminate the command
(The MRB-CI2 automatically calculates the length and checksums, so don’t worry that you don’t have to enter them directly.)
A ping to node 0x03 would look like:
If everything is properly formed, the CI2 will respond with “Ok”. You’ll see something like the following on the terminal:
:FE->03 41; Ok P:03 FE 06 72 AE 41 P:FE 03 06 D1 1E 61
The first packet is the outgoing command from the CI2 to node 0x03. The second is the ping response coming back from 0x03 to 0xFE (the fake address I used for the CI2 in the command). Note that the command type in the response is 0x61 – a lowercase ‘a’. By convention, commands are uppercase and the responses are in lowercase.
Other types of commands could be things like write new configuration values, read back configuration values, change states (start a timer, turn on sprinklers, change a model railroad turnout or signals, etc.), or even reset the node entirely.
The Physical Bus
Now that we’ve talked about how nodes communicate data at a logical level, let’s get down into the nitty gritty of how actual electrical signals carry those bytes between nodes.
MRBus requires a minimum of two twisted pairs of wire connected to every node. One pair actually carries the data as a differential signal – a modified form of RS-485, and the other pair carries power (typically 9VDC up to 1A, but can range from 8-15VDC). Normally RS-485 only allows for 32 nodes on a network, but by using 1/10th unit load transceivers, we can accommodate the full 253 nodes that can be addressed on any particular bus segment, giving plenty of capacity for most model railroad or telemetry networks.
The bus is based around RS-485 transceivers, but isn’t strictly standard RS-485. Before each packet is transmitted, nodes go through an arbitration phase to establish bus ownership, where the bus is actively driven when transmitting a zero and passively pulled to the correct state when transmitting a one. Each node transmits its own address in this phase to assure that it will be sending a unique number. Because a zero will override a one, if two nodes are transmitting at the same time, one of them will notice the bit it’s reading back (zero) doesn’t match what it’s sending out (one). This allows nodes to detect and avoid collisions without any sort of central coordinator.
The one consequence of using this collision avoidance scheme is that every bus segment must have one set of bias resistors on it to assure the lines are pulled to the idle (one) state when nobody is transmitting. These are included on the MRB-CI2, MRB-AP, and MRB-RJ45-PWR, making it easy to get each bus segment biased with nodes you’re probably going to have anyway.
The arbitration phase is very slow (4800 bits/second), because the bus is a giant RC circuit and sufficient time must be given for the bus biasing resistors to pull around the capacitance of all the bus wiring. Once arbitration succeeds, the winning node cranks up the baud rate to 57.6kbps and actively drives the bus to both 1 and 0 states to actually transmit the packet.
In addition to the wired version of MRBus, there’s a wireless variant that operates over 2.4GHz XBee radios for those places where running a wire is impractical or impossible. The wireless variant of MRBus is nicknamed MRBee. Nodes can be natively wireless, or can communicate over a wired segment to an MRB-AP (MRBus Access Point), which bridges wired and wireless traffic.
Configuring the Nodes
The only part of our test network that we haven’t yet talked about is the actual configuration of each node. MRBus isn’t magic, and it can’t use some psychic ability to figure out what you want it to do. Each node needs some amount of configuration to tell it what to do.
Nodes store their configuration in non-volatile EEPROM memory. We’ll call these addresses “configuration values”, or CVs for short. There are several ways to program values into EEPROM, but probably the easiest is an AVR programmer. (Michael and I personally use the USBtinyISP from Adafruit, but there are a number of units on the market from other vendors.) Each MRBus board has a 6-pin programming header on it, and with a programmer and appropriate software, you can load new firmware or read/write/change EEPROM.
The two most important values for most nodes are the node address – always at CV 0 – and the period between status packet transmissions, stored as a 16-bit number in CV 2-3 that represents the interval in 0.1s increments.
The GIOs, however, have more CVs specifically related to their function. The most important of these is CV16, which configures whether the node is an input, output, or some of each. 0x00 configures the device as all inputs, 0x01 configures it as all outputs from a single source, 0x02 configures it as half/half from a single source, and 0x20 puts it in complex configuration mode. Complex mode is beyond the scope of this blog post, so we’ll ignore it for now, but it gives us the power to configure each I/O line independent of the others.
For our input node, we’ll set CV0 to 0x03, CV2 to 0x00, CV3 to 0x14, and CV16 to 0x00. That gives an address of 0x03, 0x0014 or 20 deciseconds between status packets, and configures the node to be all inputs.
For our output node, we’ll set CV0 to 0x04, CV2 to 0x00, CV3 to 0x14, CV16 to 0x01, and CV17 to 0x03. That gives an address of 0x03, 0x0014 or 20 deciseconds between status packets, configure the node to be all outputs from a single source, and specify the source address as 0x03.
The easiest way to set CVs is with an AVR programmer using the program “avrdude” (AVR Downloader/UploaDEr) in terminal mode. It’s available as a package for most Linux distros, or for Windows folks you can get it as part of WinAVR or MHV AVR Tools. To get it working, connect the programmer and, from the command line, type:
avrdude -c <progtype> -p <devtype> -F -t
<progtype> is the type of your programmer. If you’re using a USBtinyISP like me, it’ll be usbtiny
<devtype> is the AVR microcontroller type on the board. For the MRB-GIO that’ll be an atmega328 or atmega328p. Since avrdude doesn’t usually know about each specific subtype, you often have to add a -F command line option if the AVR type doesn’t match exactly.
To set a CV, you’d type the following from a command prompt:
write eeprom <CV#> <value>
When you’ve configured all of the CVs that you need to set on a given node, simply type “quit” to exit terminal mode, disconnect the programmer, and the node is ready to go.
But Wait, There’s More!
MRBus is open – the specification, the hardware we produce, and the reference implementation code. Open, open, open. Plus, in addition to the more specific-purpose nodes we sell (like the MRB-BD42, which is pretty much only good for model railroad block detection), Iowa Scaled Engineering has a variety of products designed for those wanting to create their own MRBus nodes. There’s the MRB-ARD, an Arduino shield with an MRBus interface (and there’s a ready-made Arduino library!). There’s the MRB-GIO, which is just an AVR and MRBus interface, with 16 of the remaining I/O lines pinned out to convenient screw terminals. There’s the MRB-IAS, providing an AVR, MRBus, and I2C interface on a single board (which goes nicely with the I2C-XIO, which allows you to add up to 320 I/O channels to a MRB-IAS or other nodes with an I2C bus!) The possibilities for what it can do are nearly limitless. This post is the first in a series that will explore how MRBus can be used in a variety of applications.
In our next installment, we’ll look at MRBFS, the MRBus Filesystem, and how it can be used for controlling nodes from a computer or collecting data from nodes. Stay tuned, there’s so much more!
If you just can’t wait for the next part in our series, be sure to check out the MRBus website. It’s a little out of date since I’ve spent most of my free time on ISE lately, but the basics about joining the discussion list, the technical specification, and developer information are all there to get you started.