MRBus Arduino Library Reference

The MRBus Library for Arduino is a C++ reimplementation of the MRBus Core Library designed specifically with use in the Arduino environment. It trades the flexibility (and complexity) of the original core library for simplicity and ease of use.

The latest MRBus Library for Arduino code is now available from Github as part of Iowa Scaled Engineering's MRB-ARD project. The library, written originally to support the MRB-ARD Arduino shield, is now maintained as part of the same repository.

To install, place the src/MRBusArduino directory your arduino/libraries directory. Once installed, you'll be able to include the MRBus library in projects just by choosing "Sketch -> Import Library... MRBusArduino".

An example Arduino sketch using this library to construct a simple MRBus node is distributed with the library, and can be opened from File -> Examples -> MRBusArduino once the library is installed.

Hardware

Right now, the library is known to be compatible with Arduino Leonardo boards, because that's what it was developed with.

It will use digital outputs 0-2, being assigned to TX, RX, and TXEN respectively.

The only suspected issue with classic Arduino boards is the Arduino Uno, which are based on a less precise ceramic resonator clock rather than a crystal. These may work in many cases, but the ceramic resonator is likely not stable enough to handle long asynchronous serial bursts.

It will not work with the new Arduino Due, because that board is based on an ARM processor and not an AVR, requiring significant behind-the-scenes work to handle the radically different hardware.

License

The MRBus Library for Arduino is free software, licensed under the GNU General Public License v3.

A Note About Variable Types

Despite being designed for the Arduino, I didn't want to do anything that would restrict it from being a general-purpose AVR C++ MRBus library. So, throughout the library will use standard C99 variable types, like uint8_t or uint16_t. In the world of the Wiring development environment, uint8_t is the equivalent of byte, and uint16_t is the equivalent of unsigned int.

Classes and Structures

class MRBus

The MRBus class holds node configuration information, the current receive and transmit queues, and interfaces to the interrupt routines and associated buffers used behind the scenes. Any sketch implementing MRBus should have one global MRBus object (meaning, defined above both loop() and setup()).

Because of limitations associated with being physically linked to the Arduino's UART ISR vectors, any node can only have one MRBus object. In the future, it may be possible to instantiate multiple MRBus objects on devices like the Mega2560 that have multiple UARTs, but this isn't yet completed.

Public Class Interface:

class MRBus
{

public:
void begin();
uint8_t hasRxPackets();
bool getReceivedPacket(uint8_t* pkt, uint8_t len);
uint8_t hasTxPackets();
bool queueTransmitPacket(uint8_t* pkt, uint8_t len);
uint8_t transmit();
uint8_t transmitBlocking();
void setNodeAddress(uint8_t nodeAddress);
uint8_t getNodeAddress();
void setNodePriority(uint8_t nodePriority);
uint8_t getNodePriority();

}


struct MRBusPacket

The MRBusPacket structures are used to encapsulate an MRBus bus datagram (packet) for passing in and out of the MRBus transmit and receive interface.

typedef struct 
{
   uint8_t pkt[MRBUS_BUFFER_SIZE];
} MRBusPacket;

Currently, it just encapsulates the raw data, as stored in the pkt array of uint8_t elements. Offsets into the datagram structure can be found under the Constants section.

MRBus Class Public Methods / Functions

void MRBus::begin()

The MRBus::begin() function should be called within the setup() function of your sketch. This initializes buffers and sets up interrupt handling. Your node should also probably implement MRBus::setNodeAddress() shortly after calling the MRBus::begin() function.


void MRBus::setNodeAddress(uint8_t address)

The MRBus::setNodeAddress() function should be called within the setup() function, shortly after the class is initialized with MRBus::begin(). The function will accept any 8-bit address as this node's address, but the reserved addresses 0x00 (ignore) and 0xFF (broadcast) should only be used in special cases. Any node that transmits must have a valid node address. If not set, the node address will default to 0x03.

Also, per the MRBus specification, the node address should be stored in Configuration Value 0. The CVs are typically implemented as the EEPROM byte of the same address.


uint8_t MRBus::getNodeAddress()

The MRBus::getNodeAddress() function will retrieve the current MRBus node address from the MRBus object. It is the corresponding accessor function to MRBus::setNodeAddress().


void MRBus::setNodePriority(uint8_t priority)

The MRBus::setNodePriority() function is not needed by most applications. The MRBus specification allows for 16 priority levels based on break bus arbitration ties (values 0-15). Lower numbers will give nodes an increased chance of getting a packet transmitted on the first attempt. Thus, nodes that are sensitive to transmission delay (clocks and model railroad throttles being the two classic examples) should use a lower number. The priority defaults to six, and most nodes should keep that prioirity to maintain fairness in traffic balancing.


uint8_t MRBus::getNodePriority()

The MRBus::getNodePriority() function will retrieve the current MRBus node transmission priority from the MRBus object. It is the corresponding accessor function to MRBus::setNodePriority().


uint8_t MRBus::hasRxPackets()

The MRBus::hasRxPackets() function will return the number of unprocessed packets sitting in the receive queue. A zero indicates no unprocessed packets. Packets arrive in the receive queue from the MRBus interface and remain there until the application retrieves them using MRBus::getReceivedPacket().


bool MRBus::getReceivedPacket(MRBusPacket& mrbPacket)
bool MRBus::getReceivedPacket(uint8_t* pktData, uint8_t pktLen)

The MRBus::getReceivedPacket() function will retrieve and remove a packet from the receive queue. Packets get placed into the queue as they're received from other nodes via MRBus.

Two function prototypes are available - one that takes an MRBusPacket structure passed by reference, and one that takes a pointer to an array of uint8_t characters and a buffer length.

The function will return true if a packet has been retrieved from the receive queue and placed in the argument (MRBusPacket or buffer), or false if there are no packets in the receive queue.

Example usage:


MRBus mrbus;

// Your code... things like the setup function

void loop()
{

        // ... blah blah blah - your application here ...

        // Check if we have packets to be processed - clear out the buffer to avoid
        // packets building up.  If your loop executes fast enough that you only want to handle
        // one packet at a time, this can become:  if(mrbus.hasRxPackets())
        while (mrbus.hasRxPackets())
        {
                MRBusPacket mrbPacket;
                bool successfulGet = mrbus.getReceivedPacket(mrbPacket);
                if (false == successfulGet)
                        continue;

                // At this point, a received packet has been placed in mrbPacket
                // Application code should handle the packet in an appropriate way
                // This test that follows is pretty common - it ignores any packet that's not destined to us
                // or to the broadcast address.  More importantly, it serves as an example of how to access the
                // MRBusPacket structure.

                if (0xFF != mrbPacket.pkt[MRBUS_PKT_DEST] && mrbus.getNodeAddress() != mrbPacket.pkt[MRBUS_PKT_DEST])
                        continue;

                // More packet handling should happen here

        }

        // ... blah blah blah - your application here ...
 

Note: The buffer/length interface is intended for advanced users, or where memory is tight. If used length of the buffer should always be MRBUS_BUFFER_SIZE.

Tip: Given the limited memory on AVR-based devices, the packet queue is typically four elements deep, and thus it's important that the receive packets be dequeued and processed in a timely manner. A busy MRBus segment can deliver 200+ packets per second.


uint8_t MRBus::hasTxPackets()

The MRBus::hasTxPackets() function will return the number of untransmitted packets sitting in the transmit queue. Zero indicates no untransmitted packets. Packets are placed in the transmit queue by the application using the MRBus::queueTransmitPacket() function. Packets can be transmitted to empty the queue by using MRBus::transmit() or MRBus::transmitBlocking().


bool MRBus::queueTransmitPacket(MRBusPacket& mrbPacket)
bool MRBus::queueTransmitPacket(uint8_t* pktData, uint8_t pktLen)

The MRBus::queueTransmitPacket() function will take in a packet and add it to the queue of packets to be transmitted. Note: This function only places a packet in the transmit queue and does not actually transmit it. The actual transmission is accomplished by MRBus::transmit() or MRBus::transmitBlocking(), allowing your code to separate out generating packets from the timing-sensitive part of transmitting packets.

Two function prototypes are available - one that takes an MRBusPacket structure passed by reference, and one that takes a pointer to an array of uint8_t characters and a buffer length.

MRBus::queueTransmitPacket() will return true if the packet was successfully queued, or false if the queue was full. If the queue is full, the only option is to transmit packets before adding more to the queue. Data from the input is copied off as part of the function, and thus upon the function returning, the MRBusPacket structure or the buffer may be immediately reused without corrupting the just-queued packet.

The MRBus::queueTransmitPacket() will use the node address stored in the MRBus object ( see MRBus::setNodeAddress() ) as the source address regardless of what's in the incoming buffer. It will also calculate the CRC of the packet. The interface with an MRBusPacket structure expects the correct length to be in MRBusPacket.pkt[MRBUS_PKT_LEN], whereas the buffer/length interface will use the length passed in and place it in the packet's length byte.


uint8_t MRBus::transmit()

The MRBus::transmit() function will attempt to transmit the oldest packet in the transmit queue (see MRBus::queueTransmitPacket()). This function will not always succeed - transmissions may error out because of bus traffic. If an error occurs, the packet will remain at the top of the transmit queue and can be reattempted by calling MRBus::transmit() again. The reason that this function will only attempt a one-time transmission is to allow the application to continue and not block until transmit succeeds. Possible important uses for this include processing any packet(s) that came in while the transmit attempt happened or maintaining other critical updates in the main loop that cannot be stopped for a blocking transmit call.

If you want a call that will block until a packet is successfully transmitted, see MRBus::transmitBlocking().

Return codes from MRBus::transmit() include:

  • MRBUS_TRANSMIT_SUCCESS - Transmission was successful and the top packet in the transmit queue has been sent
  • MRBUS_TRANSMIT_FAIL_NO_PKT - Transmission failed because there were no packets in the transmit queue
  • MRBUS_TRANSMIT_FAIL_BUSY - Transmission failed because another node is transmitting
  • MRBUS_TRANSMIT_FAIL_LEN - Transmission failed because the packet length was incorrect. Bogus packet has been removed from transmit queue.

uint8_t MRBus::transmitBlocking()

The MRBus::transmitBlocking() function will attempt to transmit the oldest packet in the transmit queue (see MRBus::queueTransmitPacket()). This function will block (stall program execution) until either the top packet in the transmit queue is sent, there are no packets to transmit queue, or the receive queue is full and needs to be handled. Upon successful transmission, the top packet in the transmit queue will be removed from the queue.

If you want a call that will not block but rather immediately return an error if transmission isn't possible, see MRBus::transmit().

Return codes from MRBus::transmitBlocking() include:

  • MRBUS_TRANSMIT_SUCCESS - Transmission was successful and the top packet in the transmit queue has been sent
  • MRBUS_TRANSMIT_FAIL_NO_PKT - Transmission failed because there were no packets in the transmit queue
  • MRBUS_TRANSMIT_FAIL_RX_FULL - Transmission was aborted because the receive queue is full. Continuing to block would cause packet loss, and the application needs to process incoming packets using MRBus::getReceivedPacket().

bool MRBus::isTransmitting()

Because the MRBus transmission is largely done with interrupts, the call to MRBus::transmit() or MRBus::transmitBlocking() will actually return before the transmission completes. The function MRBus::isTransmitting() will test if a transmission is still actively being sent.

Constants

Packet Byte Offset Constants

These constants define the index in the MRBusPacket.pkt buffer at which fixed data elements reside.

MRBUS_PKT_DEST  = 0
MRBUS_PKT_SRC   = 1 
MRBUS_PKT_LEN   = 2
MRBUS_PKT_CRC_L = 3
MRBUS_PKT_CRC_H = 4
MRBUS_PKT_TYPE  = 5

Miscellaneous Constants

MRBUS_BUFFER_SIZE - The maximum size of a MRBus datagram packet, defined as 20 bytes total (header+data).

Copyright 2012 by the MRBus Group.
Licensed under a Creative Commons Attribution-ShareAlike 3.0 License.
Questions? Comments? Please email us at support@iascaled.com

Last modified on February 19, 2012, at 01:36 PM