BACnet MS/TP
BACnet MS/TP carries BACnet messages over an RS-485 trunk. The chapter has these pages:
- Byte and packet mode: running the stack’s own MS/TP engine from byte callbacks, its threading and counters, or handing it whole frames in packet mode.
- Network Port and settings: the MS/TP Network Port object, Max_Master and Max_Info_Frames, the settings the code enforces and the timers derived from the baud rate.
- Example on GitHub: a complete MS/TP device to build and run.
- Practical notes: addresses, baud rates, frame sizes and the timing problems to look for.
- Moving from MSTP.c: for an application built on the removed MSTP.c engine.
Two ways to run MS/TP
Section titled “Two ways to run MS/TP”A port runs in one of two modes. The mode is chosen per port by which callbacks you register, and you can change it while the stack runs.
| Packet mode | Stack-owned byte mode | |
|---|---|---|
| Who does MS/TP framing, CRCs and token passing | Your code (your own engine: the stack provides none for this mode) | The stack |
| What you give the stack | NPDUs, through BACnetStack_RegisterCallbackReceiveMessageForPort() and BACnetStack_RegisterCallbackSendMessageForPort() |
Octets, through BACnetStack_RegisterCallbackMSTPReceiveBytes(), ...MSTPSendBytes() and ...MSTPMicroseconds() |
| What you call in the main loop | Your engine’s loop and BACnetStack_Tick() |
BACnetStack_LoopMSTP() and BACnetStack_Tick() |
| MS/TP subordinate proxying (DM-SP-B) | No: the send callback has no source address, so the port shows none of the properties and sends nothing for it | Yes: the Network Port shows Subordinate_Proxy_Enable, Manual_Subordinate_Address_Binding, Auto_Subordinate_Discovery and Subordinate_Address_Binding from the first BACnetStack_Tick() in byte mode, and the stack sends each I-Am with the subordinate’s MAC address as the frame’s source |
| Default | Yes: a port with none of the byte callbacks registered is in packet mode |
The stack’s own MS/TP engine runs only in byte mode. Register the byte callbacks and call BACnetStack_LoopMSTP(), or use packet mode with your own framing. An application built around MSTP.c and MSTP.h: see Moving from MSTP.c.
A port is in byte mode while the receive-bytes, send-bytes and microseconds callbacks are all registered. Registering NULL for any of the three ends byte mode at once on every port: the engine stops, its queues are emptied, its counters stay readable, and the port goes back to packet mode. The transmit-enable and frame-header callbacks are optional.
What the host must provide
Section titled “What the host must provide”Build option
Section titled “Build option”Define STACK_OPTION_DATA_LINK_LAYER_MSTP in source/CIBuildSettings.h, or pass it in CASBACNET_EXTRA_OPTIONS to
CMake; see configuration presets.
STACK_OPTION_TARGET_FULL turns it on through STACK_OPTION_ENABLE_ALL_DATA_LINK. The Network Port object needs STACK_OPTION_OBJECT_TYPE_NETWORK_PORT.
STACK_OPTION_MAX_DATA_LINK_INSTANCES_MSTP sets how many MS/TP Network Port objects the stack can
serve: 2 with STACK_OPTION_TARGET_FULL, otherwise 1. Every port has its own engine, so a process can drive more than one serial port.
Serial port
Section titled “Serial port”An RS-485 transceiver on a UART, 8 data bits, no parity, 1 stop bit. You open and configure it at the rate the Network Port’s Link_Speed property reports.
Stack callbacks
Section titled “Stack callbacks”| Registration | Why MS/TP needs it |
|---|---|
BACnetStack_RegisterCallbackMSTPReceiveBytes(), ...MSTPSendBytes(), ...MSTPMicroseconds() |
Byte mode (above). |
BACnetStack_RegisterCallbackGetPropertyOctetString() |
Serves the Network Port’s MAC_Address: exactly one octet, this node’s station address. |
BACnetStack_RegisterCallbackGetPropertyUnsignedInteger() |
Serves Max_Master and Max_Info_Frames. |
BACnetStack_RegisterCallbackGetPropertyReal() |
Serves Link_Speed, the baud rate. |
BACnetStack_RegisterCallbackSetPropertyUnsignedInteger() and BACnetStack_RegisterCallbackReinitializeDevice() |
Needed for a client to write Max_Master or Max_Info_Frames. Without them the write is aborted. |
BACnetStack_RegisterCallbackReceiveMessageForPort() and ...SendMessageForPort() |
Packet mode only. |
If your transceiver echoes what you transmit, you can return the echo from the receive callback. The engine recognises the echo of each frame it sent and discards it. Alternatively, disable the receiver while the driver-enable callback is on.
Replies to confirmed requests
Section titled “Replies to confirmed requests”When a frame that expects a reply arrives, the engine passes the NPDU to the stack and waits for the reply (the ANSWER_DATA_REQUEST state, cl. 9.5.6.9). If the stack queues the reply within half of Treply_delay (125 ms) of the request, the engine sends it at once, without waiting for the token. Otherwise it sends a Reply Postponed frame, and the reply goes out the next time this node holds the token. A request whose NPDU carries a destination network (it is to be routed on, cl. 9.7.1) is answered with a Reply Postponed at once, because its answer comes from another network. Test_Request frames are answered by the engine and never reach the stack.
Outgoing frames are sent as Data Expecting Reply when the NPDU’s data-expecting-reply bit is set, except to the broadcast address.
Example on GitHub
Section titled “Example on GitHub”Chipkin keeps its examples on GitHub up to date with the stack, so this manual does not copy their code. For MS/TP,
use BACnetProfileExample-B-BC-CPP, a BACnet Building Controller. Started with --datalink mstp and
--mstp-port naming your RS-485 adapter’s serial port, it runs the stack’s MS/TP engine in byte mode. Its MS/TP data
link is in mstp/MstpDataLink.cpp, and its README lists the other options,
such as --mstp-baud.
