from:
http://www.codeproject.com/KB/trace/modbuscs.aspx
IntroductionOften in my day I find myself testing and debugging devices that communicate via the modbus protocol. In an effort to save myself time, I have tried several downloadable applications but always came to the same conclusion - "Why pay money for something when it's not that hard to build myself?" Also, given that modbus is a 30 year old standard, it makes it difficult to find current code that is usable in today's popular languages. What follows is a brief description of the madness behind the modbus and a small application that I find myself using daily when testing RS232 modbus devices. The code is hardly robust, but it gets the job done in the sense that it works and it is easy to use and alter - two features I find the most important in a lab setting. I hope you find this of some use!
BackgroundModbus is a serial communications protocol used extensively in the industrial world going all the way back to the 1970s. It is especially useful in situations where one needs to connect to several devices in a network (or slaves) through its implementation of multiple device addresses. But if you found this article, then chances are you already know what modbus is and would rather not be bored to death with the details. For those who want to get their hands dirty with the finer points,
here is the place to go for the relevant details.
For the purposes of this article, I'll do a quick rundown of the modbus implementation used and the functions covered.
Modbus VariantsThe modbus protocol comes in two flavors:
RTU and
ASCII. RTU is a binary implementation and is often most desirable. As such, this discussion will pertain solely to the RTU standard.
Modbus FunctionsThere are numerous functions available in the modbus protocol, yet I haven't found any use for most besides the basic read and write commands. Therefore we will only be looking at implementations of
Function 3 - Read Multiple Registers and
Function 16 - Write Multiple Registers commands. Other commands can be learned about by following the link provided in the 'Background' portion of this article.
Function 3 - Read Multiple Registers Message Framing
Request
- Address (one byte denoting the slave ID)
- Function Code (one byte denoting the function ID, in this case '3')
- Starting Address (two bytes representing the modbus register at which to begin reading)
- Register Quantity (two bytes denoting the amount of registers to read)
- CRC (two bytes containing the cyclical redundancy check checksum for the outgoing message)
Response
- Address (one byte containing the ID of the slave responding)
- Function Code (one byte denoting the function to which the slave is responding, in this case '3')
- Byte Count (one byte representing the quantity of bytes being read. Each modbus register is made up of 2 bytes, so this value would be 2 * N, with N being the quantity of registers being read)
- Register Values (2 * N bytes representing the values being read)
- CRC (two bytes containing the CRC checksum for the incoming message)
Function 16 - Write Multiple Registers Message Framing Request
- Address (one byte denoting the slave ID)
- Function Code (one byte denoting the function ID, in this case '16')
- Starting Address (two bytes representing the modbus register at which to begin writing)
- Register Quantity (two bytes denoting the amount of registers to write)
- Byte Count (one byte representing the quantity of bytes being written. Each modbus register is made up of 2 bytes, so this value would be 2 * N, with N being the quantity of registers being written to)
- Register Values (2 * N bytes containing the actual bytes being written)
- CRC (two bytes containing the cyclical redundancy check checksum for the outgoing message)
Response
- Address (one byte containing the ID of the slave responding)
- Function Code (one byte representing the function being responded to, in this case '16')
- Starting Address (two bytes stating the starting register address that was first written to)
- Register Quantity (two bytes denoting the quantity of modbus registers that were written to)
- CRC (two bytes representing the CRC checksum of the incoming message)
Error and Exception CodingThe modbus protocol describes numerous exception codes available for each function in its implementation guide. All that is handled in this code is a simple CRC evaluation, as this is hardly a full implementation of the entire modbus protocol. If one chooses to add more error checking, additional procedures can easily be added to the provided code.
Using the CodeThe class
modbus.cs contains several functions for serial port handling and data transfer. All functions make use of the SerialPort class found in the
System.IO.Ports namespace of .NET 2.0.
One peculiarity of the SerialPort class exists in its DataReceived event. As has been documented in other articles on this site, the DataReceived event, which should fire each time the serial port receives an incoming event, doesn't "actually do this"
. This prevents truly event driving communications from working properly without the additional fixes and workarounds.
Fortunately, using something like the modbus protocol gives us the benefit of known incoming and outgoing message lengths. This allows us to use the ReadByte function as the backbone of any read commands and we can bypass the problem completely. In a perfect world, this would be an event driven class - but in the meantime we'll reject the tradeoff in lost data and tell the port when to read on our own.