使用C#實(shí)現(xiàn)自己封裝的Modbus工具類庫
前言
Modbus通訊協(xié)議在工控行業(yè)的應(yīng)用是很多的,并且也是上位機(jī)開發(fā)的基本技能之一。相關(guān)的類庫也很多也很好用。以前只負(fù)責(zé)用,對其并沒有深入學(xué)習(xí)和了解。前段時(shí)間有點(diǎn)空就在這塊挖了挖。想做到知其然還要知其所以然。所以就有了自己封裝的Modbus工具類庫的想法。一來是練練手,二來是自己封裝的用的更順手。
Modbus通訊協(xié)議我在工作中目前只用到了兩種一個(gè)是串口通訊ModbusRTU,還有一個(gè)是網(wǎng)絡(luò)通訊ModbusTcp。所以本文只有這兩種通訊的實(shí)現(xiàn)。
設(shè)計(jì)思想
C#是高級語言有很多好用的東西,如面像對像,設(shè)計(jì)模式等。但我在工作中還是經(jīng)??吹矫嫦襁^程的編程。如有多個(gè)串口設(shè)備就有多個(gè)代碼類似的工具類。代碼重復(fù)非常嚴(yán)重。我認(rèn)為這種事還是要發(fā)點(diǎn)時(shí)間總結(jié)和代碼一下代碼,把它封裝工具類庫。以便后繼在其他上的使用。
本次的封裝用了一點(diǎn)面像對像的方法,設(shè)計(jì)了一個(gè)多個(gè)Modbus 基類將一些公共方法放在基類中,子類就可以繼續(xù)使用。不同的子類有不同的功能??梢园葱枵{(diào)用。使用簡單方便。
調(diào)用示例
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(); }
類庫項(xiàng)目結(jié)構(gòu)
代碼
Modbus結(jié)果實(shí)體
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CJH.ModbusTool { /// <summary> /// Modbus結(jié)果實(shí)體 /// </summary> /// <typeparam name="DateType"></typeparam> public class ModbusResultModel { public ModbusResultModel() { IsSucceed = false; Msg = "失敗(默認(rèn))"; } private bool _isSucceed = false; /// <summary> /// 是否成功 /// </summary> public bool IsSucceed { get { return _isSucceed; } set { _isSucceed = value; if (IsSucceed) { Msg = "成功"; } } } /// <summary> /// 返回消息 /// </summary> public string Msg { get; set; } /// <summary> /// 發(fā)送報(bào)文 /// </summary> public string SendDataStr { get; set; } /// <summary> /// 原始數(shù)據(jù) /// </summary> public byte[] Datas { get; set; } } /// <summary> /// Modbus結(jié)果實(shí)體 /// </summary> /// <typeparam name="DateType"></typeparam> public class ModbusResultModel<DateType> : ModbusResultModel { public ModbusResultModel() : base() { ResultList = new List<DateType>(); } /// <summary> /// 解析后的數(shù)據(jù) /// </summary> public List<DateType> ResultList { get; set; } } }
Modbus 基類
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 { /// <summary> /// Modbus 基類 /// </summary> public abstract class ModbusBase { /// <summary> /// 生成讀取報(bào)文的 公共方法 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="length">寄存器數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>返回報(bào)文(協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量)</returns> protected byte[] GenerateReadCommandBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0) { //1.拼接報(bào)文: var sendCommand = new List<byte>(); //協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量 //站地址 sendCommand.Add(devAddr); //功能碼 sendCommand.Add((byte)functionCode.GetHashCode()); //起始寄存器地址 sendCommand.Add((byte)(startAddr / 256)); sendCommand.Add((byte)(startAddr % 256)); //寄存器數(shù)量 sendCommand.Add((byte)(length / 256)); sendCommand.Add((byte)(length % 256)); //CRC //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count); //sendCommand.AddRange(crc); return sendCommand.ToArray(); } /// <summary> /// 生成讀取報(bào)文的 公共方法 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="data">定入數(shù)據(jù)</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">寫入地址</param> /// <returns>返回報(bào)文(協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量)</returns> protected byte[] GenerateWriteCommandBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0) { //1.拼接報(bào)文: var sendCommand = new List<byte>(); //協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量 //站地址 sendCommand.Add(devAddr); //功能碼 sendCommand.Add((byte)functionCode.GetHashCode()); //寫入地址 sendCommand.Add((byte)(startAddr / 256)); sendCommand.Add((byte)(startAddr % 256)); //寫入數(shù)據(jù) 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(); } /// <summary> /// 生成發(fā)送命令報(bào)文 /// </summary> /// <param name="sendCommand"></param> /// <returns></returns> protected string generateSendCommandStr(byte[] sendCommand) { var sendCommandStr = string.Empty; foreach (var item in sendCommand) { sendCommandStr += Convert.ToString(item, 16) + " "; } return sendCommandStr; } /// <summary> /// 驗(yàn)證CRC /// </summary> /// <param name="value">要驗(yàn)證的數(shù)據(jù)</param> /// <returns></returns> 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); //自己驗(yàn)證的結(jié)果 byte[] CRCbuf = Crc16(buf, buf.Length); //把上面驗(yàn)證的結(jié)果和串口返回的校驗(yàn)碼(最后兩個(gè))進(jìn)行比較 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 }; /// <summary> /// CRC校驗(yàn) /// </summary> /// <param name="pucFrame">字節(jié)數(shù)組</param> /// <param name="usLen">驗(yàn)證長度</param> /// <returns>2個(gè)字節(jié)</returns> 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; } } /// <summary> /// Modbus 功能碼 /// </summary> public enum ModbusFunctionCode { /// <summary> /// 讀取輸出線圈 /// </summary> [Description("讀取輸出線圈")] ReadOutCoil = 1, /// <summary> /// 讀取輸入線圈 /// </summary> [Description("讀取輸入線圈")] ReadInputCoil = 2, /// <summary> /// 讀取保持寄存器 /// </summary> [Description("讀取保持寄存器")] ReadRegister = 3, /// <summary> /// 讀取輸入寄存器 /// </summary> [Description("讀取輸入寄存器")] ReadInputRegister = 4, /// <summary> /// (寫入)預(yù)置單線圈 /// </summary> [Description("(寫入)預(yù)置單線圈")] WriteCoil = 5, /// <summary> /// (寫入)預(yù)置單個(gè)寄存器 /// </summary> [Description("(寫入)預(yù)置單個(gè)寄存器")] WriteRegister = 6, /// <summary> /// (寫入)預(yù)置多寄存器 /// </summary> [Description("(寫入)預(yù)置多寄存器")] WriteRegisterMultiple = 16, } }
RTU
串口基類 SerialPortBase
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 規(guī)定4個(gè)存儲區(qū) // 區(qū)號 名稱 讀寫 范圍 // 0區(qū) 輸出線圈 可讀可寫 00001-09999 // 1區(qū) 輸入線圈 只讀 10001-19999 // 2區(qū) 輸入寄存器 只讀 30001-39999 // 4區(qū) 保存寄存器 可讀可寫 40001-19999 //功能碼 //01H 讀取輸出線圈 //02H 讀取輸入線圈 //03H 讀取保持寄存器 //04H 讀取輸入寄存器 //05H (寫入)預(yù)置單線圈 //06H (寫入)預(yù)置寄存器 //0FH (寫入)預(yù)置多線圈 //10H (寫入)預(yù)置多寄存器 /// <summary> /// 串口基類 /// </summary> public abstract class SerialPortBase : ModbusBase { protected SerialPort SerialPortObj; /// <summary> /// 初始化 /// </summary> /// <param name="portName">COM口名稱</param> /// <param name="baudRate">波特率</param> /// <param name="parity">檢驗(yàn)位</param> /// <param name="dataBits">數(shù)據(jù)位</param> /// <param name="stopBits">停止位</param> 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(); } /// <summary> /// 關(guān)閉 /// </summary> public void Close() { if (SerialPortObj.IsOpen) { SerialPortObj.Close(); SerialPortObj.Dispose(); SerialPortObj = null; } } } //功能碼 //01H 讀取輸出線圈 //02H 讀取輸入線圈 //03H 讀取保持寄存器 //04H 讀取輸入寄存器 //05H (寫入)預(yù)置單線圈 //06H (寫入)預(yù)置寄存器 //0FH (寫入)預(yù)置多線圈 //10H (寫入)預(yù)置多寄存器 }
Modbus 串口通訊
(串口操作的所有功能這個(gè)類都能做)
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 { /// <summary> /// Modbus 串口通訊 /// </summary> public class ModbusRTU : SerialPortBase { private string _className = "ModbusRTU"; /// <summary> /// Modbus 串口通訊 /// </summary> /// <param name="portName">COM口名稱</param> /// <param name="baudRate">波特率</param> /// <param name="parity">檢驗(yàn)位</param> /// <param name="dataBits">數(shù)據(jù)位</param> /// <param name="stopBits">停止位</param> 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); } /// <summary> /// 讀取線圈數(shù)據(jù) ok /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="length">寄存器數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>線圈數(shù)據(jù)</returns> public ModbusResultModel ReadData(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0) { return ReadData(devAddr, length, (byte)functionCode, startAddr); } /// <summary> /// 讀取數(shù)據(jù) ok /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="length">寄存器數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>線圈數(shù)據(jù)</returns> 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.拼接報(bào)文: var sendCommand = new List<byte>(); //協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量+CRC //站地址 sendCommand.Add(devAddr); //功能碼 sendCommand.Add(functionCode); //起始寄存器地址 sendCommand.Add((byte)(startAddr / 256)); sendCommand.Add((byte)(startAddr % 256)); //寄存器數(shù)量 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.發(fā)送報(bào)文 SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count); //3.接收報(bào)文 Thread.Sleep(50);//要延時(shí)一下,才能讀到數(shù)據(jù) //讀取響應(yīng)報(bào)文 byte[] respBytes = new byte[SerialPortObj.BytesToRead]; SerialPortObj.Read(respBytes, 0, respBytes.Length); // respBytes -> 01 01 02 00 00 B9 FC resultModel.Datas = respBytes; // 檢查一個(gè)校驗(yàn)位 //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 = "響應(yīng)報(bào)文校驗(yàn)失敗"; } } catch (Exception ex) { resultModel.Msg = "異常:" + ex.Message; } } else { //throw new Exception("功能碼不正確[1-4]"); resultModel.Msg = "功能碼不正確[1-4]"; } //SerialPortObj.Close(); return resultModel; } /// <summary> /// 寫入單個(gè)寄存器 ok /// 數(shù)據(jù)示例: 200 /// 功能碼 6 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="value">寫入的數(shù)據(jù)</param> /// <param name="startAddr">寫入地址</param> /// <returns>是否成功</returns> public ModbusResultModel WriteDataShort(int devAddr, short value, short startAddr = 0) { var resultModel = new ModbusResultModel(); try { //bool isOk = false; //1.拼接報(bào)文: //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.發(fā)送報(bào)文 SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Length); //3.接收報(bào)文 Thread.Sleep(50);//要延時(shí)一下,才能讀到數(shù)據(jù) //讀取響應(yīng)報(bào)文 byte[] respBytes = new byte[SerialPortObj.BytesToRead]; SerialPortObj.Read(respBytes, 0, respBytes.Length); // respBytes -> 01 01 02 00 00 B9 FC // 檢查一個(gè)校驗(yàn)位 if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == 0x06) { //isOk = true; resultModel.IsSucceed = true; } else { resultModel.Msg = "響應(yīng)報(bào)文校驗(yàn)失敗"; } } catch (Exception ex) { resultModel.Msg = "異常:" + ex.Message; } //SerialPortObj.Close(); return resultModel; } /// <summary> /// 寫入單個(gè)寄存器 ok /// 數(shù)據(jù)示例: 200 /// 功能碼 6 /// </summary> /// <param name="devAddr">站地址</param> /// <param name="dataList">寫入的數(shù)據(jù)集合</param> /// <param name="startAddr">寫入地址</param> /// <returns>是否成功</returns> 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; } /// <summary> /// 獲取寫入單個(gè)寄存器的報(bào)文 /// </summary> /// <param name="slaveStation">從站地址</param> /// <param name="startAddr">寄存器地址</param> /// <param name="value">寫入值</param> /// <returns>寫入單個(gè)寄存器的報(bào)文</returns> private byte[] GetSingleDataWriteMessage(int slaveStation, short startAddr, short value) { //從站地址 byte station = (byte)slaveStation; //功能碼 byte type = 0x06;//06H (寫入)預(yù)置寄存器 //寄存器地址 byte[] start = BitConverter.GetBytes(startAddr); //值 byte[] valueBytes = BitConverter.GetBytes(value); //根據(jù)計(jì)算機(jī)大小端存儲方式進(jìn)行高低字節(jié)轉(zhuǎn)換 if (BitConverter.IsLittleEndian) { Array.Reverse(start); Array.Reverse(valueBytes); } //拼接報(bào)文 byte[] result = new byte[] { station, type }; result = result.Concat(start.Concat(valueBytes).ToArray()).ToArray(); //計(jì)算校驗(yàn)碼并拼接,返回最后的報(bào)文結(jié)果 return result.Concat(Crc16(result, result.Length)).ToArray(); } /// <summary> /// 獲取寫入單個(gè)寄存器的報(bào)文 /// </summary> /// <param name="slaveStation">從站地址</param> /// <param name="startAddr">寄存器地址</param> /// <param name="data">寫入值</param> /// <returns>寫入單個(gè)寄存器的報(bào)文</returns> private byte[] GetSingleDataWriteMessageList(int slaveStation, short startAddr, short data) { //1.拼接報(bào)文: var sendCommand = new List<byte>(); //從站地址 byte station = (byte)slaveStation; //功能碼 byte type = 0x06;//06H (寫入)預(yù)置寄存器 //寄存器地址 byte[] start = BitConverter.GetBytes(startAddr); //值 byte[] valueBytes = BitConverter.GetBytes(data); //根據(jù)計(jì)算機(jī)大小端存儲方式進(jìn)行高低字節(jié)轉(zhuǎn)換 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(); } /// <summary> /// 寫入多個(gè)寄存器 ok /// 數(shù)據(jù)示例: 123.45f, 14.3f /// 功能碼 10 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="data">寫入的數(shù)據(jù)</param> /// <param name="startAddr">寫入地址</param> /// <returns>是否成功</returns> public ModbusResultModel WriteDataFloat(byte devAddr, float data, ushort startAddr = 0) { return WriteDataFloat(devAddr, new List<float>() { data }, startAddr); } /// <summary> /// 寫入多個(gè)寄存器 ok /// 數(shù)據(jù)示例: 123.45f, 14.3f /// 功能碼 10 /// </summary> /// <param name="devAdd">從站地址</param> /// <param name="dataList">寫入的數(shù)據(jù)</param> /// <param name="startAddr">寫入地址</param> /// <returns>是否成功</returns> 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.拼接報(bào)文: var sendCommand = new List<byte>(); //協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量+CRC //站地址 sendCommand.Add(devAddr); //功能碼 sendCommand.Add(functionCode); //寫入地址 sendCommand.Add((byte)(startAddr / 256)); sendCommand.Add((byte)(startAddr % 256)); //寄存器數(shù)量 sendCommand.Add((byte)(length / 256)); sendCommand.Add((byte)(length % 256)); // 獲取數(shù)值的byte[] List<byte> valueBytes = new List<byte>(); foreach (var data in dataList) { List<byte> temp = new List<byte>(BitConverter.GetBytes(data)); temp.Reverse();// 調(diào)整字節(jié)序 valueBytes.AddRange(temp); } // 字節(jié)數(shù) 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 //報(bào)錯(cuò)了 //2.發(fā)送報(bào)文 SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count); //3.接收報(bào)文 Thread.Sleep(50);//要延時(shí)一下,才能讀到數(shù)據(jù) //讀取響應(yīng)報(bào)文 byte[] respBytes = new byte[SerialPortObj.BytesToRead]; SerialPortObj.Read(respBytes, 0, respBytes.Length); // respBytes -> 01 01 02 00 00 B9 FC // 檢查一個(gè)校驗(yàn)位 if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode) { resultModel.IsSucceed = true; } else { resultModel.Msg = "響應(yīng)報(bào)文校驗(yàn)失敗"; } } catch (Exception ex) { resultModel.Msg = "異常:" + ex.Message; } //SerialPortObj.Close(); } else { resultModel.Msg = "dataLis參數(shù)不能為NULL 且 Count 要大于0"; } return resultModel; } /// <summary> /// 寫單個(gè)線圈輸出 ok /// </summary> /// <param name="on">開關(guān)</param> /// <param name="devAddr">從站地址</param> /// <param name="startAddr">寫入地址</param> /// <returns></returns> public ModbusResultModel WriteSingleOutOnOff(bool on, byte devAddr = 1, ushort startAddr = 0) { var resultModel = new ModbusResultModel(); try { //var isOk = false; //1.拼接報(bào)文: var sendCommand = new List<byte>(); //協(xié)議格式:站地址+功能碼+起始寄存器地址+寄存器數(shù)量+CRC //站地址 sendCommand.Add(devAddr); //功能碼 byte functionCode = 0x05; sendCommand.Add(functionCode); //寫入地址 sendCommand.Add((byte)(startAddr / 256)); sendCommand.Add((byte)(startAddr % 256)); //寫入數(shù)據(jù) sendCommand.Add((byte)(on ? 0xFF : 0x00));//true : 0xFF 開,false : 0x00 關(guān) sendCommand.Add(0x00); //CRC byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count); sendCommand.AddRange(crc); //2.發(fā)送報(bào)文 SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count); //isOk = true; resultModel.IsSucceed = true; } catch (Exception ex) { resultModel.Msg = "異常:" + ex.Message; } return resultModel; } } }
Modbus 串口通訊 讀線圈狀態(tài)
(這個(gè)類是針對線圈的 突出讀取數(shù)據(jù))
using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CJH.ModbusTool.RTU { /// <summary> /// Modbus 串口通訊 讀線圈狀態(tài) /// </summary> public class ModbusRTUCoil : ModbusRTU { //ModbusRTU rtu = new ModbusRTU(portName); //var resultModel = rtu.ReadData(1, readLen, ModbusFunctionCode.ReadOutCoil); /// <summary> /// Modbus 串口通訊 /// </summary> /// <param name="portName">COM口名稱</param> /// <param name="baudRate">波特率</param> /// <param name="parity">檢驗(yàn)位</param> /// <param name="dataBits">數(shù)據(jù)位</param> /// <param name="stopBits">停止位</param> 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); } /// <summary> /// 讀取線圈數(shù)據(jù) ok /// </summary> /// <param name="devAdd">從站地址</param> /// <param name="length">寄存器數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>線圈數(shù)據(jù)</returns> 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; //報(bào)文解析 // 檢查一個(gè)校驗(yàn)位 List<byte> respList = new List<byte>(model.Datas); respList.RemoveRange(0, 3); respList.RemoveRange(respList.Count - 2, 2); // 00 00 //集合反轉(zhuǎn) respList.Reverse(); //轉(zhuǎn)換成2進(jìn)制 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; } } }
TCP
ModbusTCP 基類
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 { /// <summary> /// ModbusTCP 基類 /// </summary> public abstract class ModbusTCPBase : ModbusBase { private Socket _socket = null; ushort _tid = 0;//TransactionId 最大 65535 /// <summary> /// 異常碼 字典 /// </summary> protected Dictionary<int, string> Errors = new Dictionary<int, string>() { { 0x01 , "非法功能碼"}, { 0x02 , "非法數(shù)據(jù)地址"}, { 0x03 , "非法數(shù)據(jù)值"}, { 0x04 , "從站設(shè)備故障"}, { 0x05 , "確認(rèn),從站需要一個(gè)耗時(shí)操作"}, { 0x06 , "從站忙"}, { 0x08 , "存儲奇偶性差錯(cuò)"}, { 0x0A , "不可用網(wǎng)關(guān)路徑"}, { 0x0B , "網(wǎng)關(guān)目標(biāo)設(shè)備響應(yīng)失敗"}, }; /// <summary> /// Modbus TCP 通訊 初始化 /// </summary> /// <param name="host">主機(jī)地址</param> /// <param name="port">端口</param> protected void Init(string host, int port) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _socket.Connect(host, port); } /// <summary> /// 讀取報(bào)文的 公共方法 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="length">寄存器數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>返回報(bào)文(協(xié)議格式:TransactionId+協(xié)議標(biāo)識+后續(xù)字節(jié)數(shù)+站地址+功能碼+起始寄存器地址+寄存器數(shù)量)</returns> 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 協(xié)議標(biāo)識 sendCommand.Add(0x00); sendCommand.Add(0x00); //后續(xù)字節(jié)數(shù) sendCommand.Add((byte)(baseCommand.Length / 256)); sendCommand.Add((byte)(baseCommand.Length % 256)); _tid++; _tid %= 65535; sendCommand.AddRange(baseCommand); return sendCommand.ToArray(); } /// <summary> /// 讀取報(bào)文的 公共方法 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="data">輸入數(shù)據(jù)</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始寄存器地址</param> /// <returns>返回報(bào)文(協(xié)議格式:TransactionId+協(xié)議標(biāo)識+后續(xù)字節(jié)數(shù)+站地址+功能碼+起始寄存器地址+寄存器數(shù)量)</returns> 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 協(xié)議標(biāo)識 sendCommand.Add(0x00); sendCommand.Add(0x00); //后續(xù)字節(jié)數(shù) 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 { //報(bào)文 //TransactionId Modbus 協(xié)議標(biāo)識 后續(xù)字節(jié)數(shù) 從站地址 功能碼 起始寄存器地址 寄存器數(shù)量 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 數(shù)據(jù)長度 (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); //取出下標(biāo)為 :4和5的數(shù)據(jù)[00 0D] var len_bytes = resp_bytes.ToList().GetRange(4, 2); //起始寄存器地址 的反向操作 //將下標(biāo)為4和5 兩個(gè)字節(jié)轉(zhuǎn)成10進(jìn)制數(shù) int len = len_bytes[0] * 256 + len_bytes[1]; //獲取數(shù)據(jù)的長度 //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); //檢查響應(yīng)報(bào)文是否正常 //0x83 1000 0011 //01 83 02 [異常,83, 異常代碼 :02] if (resp_bytes[1] > 0x08)//判斷是否異常 { //resp_bytes[2] = 異常代碼 :02 //說明響應(yīng)是異常報(bào)文 //返回異常信息,根據(jù)resp_bytes字節(jié)進(jìn)行異常關(guān)聯(lián) if (Errors.ContainsKey(resp_bytes[2])) { resultModel.Msg = Errors[resp_bytes[2]];//獲取異常碼對應(yīng)的異常說明 } } else { //resp_bytes[2] = 0A 數(shù)據(jù)長度 (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; } /// <summary> /// 解析數(shù)據(jù) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="datas"></param> /// <returns></returns> public List<T> AnalysisDatas<T>(byte[] datas) { //data_bytes 每兩個(gè)字節(jié)轉(zhuǎn)成一個(gè)數(shù)字, float 4個(gè)字節(jié)轉(zhuǎn)成一個(gè)數(shù)字,double 8個(gè)字節(jié)轉(zhuǎn)成一個(gè)數(shù)字 //2 ushort short int16 uint32 float //4 int uint int32 uint32 float //8 double //16 decimal var resultValue = new List<T>(); 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(); //找到返回類型和傳入的類型一至,且方法的參數(shù)是2個(gè)的方法 var method = typeMethodList.FirstOrDefault(mi => mi.ReturnType == typeof(T) && mi.GetParameters().Length == 2); if (method == null) { throw new Exception("數(shù)據(jù)轉(zhuǎn)換類型出錯(cuò)!"); } else { //由 bitConverter_type 執(zhí)行找到的 method方法,注意參數(shù)數(shù)量,上面找是的兩個(gè)參數(shù)的方法 var value = method.Invoke(bitConverter_type, new object[] { temp_bytes.ToArray(), 0 }); resultValue.Add((T)value); } } } catch (Exception ex) { } return resultValue; } } }
Modbus TCP 通訊
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.Sockets; namespace CJH.ModbusTool.TCP { /// <summary> /// Modbus TCP 通訊 /// </summary> public class ModbusTCP : ModbusTCPBase { /// <summary> /// Modbus TCP 通訊 /// </summary> /// <param name="host">主機(jī)地址</param> /// <param name="port">端口</param> public ModbusTCP(string host, int port) { //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //_socket.Connect(host, port); Init(host, port); } /// <summary> /// 讀取保持型寄存器 03 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="count">數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始地址</param> //public ModbusResultModel ReadHoldingRegister(byte devAddr, ushort length, byte functionCode = 3, ushort startAddr = 0) //{ // var resultModel = new ModbusResultModel(); // //報(bào)文 // //TransactionId Modbus 協(xié)議標(biāo)識 后續(xù)字節(jié)數(shù) 從站地址 功能碼 起始寄存器地址 寄存器數(shù)量 CRC // //0x00 0x01 0x00 0x00 0x00 0x06 devAddr functionCode 0x00 0x0A // try // { // ushort tid = 0;//TransactionId 最大 65535 // var sendCommand = new List<byte>(); // //TransactionId // sendCommand.Add((byte)(tid / 256)); // sendCommand.Add((byte)(tid % 256)); // //Modbus 協(xié)議標(biāo)識 // sendCommand.Add(0x00); // sendCommand.Add(0x00); // //后續(xù)字節(jié)數(shù) // sendCommand.Add(0x00); // sendCommand.Add(0x06); // //從站地址 // sendCommand.Add(devAddr); // //功能碼 // sendCommand.Add(functionCode); // //起始寄存器地址 // sendCommand.Add((byte)(startAddr / 256)); // sendCommand.Add((byte)(startAddr % 256)); // //寄存器數(shù)量 // 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 數(shù)據(jù)長度 (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); // //取出下標(biāo)為 :4和5的數(shù)據(jù)[00 0D] // var len_bytes = resp_bytes.ToList().GetRange(4, 2); // //起始寄存器地址 的反向操作 // //將下標(biāo)為4和5 兩個(gè)字節(jié)轉(zhuǎn)成10進(jìn)制數(shù) // int len = resp_bytes[4] * 256 + resp_bytes[5]; // //獲取數(shù)據(jù)的長度 // //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); // //檢查響應(yīng)報(bào)文是否正常 // //0x83 1000 0011 // //01 83 02 [異常,83, 異常代碼 :02] // if (resp_bytes[1] > 0x08)//判斷是否異常 // { // //resp_bytes[2] = 異常代碼 :02 // //說明響應(yīng)是異常報(bào)文 // //返回異常信息,根據(jù)resp_bytes字節(jié)進(jìn)行異常關(guān)聯(lián) // if (Errors.ContainsKey(resp_bytes[2])) // { // resultModel.Msg = Errors[resp_bytes[2]];//獲取異常碼對應(yīng)的異常說明 // } // } // else // { // //resp_bytes[2] = 0A 數(shù)據(jù)長度 (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; //} /// <summary> /// 讀取保持型寄存器 03 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="length">數(shù)量</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始地址</param> /// <returns>返回對象</returns> public ModbusResultModel<T> ReadHoldingRegister<T>(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0) { var resultModel = new ModbusResultModel<T>(); 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<T>(receptionModel.Datas); resultModel.IsSucceed = true; } } catch (Exception ex) { resultModel.Msg = ex.Message; } return resultModel; } /// <summary> /// 寫入保持型寄存器 03 /// </summary> /// <param name="devAddr">從站地址</param> /// <param name="datas">寫入數(shù)據(jù)</param> /// <param name="functionCode">功能碼</param> /// <param name="startAddr">起始地址</param> /// <returns>返回對象</returns> 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; } } }
這就是全部的代碼。
以上就是使用C#實(shí)現(xiàn)自己封裝的Modbus工具類庫的詳細(xì)內(nèi)容,更多關(guān)于C# Modbus的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Silverlight DataGrid中無代碼設(shè)置開始與結(jié)束日期DatePicker的實(shí)現(xiàn)方法
本篇文章是對Silverlight DataGrid中無代碼設(shè)置開始與結(jié)束日期DatePicker的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C#使用密封類實(shí)現(xiàn)密封用戶信息的示例詳解
在C#中,密封類(sealed class)是一種不能被其他類繼承的類,它用于防止其他類繼承它的功能和屬性, 下面我們就來看看如何使用密封類密封用戶的信息吧2024-02-02VS?Code里使用Debugger?for?Unity插件調(diào)試的方法(2023最新版)
Debugger for Unity是一個(gè)非正式支持的,官方推薦的,應(yīng)用最廣的,Visual Studio Code上的Unity調(diào)試插件,這篇文章主要介紹了VS?Code里使用Debugger?for?Unity插件進(jìn)行調(diào)試(2023最新版),需要的朋友可以參考下2023-02-02C#實(shí)現(xiàn)進(jìn)制轉(zhuǎn)換
這篇文章介紹了C#實(shí)現(xiàn)進(jìn)制轉(zhuǎn)換的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05