Meadow in Industrial Applications: Modbus RTU

Industrial applications almost always have lots of sensors involved in them, and as a .NET developer, what better way is there to interact with sensors than using one of the Meadow platforms? Over the coming weeks and months I’ll cover a lot of the challenges with industrial applications and how Wilderness Labs is working to address those challenges.
Industrial applications almost always have lots of sensors involved in them, and as a .NET developer, what better way is there to interact with sensors than using one of the Meadow platforms? Over the coming weeks and months I’ll cover a lot of the challenges with industrial applications and how Wilderness Labs is working to address those challenges.

Today let’s talk about Modbus. Modbus is a communication protocol that dates all the way back to the 1970’s and is still used today. It’s well entrenched in industrial environments, and if you want to build industrial applications, you will likely want to interface with something using the Modbus protocol. There are plenty of resources that go into depth on the protocol if you’re interested in exactly how it works, but for this discussion, we’ll just talk about how we use it from Meadow.

The oldest implementation of Modbus (or the oldest you’re likely to encounter, anyway) is Modbus RTU, often referred to simply as Modbus Serial. It is nearly always running on an RS485 serial bus. RS485 is a differential-pair asynchronous serial bus and it works really well for long distances in noisy environments, and it supports multi-drop (multiple devices on one bus). What this means to us as application developers is that to talk to a Modbus RTU device, we’re going to need two things:

  • An RS485 harward interface
  • A Modbus RTU software library

RS485 and Meadow

Adding support for RS485 to your Meadow platform depends on which platform you’re on, but it’s straightforward in any case.

For Meadow.Linux platforms, the simplest way to get RS485 support is through a USB to RS485 adapter. I’ve had pretty good luck with a variety of them, though I tend to look for devices with an FTDI chipset instead of Prolific because it seems that a lot of the cheapest devices have counterfeit PL2303 chips in them and Windows won’t recognize them without workarounds.

For Meadow F7 platforms like the F7Feather, I use an RS485 breakout such as the transceiver from Sparkfun. One thing to note is that RS485 requires an Enable line. The USB adapters handle this internally for you, but on something like the F7 you will need to use a Digital Output Port. I’ll cover that when I discuss the software side of things later on in this post.

On the F7 I connect COM4 to the transceiver, the D02 to the transceiver RTS line, and 5V power and ground.

Connecting a Peripheral

For my solution I’m using a commercial thermostat from Temco Controls. The thermostat has its own, separate 24VAC power supply. The communication lines are connected from A on the transceiver to RS485+ on the stat and B on the transceiver to RS485- on the stat. I’m not in an electrically noisy environment, and the communication wire I’m using is shielded twisted pair, so I’m simply connecting the transceiver G (communications ground) to the ground rail, though it could probably be left unconnected.

I have configured the thermostat to used Modbus address 201, which you need to set in the application when you’re communicating with the target device (since RS485 is multi-drop).

Code

Now that we have a physical connection to the peripheral, we can write code to interact with it. We at Wilderness Labs have written an open source (Apache 2.0) library for Modbus that I will use for communication with the thermostat. The library supports Modbus RTU and Modbus TCP clients as well as hosting a Modbus TCP server. The latter two features I’ll cover in other articles.

First we need to import the Meadow.Modbus library into our project.

PM> Install-Package Meadow.Modbus

Next we add a using directive to our code file:

using Meadow.Modbus;

And create a ModbusRtuClient instance, passing in the SerialPort for COM4 and DigitalOutputPort used for the enable.

var port = Device.CreateSerialPort(Device.SerialPortNames.Com4, 19200, 8, Meadow.Hardware.Parity.None, Meadow.Hardware.StopBits.One);
port.WriteTimeout = port.ReadTimeout = TimeSpan.FromSeconds(5);
var enable = Device.CreateDigitalOutputPort(Device.Pins.D02, false);
var client = new ModbusRtuClient(port, enable);

The Temco TSTAT8 uses holding registers for all of its interfacing. It has a lot of registers for reading or controlling just about every aspect of its operation. For our purposes we’ll only look at two of them: current temperature and current occupied setpoint. Since Modbus holding registers are ushort values, but the actual temperature and setpoint are in tenths of a degree, we have to do scaling in our application. For example a current temperature register reading of 721 equates to a temeprature of 72.1 degrees. Similarly if we want to set a setpoint of 69.5 degrees, we write 695.

To read the current temperature and output it to the console every 5 seconds, we can use a loop like this:

byte address = 201;
ushort tempRegister = 121; // current temp, in tenths of a degree

while (true)
{
    registers = await client.ReadHoldingRegisters(address, tempRegister, 1);

    Console.WriteLine($"Current temp: {registers[0] / 10f}");

    await Task.Delay(TimeSpan.FromSeconds(5));
}

Writing to a holding register is similar to the read I showed above. Below is code that reads the setpoint, changes it with a write, then re-reads to verify the change:

byte address = 201;
ushort setPointRegister = 345; // occupied setpoint, in tenths of a degree

Console.WriteLine($"Reading setpoint holding register...");

var registers = await client.ReadHoldingRegisters(address, setPointRegister, 1);
Console.WriteLine($"Current set point: {registers[0] / 10f}");

var r = new Random();
var delta = r.Next(-20, 20);

var newSetpoint = (ushort)(registers[0] + delta);
Console.WriteLine($"Changing set point to: {newSetpoint / 10f}...");

await client.WriteHoldingRegister(address, setPointRegister, newSetpoint);
Console.WriteLine($"Re-reading thermostat holding registers...");

registers = await client.ReadHoldingRegisters(address, setPointRegister, 1);
Console.WriteLine($"Current set point: {registers[0] / 10f}");

Summary

Modbus is a workhorse protocol in many industrial applications, and the Meadow.Modbus library makes it very easy to write .NET applications that can interact with Modbus devices, even if your client application isn’t specifically a Meadow device.
In this article, I covered how you can read and write Modbus holding registers over Modbus RTU. The library also supports Modbus coils.
Next, we’ll look at using a network connection instead of RS485 and communicate with a PLC over Modbus TCP.