Modbus通讯协议在工控行业的应用是很多的,并且也是上位机开发的基本技能之一。相关的类库也很多也很好用。以前只负责用,对其并没有深入学习和了解。前段时间有点空就在这块挖了挖。想做到知其然还要知其所以然。所以就有了自己封装的Modbus工具类库的想法。一来是练练手,二来是自己封装的用的更顺手。
Modbus通讯协议我在工作中目前只用到了两种一个是串口通讯ModbusRTU,还有一个是网络通讯ModbusTcp。所以本文只有这两种通讯的实现。
C#是高级语言有很多好用的东西,如面像对像,设计模式等。但我在工作中还是经常看到面像过程的编程。如有多个串口设备就有多个代码类似的工具类。代码重复非常严重。我认为这种事还是要发点时间总结和代码一下代码,把它封装工具类库。以便后继在其他上的使用。
本次的封装用了一点面像对像的方法,设计了一个多个Modbus 基类将一些公共方法放在基类中,子类就可以继续使用。不同的子类有不同的功能。可以按需调用。使用简单方便。
- var _serialPort = new ModbusRTUCoil(portName, baudRate, parity, dataBits, stopBits);
- var isOk = false;
- var resultModel = _serialPort.ReadDataCoil(1, 1, ModbusFunctionCode.ReadInputCoil, (ushort)type.GetHashCode());
- if (resultModel.ResultList != null && resultModel.ResultList.Count > 0)
- {
- isOk = resultModel.ResultList.FirstOrDefault();
- }

- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool
- {
- ///
- /// Modbus结果实体
- ///
- ///
- public class ModbusResultModel
- {
- public ModbusResultModel()
- {
- IsSucceed = false;
- Msg = "失败(默认)";
- }
-
- private bool _isSucceed = false;
- ///
- /// 是否成功
- ///
- public bool IsSucceed
- {
- get
- {
- return _isSucceed;
- }
- set
- {
- _isSucceed = value;
- if (IsSucceed)
- {
- Msg = "成功";
- }
- }
- }
-
- ///
- /// 返回消息
- ///
- public string Msg { get; set; }
-
- ///
- /// 发送报文
- ///
- public string SendDataStr { get; set; }
-
- ///
- /// 原始数据
- ///
- public byte[] Datas { get; set; }
- }
-
- ///
- /// Modbus结果实体
- ///
- ///
- public class ModbusResultModel<DateType> : ModbusResultModel
- {
- public ModbusResultModel() : base()
- {
- ResultList = new List
(); - }
-
- ///
- /// 解析后的数据
- ///
- public List
ResultList { get; set; } - }
- }
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool
- {
- ///
- /// Modbus 基类
- ///
- public abstract class ModbusBase
- {
- ///
- /// 生成读取报文的 公共方法
- ///
- /// 从站地址
- /// 寄存器数量
- /// 功能码
- /// 起始寄存器地址
- ///
返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量) - protected byte[] GenerateReadCommandBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- //1.拼接报文:
- var sendCommand = new List<byte>();
-
- //协议格式:站地址+功能码+起始寄存器地址+寄存器数量
- //站地址
- sendCommand.Add(devAddr);
- //功能码
- sendCommand.Add((byte)functionCode.GetHashCode());
- //起始寄存器地址
- sendCommand.Add((byte)(startAddr / 256));
- sendCommand.Add((byte)(startAddr % 256));
- //寄存器数量
- sendCommand.Add((byte)(length / 256));
- sendCommand.Add((byte)(length % 256));
- //CRC
- //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- //sendCommand.AddRange(crc);
-
- return sendCommand.ToArray();
- }
-
- ///
- /// 生成读取报文的 公共方法
- ///
- /// 从站地址
- /// 定入数据
- /// 功能码
- /// 写入地址
- ///
返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量) - protected byte[] GenerateWriteCommandBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- //1.拼接报文:
- var sendCommand = new List<byte>();
-
- //协议格式:站地址+功能码+起始寄存器地址+寄存器数量
- //站地址
- sendCommand.Add(devAddr);
- //功能码
- sendCommand.Add((byte)functionCode.GetHashCode());
- //写入地址
- sendCommand.Add((byte)(startAddr / 256));
- sendCommand.Add((byte)(startAddr % 256));
- //写入数据
- var temp_bytes = BitConverter.GetBytes(data);
- if (BitConverter.IsLittleEndian)
- {
- //temp_bytes.Reverse();
- Array.Reverse(temp_bytes);
- }
- sendCommand.AddRange(temp_bytes);
- //CRC
- //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- //sendCommand.AddRange(crc);
-
- return sendCommand.ToArray();
- }
-
- ///
- /// 生成发送命令报文
- ///
- ///
- ///
- protected string generateSendCommandStr(byte[] sendCommand)
- {
- var sendCommandStr = string.Empty;
- foreach (var item in sendCommand)
- {
- sendCommandStr += Convert.ToString(item, 16) + " ";
- }
- return sendCommandStr;
- }
-
- ///
- /// 验证CRC
- ///
- /// 要验证的数据
- ///
- protected bool CheckCRC(byte[] value)
- {
- var isOk = false;
- if (value != null && value.Length >= 2)
- {
- int length = value.Length;
- byte[] buf = new byte[length - 2];
- Array.Copy(value, 0, buf, 0, buf.Length);
-
- //自己验证的结果
- byte[] CRCbuf = Crc16(buf, buf.Length);
- //把上面验证的结果和串口返回的校验码(最后两个)进行比较
- if (CRCbuf[0] == value[length - 2] && CRCbuf[1] == value[length - 1])
- {
- isOk = true;
- }
- }
- return isOk;
- }
-
- protected byte[] Crc16(byte[] pucFrame, int usLen)
- {
- int i = 0;
- byte[] res = new byte[2] { 0xFF, 0xFF };
- ushort iIndex;
- while (usLen-- > 0)
- {
- iIndex = (ushort)(res[0] ^ pucFrame[i++]);
- res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
- res[1] = aucCRCLo[iIndex];
- }
- return res;
- }
-
- protected readonly byte[] aucCRCHi = {
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
- 0x00, 0xC1, 0x81, 0x40
- };
-
- protected readonly byte[] aucCRCLo = {
- 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
- 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
- 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
- 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
- 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
- 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
- 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
- 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
- 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
- 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
- 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
- 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
- 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
- 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
- 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
- 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
- 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
- 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
- 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
- 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
- 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
- 0x41, 0x81, 0x80, 0x40
- };
-
- ///
- /// CRC校验
- ///
- /// 字节数组
- /// 验证长度
- ///
2个字节 - protected byte[] CalculateCRC(byte[] pucFrame, int usLen)
- {
- int i = 0;
- byte[] res = new byte[2] { 0xFF, 0xFF };
- ushort iIndex;
- while (usLen-- > 0)
- {
- iIndex = (ushort)(res[0] ^ pucFrame[i++]);
- res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
- res[1] = aucCRCLo[iIndex];
- }
- return res;
- }
- }
-
- ///
- /// Modbus 功能码
- ///
- public enum ModbusFunctionCode
- {
- ///
- /// 读取输出线圈
- ///
- [Description("读取输出线圈")]
- ReadOutCoil = 1,
-
- ///
- /// 读取输入线圈
- ///
- [Description("读取输入线圈")]
- ReadInputCoil = 2,
-
- ///
- /// 读取保持寄存器
- ///
- [Description("读取保持寄存器")]
- ReadRegister = 3,
-
- ///
- /// 读取输入寄存器
- ///
- [Description("读取输入寄存器")]
- ReadInputRegister = 4,
-
- ///
- /// (写入)预置单线圈
- ///
- [Description("(写入)预置单线圈")]
- WriteCoil = 5,
-
- ///
- /// (写入)预置单个寄存器
- ///
- [Description("(写入)预置单个寄存器")]
- WriteRegister = 6,
-
- ///
- /// (写入)预置多寄存器
- ///
- [Description("(写入)预置多寄存器")]
- WriteRegisterMultiple = 16,
- }
- }
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.IO.Ports;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool.RTU
- {
- //Modbus 规定4个存储区
- // 区号 名称 读写 范围
- // 0区 输出线圈 可读可写 00001-09999
- // 1区 输入线圈 只读 10001-19999
- // 2区 输入寄存器 只读 30001-39999
- // 4区 保存寄存器 可读可写 40001-19999
-
- //功能码
- //01H 读取输出线圈
- //02H 读取输入线圈
- //03H 读取保持寄存器
- //04H 读取输入寄存器
- //05H (写入)预置单线圈
- //06H (写入)预置寄存器
- //0FH (写入)预置多线圈
- //10H (写入)预置多寄存器
-
- ///
- /// 串口基类
- ///
- public abstract class SerialPortBase : ModbusBase
- {
- protected SerialPort SerialPortObj;
-
- ///
- /// 初始化
- ///
- /// COM口名称
- /// 波特率
- /// 检验位
- /// 数据位
- /// 停止位
- protected void Init(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
- {
- SerialPortObj = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
- if (SerialPortObj.IsOpen)
- {
- SerialPortObj.Close();
- }
- SerialPortObj.Open();
- }
-
- ///
- /// 关闭
- ///
- public void Close()
- {
- if (SerialPortObj.IsOpen)
- {
- SerialPortObj.Close();
- SerialPortObj.Dispose();
- SerialPortObj = null;
- }
- }
- }
-
- //功能码
- //01H 读取输出线圈
- //02H 读取输入线圈
- //03H 读取保持寄存器
- //04H 读取输入寄存器
- //05H (写入)预置单线圈
- //06H (写入)预置寄存器
- //0FH (写入)预置多线圈
- //10H (写入)预置多寄存器
-
-
- }
(串口操作的所有功能这个类都能做)
- using System;
- using System.Collections.Generic;
- using System.ComponentModel.Design;
- using System.IO.Ports;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool.RTU
- {
- ///
- /// Modbus 串口通讯
- ///
- public class ModbusRTU : SerialPortBase
- {
- private string _className = "ModbusRTU";
-
- ///
- /// Modbus 串口通讯
- ///
- /// COM口名称
- /// 波特率
- /// 检验位
- /// 数据位
- /// 停止位
- public ModbusRTU(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
- {
- Init(portName, baudRate, parity, dataBits, stopBits);
- //SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);
- }
-
- ///
- /// 读取线圈数据 ok
- ///
- /// 从站地址
- /// 寄存器数量
- /// 功能码
- /// 起始寄存器地址
- ///
线圈数据 - public ModbusResultModel ReadData(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- return ReadData(devAddr, length, (byte)functionCode, startAddr);
- }
-
- ///
- /// 读取数据 ok
- ///
- /// 从站地址
- /// 寄存器数量
- /// 功能码
- /// 起始寄存器地址
- ///
线圈数据 - public ModbusResultModel ReadData(byte devAddr, ushort length, byte functionCode = 2, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- //byte[] datas = null;
- if (functionCode >= 1 && functionCode <= 4)
- {
- try
- {
- //1.拼接报文:
- var sendCommand = new List<byte>();
-
- //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
- //站地址
- sendCommand.Add(devAddr);
- //功能码
- sendCommand.Add(functionCode);
- //起始寄存器地址
- sendCommand.Add((byte)(startAddr / 256));
- sendCommand.Add((byte)(startAddr % 256));
- //寄存器数量
- sendCommand.Add((byte)(length / 256));
- sendCommand.Add((byte)(length % 256));
- //CRC
- byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- sendCommand.AddRange(crc);
-
- resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
- //2.发送报文
- SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
- //3.接收报文
- Thread.Sleep(50);//要延时一下,才能读到数据
-
- //读取响应报文
- byte[] respBytes = new byte[SerialPortObj.BytesToRead];
- SerialPortObj.Read(respBytes, 0, respBytes.Length);
- // respBytes -> 01 01 02 00 00 B9 FC
- resultModel.Datas = respBytes;
- // 检查一个校验位
- //if (CheckCRC(respBytes) && (respBytes.Length == 5 + length * 2)
- // && respBytes[0] == devAdd && respBytes[1] == functionCode && respBytes[1] == length * 2)
- if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode)
- {
- //datas = respBytes;
- resultModel.IsSucceed = true;
- }
- else
- {
- resultModel.Msg = "响应报文校验失败";
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = "异常:" + ex.Message;
- }
- }
- else
- {
- //throw new Exception("功能码不正确[1-4]");
- resultModel.Msg = "功能码不正确[1-4]";
- }
- //SerialPortObj.Close();
- return resultModel;
- }
-
- ///
- /// 写入单个寄存器 ok
- /// 数据示例: 200
- /// 功能码 6
- ///
- /// 从站地址
- /// 写入的数据
- /// 写入地址
- ///
是否成功 - public ModbusResultModel WriteDataShort(int devAddr, short value, short startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- try
- {
- //bool isOk = false;
- //1.拼接报文:
- //var sendCommand = GetSingleDataWriteMessage(devAdd, startAddr, value); //ok
- var sendCommand = GetSingleDataWriteMessageList(devAddr, startAddr, value); //ok
- //var sendCommandStr = string.Join(' ', sendCommand.ToArray());
- resultModel.SendDataStr = generateSendCommandStr(sendCommand);
-
- //2.发送报文
- SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Length);
- //3.接收报文
- Thread.Sleep(50);//要延时一下,才能读到数据
-
- //读取响应报文
- byte[] respBytes = new byte[SerialPortObj.BytesToRead];
- SerialPortObj.Read(respBytes, 0, respBytes.Length);
- // respBytes -> 01 01 02 00 00 B9 FC
- // 检查一个校验位
- if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == 0x06)
- {
- //isOk = true;
- resultModel.IsSucceed = true;
- }
- else
- {
- resultModel.Msg = "响应报文校验失败";
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = "异常:" + ex.Message;
- }
- //SerialPortObj.Close();
- return resultModel;
- }
-
- ///
- /// 写入单个寄存器 ok
- /// 数据示例: 200
- /// 功能码 6
- ///
- /// 站地址
- /// 写入的数据集合
- /// 写入地址
- ///
是否成功 - public ModbusResultModel WriteDataShort(int devAddr, List<short> dataList, short startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- if (dataList != null && dataList.Count > 0)
- {
- foreach (var item in dataList)
- {
- resultModel = WriteDataShort(devAddr, item, startAddr);
- startAddr++;
- }
- }
- return resultModel;
- }
-
- ///
- /// 获取写入单个寄存器的报文
- ///
- /// 从站地址
- /// 寄存器地址
- /// 写入值
- ///
写入单个寄存器的报文 - private byte[] GetSingleDataWriteMessage(int slaveStation, short startAddr, short value)
- {
- //从站地址
- byte station = (byte)slaveStation;
- //功能码
- byte type = 0x06;//06H (写入)预置寄存器
- //寄存器地址
- byte[] start = BitConverter.GetBytes(startAddr);
- //值
- byte[] valueBytes = BitConverter.GetBytes(value);
- //根据计算机大小端存储方式进行高低字节转换
- if (BitConverter.IsLittleEndian)
- {
- Array.Reverse(start);
- Array.Reverse(valueBytes);
- }
- //拼接报文
- byte[] result = new byte[] { station, type };
- result = result.Concat(start.Concat(valueBytes).ToArray()).ToArray();
-
- //计算校验码并拼接,返回最后的报文结果
- return result.Concat(Crc16(result, result.Length)).ToArray();
- }
-
- ///
- /// 获取写入单个寄存器的报文
- ///
- /// 从站地址
- /// 寄存器地址
- /// 写入值
- ///
写入单个寄存器的报文 - private byte[] GetSingleDataWriteMessageList(int slaveStation, short startAddr, short data)
- {
- //1.拼接报文:
- var sendCommand = new List<byte>();
- //从站地址
- byte station = (byte)slaveStation;
- //功能码
- byte type = 0x06;//06H (写入)预置寄存器
- //寄存器地址
- byte[] start = BitConverter.GetBytes(startAddr);
- //值
- byte[] valueBytes = BitConverter.GetBytes(data);
- //根据计算机大小端存储方式进行高低字节转换
- if (BitConverter.IsLittleEndian)
- {
- Array.Reverse(start);
- Array.Reverse(valueBytes);
- }
- sendCommand.Add((byte)slaveStation);
- sendCommand.Add(type);
- sendCommand.AddRange(start);
- sendCommand.AddRange(valueBytes);
-
- byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- sendCommand.AddRange(crc);
- return sendCommand.ToArray();
- }
-
- ///
- /// 写入多个寄存器 ok
- /// 数据示例: 123.45f, 14.3f
- /// 功能码 10
- ///
- /// 从站地址
- /// 写入的数据
- /// 写入地址
- ///
是否成功 - public ModbusResultModel WriteDataFloat(byte devAddr, float data, ushort startAddr = 0)
- {
- return WriteDataFloat(devAddr, new List<float>() { data }, startAddr);
- }
-
- ///
- /// 写入多个寄存器 ok
- /// 数据示例: 123.45f, 14.3f
- /// 功能码 10
- ///
- /// 从站地址
- /// 写入的数据
- /// 写入地址
- ///
是否成功 - public ModbusResultModel WriteDataFloat(byte devAddr, List<float> dataList, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- if (dataList != null && dataList.Count > 0)
- {
- try
- {
- byte functionCode = (byte)ModbusFunctionCode.WriteRegisterMultiple.GetHashCode();
- int length = dataList.Count * 2;
- //1.拼接报文:
- var sendCommand = new List<byte>();
-
- //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
- //站地址
- sendCommand.Add(devAddr);
- //功能码
- sendCommand.Add(functionCode);
- //写入地址
- sendCommand.Add((byte)(startAddr / 256));
- sendCommand.Add((byte)(startAddr % 256));
- //寄存器数量
- sendCommand.Add((byte)(length / 256));
- sendCommand.Add((byte)(length % 256));
- // 获取数值的byte[]
- List<byte> valueBytes = new List<byte>();
- foreach (var data in dataList)
- {
- List<byte> temp = new List<byte>(BitConverter.GetBytes(data));
- temp.Reverse();// 调整字节序
- valueBytes.AddRange(temp);
- }
- // 字节数
- sendCommand.Add((byte)valueBytes.Count);
- sendCommand.AddRange(valueBytes);
- //CRC
- byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- sendCommand.AddRange(crc);
-
- //000004 - Rx:01 10 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 83 23
- //000005 - Tx:01 10 00 02 00 04 60 0A
- //000006 - Rx:01 0A 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 98 F9
- //000007 - Tx:01 8A 01 86 A0 //报错了
-
- //2.发送报文
- SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
- //3.接收报文
- Thread.Sleep(50);//要延时一下,才能读到数据
-
- //读取响应报文
- byte[] respBytes = new byte[SerialPortObj.BytesToRead];
- SerialPortObj.Read(respBytes, 0, respBytes.Length);
- // respBytes -> 01 01 02 00 00 B9 FC
-
- // 检查一个校验位
- if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode)
- {
- resultModel.IsSucceed = true;
- }
- else
- {
- resultModel.Msg = "响应报文校验失败";
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = "异常:" + ex.Message;
- }
- //SerialPortObj.Close();
- }
- else
- {
- resultModel.Msg = "dataLis参数不能为NULL 且 Count 要大于0";
- }
- return resultModel;
- }
-
- ///
- /// 写单个线圈输出 ok
- ///
- /// 开关
- /// 从站地址
- /// 写入地址
- ///
- public ModbusResultModel WriteSingleOutOnOff(bool on, byte devAddr = 1, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- try
- {
- //var isOk = false;
- //1.拼接报文:
- var sendCommand = new List<byte>();
- //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
- //站地址
- sendCommand.Add(devAddr);
- //功能码
- byte functionCode = 0x05;
- sendCommand.Add(functionCode);
- //写入地址
- sendCommand.Add((byte)(startAddr / 256));
- sendCommand.Add((byte)(startAddr % 256));
- //写入数据
- sendCommand.Add((byte)(on ? 0xFF : 0x00));//true : 0xFF 开,false : 0x00 关
- sendCommand.Add(0x00);
- //CRC
- byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- sendCommand.AddRange(crc);
- //2.发送报文
- SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
- //isOk = true;
- resultModel.IsSucceed = true;
- }
- catch (Exception ex)
- {
- resultModel.Msg = "异常:" + ex.Message;
- }
- return resultModel;
- }
- }
- }
(这个类是针对线圈的 突出读取数据)
- using System;
- using System.Collections.Generic;
- using System.IO.Ports;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool.RTU
- {
- ///
- /// Modbus 串口通讯 读线圈状态
- ///
- public class ModbusRTUCoil : ModbusRTU
- {
- //ModbusRTU rtu = new ModbusRTU(portName);
- //var resultModel = rtu.ReadData(1, readLen, ModbusFunctionCode.ReadOutCoil);
-
- ///
- /// Modbus 串口通讯
- ///
- /// COM口名称
- /// 波特率
- /// 检验位
- /// 数据位
- /// 停止位
- public ModbusRTUCoil(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
- : base(portName, baudRate, parity, dataBits, stopBits)
- {
- //Init(portName, baudRate, parity, dataBits, stopBits);
- //SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);
- }
-
- ///
- /// 读取线圈数据 ok
- ///
- /// 从站地址
- /// 寄存器数量
- /// 功能码
- /// 起始寄存器地址
- ///
线圈数据 - public ModbusResultModel<bool> ReadDataCoil(byte devAdd, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel<bool>();
- var model = ReadData(devAdd, length, (byte)functionCode, startAddr);
- if (model != null && model.Datas != null && model.Datas.Length > 5)
- {
- resultModel.IsSucceed = model.IsSucceed;
- //报文解析
- // 检查一个校验位
- List<byte> respList = new List<byte>(model.Datas);
- respList.RemoveRange(0, 3);
- respList.RemoveRange(respList.Count - 2, 2);
- // 00 00
- //集合反转
- respList.Reverse();
- //转换成2进制
- var respStrList = respList.Select(r => Convert.ToString(r, 2)).ToList();
- var values = string.Join("", respStrList).ToList();
- values.Reverse();
- //values.ForEach(c => Console.WriteLine(Convert.ToBoolean(int.Parse(c.ToString()))));
- foreach (var v in values)
- {
- resultModel.ResultList.Add(v.ToString() == "1");
- }
- }
- return resultModel;
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Sockets;
- using System.Runtime.InteropServices;
- using System.Security.Cryptography;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace CJH.ModbusTool.TCP
- {
- ///
- /// ModbusTCP 基类
- ///
- public abstract class ModbusTCPBase : ModbusBase
- {
- private Socket _socket = null;
- ushort _tid = 0;//TransactionId 最大 65535
-
- ///
- /// 异常码 字典
- ///
- protected Dictionary<int, string> Errors = new Dictionary<int, string>() {
- { 0x01 , "非法功能码"},
- { 0x02 , "非法数据地址"},
- { 0x03 , "非法数据值"},
- { 0x04 , "从站设备故障"},
- { 0x05 , "确认,从站需要一个耗时操作"},
- { 0x06 , "从站忙"},
- { 0x08 , "存储奇偶性差错"},
- { 0x0A , "不可用网关路径"},
- { 0x0B , "网关目标设备响应失败"},
- };
-
- ///
- /// Modbus TCP 通讯 初始化
- ///
- /// 主机地址
- /// 端口
- protected void Init(string host, int port)
- {
- _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- _socket.Connect(host, port);
- }
-
- ///
- /// 读取报文的 公共方法
- ///
- /// 从站地址
- /// 寄存器数量
- /// 功能码
- /// 起始寄存器地址
- ///
返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量) - protected byte[] GenerateTcpCommandReadBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- var baseCommand = GenerateReadCommandBytes(devAddr, length, functionCode, startAddr);
-
- var sendCommand = new List<byte>();
- //TransactionId
- sendCommand.Add((byte)(_tid / 256));
- sendCommand.Add((byte)(_tid % 256));
- //Modbus 协议标识
- sendCommand.Add(0x00);
- sendCommand.Add(0x00);
- //后续字节数
- sendCommand.Add((byte)(baseCommand.Length / 256));
- sendCommand.Add((byte)(baseCommand.Length % 256));
- _tid++;
- _tid %= 65535;
- sendCommand.AddRange(baseCommand);
- return sendCommand.ToArray();
- }
-
- ///
- /// 读取报文的 公共方法
- ///
- /// 从站地址
- /// 输入数据
- /// 功能码
- /// 起始寄存器地址
- ///
返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量) - protected byte[] GenerateTcpCommandWriteBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0)
- {
- var baseCommand = GenerateWriteCommandBytes(devAddr, data, functionCode, startAddr);
-
- var sendCommand = new List<byte>();
- //TransactionId
- sendCommand.Add((byte)(_tid / 256));
- sendCommand.Add((byte)(_tid % 256));
- //Modbus 协议标识
- sendCommand.Add(0x00);
- sendCommand.Add(0x00);
- //后续字节数
- sendCommand.Add((byte)(baseCommand.Length / 256));
- sendCommand.Add((byte)(baseCommand.Length % 256));
- _tid++;
- _tid %= 65535;
- sendCommand.AddRange(baseCommand);
- return sendCommand.ToArray();
- }
-
- protected ModbusResultModel SendCommand(byte[] sendCommand)
- {
- var resultModel = new ModbusResultModel();
- try
- {
- //报文
- //TransactionId Modbus 协议标识 后续字节数 从站地址 功能码 起始寄存器地址 寄存器数量 CRC
- //0x00 0x01 0x00 0x00 0x00 0x06 devAddr functionCode 0x00 0x0A
-
- resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
- _socket.Send(sendCommand.ToArray());
- //000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05
- //000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00
- // 00 00 00 00 00 0D 前6位
- // 01 03 0A (0,1,2)
- // 0A 数据长度 (0A=10)
-
- //先取前6位,固定返回
- var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位
- _socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);
-
- //取出下标为 :4和5的数据[00 0D]
- var len_bytes = resp_bytes.ToList().GetRange(4, 2);
- //起始寄存器地址 的反向操作
- //将下标为4和5 两个字节转成10进制数
- int len = len_bytes[0] * 256 + len_bytes[1];
-
- //获取数据的长度
- //01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]
- //01 83 02 [异常,83, 异常代码 :02]
- resp_bytes = new byte[len];
- _socket.Receive(resp_bytes, 0, len, SocketFlags.None);
-
- //检查响应报文是否正常
- //0x83 1000 0011
- //01 83 02 [异常,83, 异常代码 :02]
- if (resp_bytes[1] > 0x08)//判断是否异常
- {
- //resp_bytes[2] = 异常代码 :02
- //说明响应是异常报文
- //返回异常信息,根据resp_bytes字节进行异常关联
- if (Errors.ContainsKey(resp_bytes[2]))
- {
- resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明
- }
- }
- else
- {
- //resp_bytes[2] = 0A 数据长度 (0A=10)
- //正常
- resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();
- resultModel.IsSucceed = true;
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = ex.Message;
- }
- return resultModel;
- }
-
- ///
- /// 解析数据
- ///
- ///
- ///
- ///
- public List<T> AnalysisDatas<T>(byte[] datas)
- {
- //data_bytes 每两个字节转成一个数字, float 4个字节转成一个数字,double 8个字节转成一个数字
- //2 ushort short int16 uint32 float
- //4 int uint int32 uint32 float
- //8 double
- //16 decimal
- var resultValue = new List
(); - try
- {
- var type_len = Marshal.SizeOf(typeof(T));
- for (int i = 0; i < datas.Length; i += type_len)
- {
- var temp_bytes = datas.ToList().GetRange(i, type_len);
- if (BitConverter.IsLittleEndian)
- {
- temp_bytes.Reverse();
- }
- //反射 方法
- Type bitConverter_type = typeof(BitConverter);
- var typeMethodList = bitConverter_type.GetMethods().ToList();
- //找到返回类型和传入的类型一至,且方法的参数是2个的方法
- var method = typeMethodList.FirstOrDefault(mi => mi.ReturnType == typeof(T)
- && mi.GetParameters().Length == 2);
- if (method == null)
- {
- throw new Exception("数据转换类型出错!");
- }
- else
- {
- //由 bitConverter_type 执行找到的 method方法,注意参数数量,上面找是的两个参数的方法
- var value = method.Invoke(bitConverter_type, new object[] { temp_bytes.ToArray(), 0 });
- resultValue.Add((T)value);
- }
- }
- }
- catch (Exception ex)
- {
-
- }
- return resultValue;
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Net.Sockets;
-
- namespace CJH.ModbusTool.TCP
- {
- ///
- /// Modbus TCP 通讯
- ///
- public class ModbusTCP : ModbusTCPBase
- {
-
- ///
- /// Modbus TCP 通讯
- ///
- /// 主机地址
- /// 端口
- public ModbusTCP(string host, int port)
- {
- //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- //_socket.Connect(host, port);
- Init(host, port);
- }
-
- ///
- /// 读取保持型寄存器 03
- ///
- /// 从站地址
- /// 数量
- /// 功能码
- /// 起始地址
- //public ModbusResultModel ReadHoldingRegister(byte devAddr, ushort length, byte functionCode = 3, ushort startAddr = 0)
- //{
- // var resultModel = new ModbusResultModel();
- // //报文
- // //TransactionId Modbus 协议标识 后续字节数 从站地址 功能码 起始寄存器地址 寄存器数量 CRC
- // //0x00 0x01 0x00 0x00 0x00 0x06 devAddr functionCode 0x00 0x0A
- // try
- // {
- // ushort tid = 0;//TransactionId 最大 65535
-
- // var sendCommand = new List
(); - // //TransactionId
- // sendCommand.Add((byte)(tid / 256));
- // sendCommand.Add((byte)(tid % 256));
- // //Modbus 协议标识
- // sendCommand.Add(0x00);
- // sendCommand.Add(0x00);
- // //后续字节数
- // sendCommand.Add(0x00);
- // sendCommand.Add(0x06);
- // //从站地址
- // sendCommand.Add(devAddr);
- // //功能码
- // sendCommand.Add(functionCode);
- // //起始寄存器地址
- // sendCommand.Add((byte)(startAddr / 256));
- // sendCommand.Add((byte)(startAddr % 256));
- // //寄存器数量
- // sendCommand.Add((byte)(length / 256));
- // sendCommand.Add((byte)(length % 256));
- // //CRC
- // //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
- // //sendCommand.AddRange(crc);
- // tid++;
- // tid %= 65535;
- // resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
- // _socket.Send(sendCommand.ToArray());
- // //000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05
- // //000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00
- // // 00 00 00 00 00 0D 前6位
- // // 01 03 0A (0,1,2)
- // // 0A 数据长度 (0A=10)
-
- // //先取前6位,固定返回
- // var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位
- // _socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);
-
- // //取出下标为 :4和5的数据[00 0D]
- // var len_bytes = resp_bytes.ToList().GetRange(4, 2);
- // //起始寄存器地址 的反向操作
- // //将下标为4和5 两个字节转成10进制数
- // int len = resp_bytes[4] * 256 + resp_bytes[5];
-
- // //获取数据的长度
- // //01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]
- // //01 83 02 [异常,83, 异常代码 :02]
- // resp_bytes = new byte[len];
- // _socket.Receive(resp_bytes, 0, len, SocketFlags.None);
-
- // //检查响应报文是否正常
- // //0x83 1000 0011
- // //01 83 02 [异常,83, 异常代码 :02]
- // if (resp_bytes[1] > 0x08)//判断是否异常
- // {
- // //resp_bytes[2] = 异常代码 :02
- // //说明响应是异常报文
- // //返回异常信息,根据resp_bytes字节进行异常关联
- // if (Errors.ContainsKey(resp_bytes[2]))
- // {
- // resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明
- // }
- // }
- // else
- // {
- // //resp_bytes[2] = 0A 数据长度 (0A=10)
- // //正常
- // resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();
- // resultModel.IsSucceed = true;
- // }
- // }
- // catch (Exception ex)
- // {
- // resultModel.Msg = ex.Message;
- // }
- // return resultModel;
- //}
-
- ///
- /// 读取保持型寄存器 03
- ///
- /// 从站地址
- /// 数量
- /// 功能码
- /// 起始地址
- ///
返回对象 - public ModbusResultModel<T> ReadHoldingRegister<T>(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel
(); - try
- {
- var command = GenerateTcpCommandReadBytes(devAddr, length, functionCode, startAddr);
-
- resultModel.SendDataStr = generateSendCommandStr(command.ToArray());
- var receptionModel = SendCommand(command.ToArray());
- if (receptionModel.IsSucceed
- && receptionModel.Datas != null && receptionModel.Datas.Length > 0)
- {
- resultModel.Datas = receptionModel.Datas;
- resultModel.ResultList = AnalysisDatas
(receptionModel.Datas); - resultModel.IsSucceed = true;
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = ex.Message;
- }
- return resultModel;
- }
-
- ///
- /// 写入保持型寄存器 03
- ///
- /// 从站地址
- /// 写入数据
- /// 功能码
- /// 起始地址
- ///
返回对象 - public ModbusResultModel WriteHoldingRegister(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0)
- {
- var resultModel = new ModbusResultModel();
- try
- {
- var command = GenerateTcpCommandWriteBytes(devAddr, data, functionCode, startAddr);
-
- resultModel.SendDataStr = generateSendCommandStr(command.ToArray());
- var receptionModel = SendCommand(command.ToArray());
- if (receptionModel.IsSucceed)
- {
- resultModel.IsSucceed = true;
- }
- }
- catch (Exception ex)
- {
- resultModel.Msg = ex.Message;
- }
- return resultModel;
- }
- }
- }
这就是全部的代码。如有使用的问题可以给我发评论。