使用Nmodbus4 使用RTU和TCP两种方式读取数据,博途ModbusTCP设置,客户端和服务器端

部署运行你感兴趣的模型镜像

建立ModbusTCP通讯库

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modbus.Device;
using System.Net.Sockets;
using thinger.cn.DataConvertHelper;
using System.Net;

namespace NModbusHelper
{
    public class NmodbusTCPhelper
    {
        private TcpClient tcpClient = null;
        private ModbusIpMaster master;
        //private Socket tcpClient = null; //定义串口类对象
        #region 打开与关闭Socket     
        public bool Connect(string ip, int port)
        {
            //tcpclient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            try
            {
                tcpClient = new TcpClient();
                tcpClient.Connect(IPAddress.Parse(ip), port);
                //IPEndPoint ie = new IPEndPoint(IPAddress.Parse(ip), port);
                //tcpClient.Connect(ie);
                master = ModbusIpMaster.CreateIp(tcpClient);
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool Disconnect()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion

        public ushort[] ReadKeepReg(string iAddress, int iLength)//偏移量,寄存器数量
        {
            try
            {
                ushort[] des = master.ReadHoldingRegisters(Convert.ToUInt16(iAddress), Convert.ToUInt16(iLength));
                byte[] res = ByteArrayLib.GetByteArrayFromUShortArray(des);
                return des;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public List<float> AnalyseData_4x(ushort[] des, string iAddress)
        {
            int StartByte;
            StartByte = int.Parse(iAddress) * 2;
            List<float> floatArray = new List<float>();
            byte[] byteArray = ByteArrayLib.GetByteArrayFromUShortArray(des);

            for (int i = StartByte; i < byteArray.Length; i += 4)
            {
                floatArray.Add(FloatLib.GetFloatFromByteArray(byteArray, i));
            }
            return floatArray;
        }
    }
}

使用ModbusTCP 读取

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using Modbus.Device;
using thinger.cn.DataConvertHelper;
using NModbusHelper;

namespace NmodbusPractice2
{
    public partial class FrmMain : Form
    {
        public FrmMain()
        {
            InitializeComponent();
        }
        string ip = "127.0.0.1";
        int port = 502;

        NmodbusTCPhelper objTcp = new NmodbusTCPhelper();
        private bool IsConnected = false;
        private System.Windows.Forms.Timer updateTimer = new System.Windows.Forms.Timer();
        ushort[] res;
        List<float> floatArray;
        private void Addinfo(string info)
        {
            this.isInfo.Items.Insert(
                0, DateTime.Now.ToString("HH:mm:ss") + " " + info + Environment.NewLine);
        }
        private void btnConnect_Click(object sender, EventArgs e)
        {
            IsConnected = objTcp.Connect(ip, port);
            Addinfo(IsConnected ? "连接成功" : "连接失败");
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            this.updateTimer.Interval = 1000;
            this.updateTimer.Tick += UpdateTimer_Tick;
            this.updateTimer.Start();
            if (res != null)
            {
                Addinfo("读取成功");
            }
        }

        private void UpdateTimer_Tick(object sender, EventArgs e)
        {
            string iAddress = this.txtVarAdd.Text;
            int iLength = Convert.ToUInt16(this.numCount.Text);

            res = objTcp.ReadKeepReg(iAddress, iLength);

            //Uint
            //this.isInfo.Items.Add(res.ToString());
            //for (int i = 0; i < res.Length; i++)
            //{
            //    this.isInfo.Items.Add(res[i].ToString());
            //}

            //float

            floatArray = objTcp.AnalyseData_4x(res,iAddress);
            for (int i = 0; i < floatArray.ToArray().Length; i++)
            {
                this.isInfo.Items.Add(floatArray.ToArray()[i].ToString());
            }

        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            objTcp.Disconnect();
            Addinfo("断开连接");
        }

        private void ChangeRTU_Click(object sender, EventArgs e)
        {
            FrmRTU frmRTU = new FrmRTU();
            frmRTU.Show();
        }
    }
 
}

建立ModbusRTU的对象和通信库

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;

namespace NModbusHelper
{
    public class ModbusRTUEntity
    {
        public string Port { get; set; }

        //波特率
        public int Paud { get; set; }

        //数据位
        public int DataBit { get; set; }

        //校验位
        public Parity IParity { get; set; }

        //停止位
        public StopBits IStopBit { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using thinger.cn.DataConvertHelper;
using System.IO.Ports;
using Modbus.Device;
using System.Threading;

namespace NModbusHelper
{
    public class NmodbusRTUhelper
    {
        private SerialPort MyCom = new SerialPort();
        IModbusSerialMaster master;
        //定义CRC校验高低位
        private byte ucCRCHi = 0xFF;
        private byte ucCRCLo = 0xFF;

        public bool OpenMyComm(int iBaudRate, string iPortNo, int iDataBits, Parity iParity, StopBits iStopBits)
        {
            try
            {
                if (MyCom.IsOpen)
                {
                    MyCom.Close();
                }
                //MyCom.BaudRate = iBaudRate;
                //MyCom.PortName = iPortNo;
                //MyCom.DataBits = iDataBits;
                //MyCom.Parity = iParity;
                //MyCom.StopBits = iStopBits;
                MyCom.BaudRate = iBaudRate;
                MyCom.PortName = iPortNo;
                MyCom.DataBits = iDataBits;
                MyCom.Parity = iParity;
                MyCom.StopBits = iStopBits;

                MyCom.Open();

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public bool ClosePort()
        {
            if (MyCom.IsOpen)
            {
                MyCom.Close();
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="iDevAdd">从站地址</param>
        /// <param name="iAddress">偏移量</param>
        /// <param name="iLength">寄存器个数</param>
        /// <returns></returns>
        public ushort[] ReadKeepReg(int iDevAdd, int iAddress, int iLength)
        {
            byte[] SendCommand = new byte[8];
            SendCommand[0] = (byte)iDevAdd;
            SendCommand[1] = 0x03;
            SendCommand[2] = (byte)((iAddress - iAddress % 256) / 256);
            SendCommand[3] = (byte)(iAddress % 256);
            SendCommand[4] = (byte)((iLength - iLength % 256) / 256);
            SendCommand[5] = (byte)(iLength % 256);
            Crc16(SendCommand, 6);
            SendCommand[6] = ucCRCLo;
            SendCommand[7] = ucCRCHi;
            try
            {
                MyCom.Write(SendCommand, 0, 8);
            }
            catch (Exception)
            {

                return null;
            }
            MyCom.ReadTimeout = 500;
            Thread.Sleep(500);
            if (MyCom.BytesToRead>0)
            {
                master = ModbusSerialMaster.CreateRtu(MyCom);
            }
            //Convert.ToByte(iDevAdd), (ushort)iAddress, (ushort)iLength
            ushort[] des = master.ReadHoldingRegisters(Convert.ToByte(iDevAdd), (ushort)iAddress, (ushort)iLength);
            //byte[] res = ByteArrayLib.GetByteArrayFromUShortArray(des);
            return des;
        }

        public List<float> AnalyseData_4x(ushort[] des, string iAddress)
        {
            int StartByte;
            StartByte = int.Parse(iAddress) * 2;
            List<float> floatArray = new List<float>();
            byte[] byteArray = ByteArrayLib.GetByteArrayFromUShortArray(des);

            for (int i = StartByte; i < byteArray.Length; i += 4)
            {
                floatArray.Add(FloatLib.GetFloatFromByteArray(byteArray, i));
            }
            return floatArray;
        }

        #region  CRC校验
        private static 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
         };
        private static 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
         };
        private void Crc16(byte[] pucFrame, int usLen)
        {
            int i = 0;
            ucCRCHi = 0xFF;
            ucCRCLo = 0xFF;
            UInt16 iIndex = 0x0000;

            while (usLen-- > 0)
            {
                iIndex = (UInt16)(ucCRCLo ^ pucFrame[i++]);
                ucCRCLo = (byte)(ucCRCHi ^ aucCRCHi[iIndex]);
                ucCRCHi = aucCRCLo[iIndex];
            }

        }

        #endregion
    }
}

读取

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Modbus.Device;
using thinger.cn.DataConvertHelper;
using NModbusHelper;
using System.IO.Ports;
using System.Threading;

namespace NmodbusPractice2
{
    public partial class FrmRTU : Form
    {
        public FrmRTU()
        {
            InitializeComponent();
            this.RTUPort.Text = "COM3";
            this.RTUPaud.Text = "9600";
            this.RTUIParity.Text = "None";
            this.RTUDataBit.Text = "8";
            this.RTUIStopBit.Text = "1";
            this.RTUiDevAdd.Text = "1";
            this.RTUiAddress.Text = "0";
            this.RTUiLength.Text = "20";
        }

        ModbusRTUEntity rTUEntity = new ModbusRTUEntity();
        NmodbusRTUhelper rTUhelper = new NmodbusRTUhelper();
        private System.Windows.Forms.Timer upDateTimer = new System.Windows.Forms.Timer();

        CancellationTokenSource cts = new CancellationTokenSource();
        private bool IsConnected = false;
        ushort[] res;
        

        private void Addinfo(string info)
        {
            this.isInfo.Items.Insert(
                0, DateTime.Now.ToString("HH:mm:ss") + " " + info + Environment.NewLine);
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            rTUEntity.Port = this.RTUPort.Text.Trim();
            rTUEntity.Paud = Convert.ToInt32(this.RTUPaud.Text.Trim());
            rTUEntity.DataBit = Convert.ToInt32(this.RTUDataBit.Text.Trim());
            switch (this.RTUIStopBit.Text.Trim())
            {
                case "1":
                    rTUEntity.IStopBit = StopBits.One;
                    break;
                case "2":
                    rTUEntity.IStopBit = StopBits.Two;
                    break;
                default:
                    rTUEntity.IStopBit = StopBits.One;
                    break;
            }

            switch (this.RTUIParity.Text.Trim())//效验信息
            {
                case "None":
                    rTUEntity.IParity = Parity.None;
                    break;
                case "Even":
                    rTUEntity.IParity = Parity.Even;
                    break;
                case "Odd":
                    rTUEntity.IParity = Parity.Odd;
                    break;
                default:
                    rTUEntity.IParity = Parity.None;
                    break;
            }

            IsConnected = rTUhelper.OpenMyComm(rTUEntity.Paud, rTUEntity.Port, rTUEntity.DataBit, rTUEntity.IParity, rTUEntity.IStopBit);
            Addinfo(IsConnected ? "连接成功" : "连接失败");
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            this.upDateTimer.Interval = 1000;
            this.upDateTimer.Tick += UpDateTimer_Tick;
            this.upDateTimer.Start();
        }

        private void UpDateTimer_Tick(object sender, EventArgs e)
        {
            string RTUiDevAdd = this.RTUiDevAdd.Text.Trim();
            string RTUiAddress = this.RTUiAddress.Text.Trim();
            string RTUiLength = this.RTUiLength.Text.Trim();
            res = rTUhelper.ReadKeepReg(int.Parse(RTUiDevAdd), int.Parse(RTUiAddress), int.Parse(RTUiLength));
            for (int i = 0; i < res.Length; i++)
            {
                this.isInfo.Items.Add(res[i].ToString());
            }
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            IsConnected = rTUhelper.ClosePort();
            Addinfo(IsConnected ? "断开失败" : "断开成功");
        }

        private void btnTaskRead_Click(object sender, EventArgs e)
        {
            string RTUiDevAdd = this.RTUiDevAdd.Text.Trim();
            string RTUiAddress = this.RTUiAddress.Text.Trim();
            string RTUiLength = this.RTUiLength.Text.Trim();
            Task.Run(() =>
            {
                while (true)
                {
                    res = rTUhelper.ReadKeepReg(int.Parse(RTUiDevAdd), int.Parse(RTUiAddress), int.Parse(RTUiLength));
                    for (int i = 0; i < res.Length; i++)
                    {
                        this.isInfo.Invoke(new Action<ushort>(t =>
                        {
                            this.isInfo.Items.Add(t.ToString());
                        }), res[i]);
                    }
                }

            }, cts.Token);
        }
    }
}

modbus可以一主多从。

主站(modbusPoll,MB_Clent)是 客户端(client),需要指定起始值,偏移量,存储区(40001等),数据长度(上位机)。

从站(modbusSlave,MB_Server)是服务器端(server),不需要指定长度,存储区。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

客户端,主站:
在这里插入图片描述

在这里插入图片描述

RTU:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DAL
{

    public class ModbusRtu
    {

        #region 对象或属性

        //定义串口通信的对象
        private SerialPort MyCom = new SerialPort();

        //创建通信超时属性
        public int ReadTimeOut { get; set; } = 2000;
        public int WriteTimeOut { get; set; } = 2000;

        //读取返回报文超时时间
        public int ReceiveTimeOut { get; set; } = 2000;

        //字节顺序
        public DataFormat DataFormat { get; set; } = DataFormat.ABCD;

        //创建一个互斥锁对象
        private SimpleHybirdLock InteractiveLock = new SimpleHybirdLock();

        //通信延时时间
        public int SleepTime { get; set; } = 20;

        #endregion

        #region 打开关闭串口
        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="iBaudRate">波特率</param>
        /// <param name="iPortName">串口号</param>
        /// <param name="iDataBits">数据位</param>
        /// <param name="iParity">校验位</param>
        /// <param name="iStopBits">停止位</param>
        public void OpenMyCom(int iBaudRate, string iPortName, int iDataBits, Parity iParity, StopBits iStopBits)
        {
            //如果当前串口是打开的,先关闭一下
            if (MyCom.IsOpen)
            {
                MyCom.Close();
            }

            //串口通信对象实例化
            MyCom = new SerialPort(iPortName, iBaudRate, iParity, iDataBits, iStopBits);

            //设置超时时间
            MyCom.ReadTimeout = this.ReadTimeOut;
            MyCom.WriteTimeout = this.WriteTimeOut;

            //打开串口
            MyCom.Open();
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        public void CloseMyCom()
        {
            if (MyCom.IsOpen)
            {
                MyCom.Close();
            }
        }
        #endregion

        #region 读取输出线圈 功能码01H
        /// <summary>
        /// 读取输出线圈方法
        /// </summary>
        /// <param name="iDevAdd">从站地址</param>
        /// <param name="iAddress">起始地址</param>
        /// <param name="iLength">长度</param>
        /// <returns></returns>
        public byte[] ReadOutputStatus(int iDevAdd, int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x01, (byte)(iAddress / 256), (byte)(iAddress % 256) });
            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            int byteLength;

            if (iLength % 8 == 0)
            {
                byteLength = iLength / 8;
            }
            else
            {
                byteLength = iLength / 8 + 1;
            }

            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:解析报文

                //验证:功能码+字节计数
                if (response[1] == 0x01 && response[2] == byteLength)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion

        #region 读取输入线圈 功能码02H
        /// <summary>
        /// 读取输入线圈
        /// </summary>
        /// <param name="iDevAdd">从站地址</param>
        /// <param name="iAddress">起始地址</param>
        /// <param name="iLength">长度</param>
        /// <returns></returns>
        public byte[] ReadInputStatus(int iDevAdd, int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x02, (byte)(iAddress / 256), (byte)(iAddress % 256) });
            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            int byteLength = 0;
            if (iLength % 8 == 0)
            {
                byteLength = iLength / 8;
            }
            else
            {
                byteLength = iLength / 8 + 1;
            }

            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:解析报文

                if (response[1] == 0x02 && response[2] == byteLength)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion

        #region 读取保持寄存器  功能码03H

        public byte[] ReadKeepReg(int iDevAdd, int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x03, (byte)(iAddress / 256), (byte)(iAddress % 256) });
            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            int byteLength = iLength * 2;

            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:解析报文

                if (response[1] == 0x03 && response[2] == byteLength&&response.Length==byteLength+5)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion

        #region 读取输入寄存器  功能码04H

        public byte[] ReadInputReg(int iDevAdd, int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x04, (byte)(iAddress / 256), (byte)(iAddress % 256) });
            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            int byteLength = iLength * 2;

            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:解析报文

                if (response[1] == 0x04 && response[2] == byteLength)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion

        #region 强制单线圈 功能码05H

        public bool ForceCoil(int iDevAdd, int iAddress, bool SetValue)
        {

            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x05, (byte)(iAddress / 256), (byte)(iAddress % 256) });
            if (SetValue)
            {
                SendCommand.Add(new byte[] { 0xFF, 0x00 });
            }
            else
            {
                SendCommand.Add(new byte[] { 0x00, 0x00 });
            }
            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            byte[] response = new byte[8];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:验证报文
                return ByteArrayEquals(SendCommand.array, response);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 预置单个寄存器 功能码06H

        public bool PreSetSingleReg(int iDevAdd, int iAddress, short SetValue)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x06, (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            byte[] response = new byte[8];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:验证报文
                return ByteArrayEquals(SendCommand.array, response);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetSingleReg(int iDevAdd, int iAddress, ushort SetValue)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x06, (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            SendCommand.Add(Crc16(SendCommand.array, 6));

            //第二步:发送报文  接受报文

            byte[] response = new byte[8];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:验证报文
                return ByteArrayEquals(SendCommand.array, response);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 强制多线圈 功能码0FH

        public bool ForceMultiCoil(int iDevAdd, int iAddress, bool[] SetValue)
        {
            //第一步:拼接报文

            byte[] iSetValue = BoolArrayToByteArray(SetValue);

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x0F, (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(SetValue.Length / 256), (byte)(SetValue.Length % 256) });

            SendCommand.Add((byte)iSetValue.Length);

            SendCommand.Add(iSetValue);

            SendCommand.Add(Crc16(SendCommand.array, 7 + iSetValue.Length));

            //第二步:发送报文  接受报文

            byte[] response = new byte[8];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:报文验证   验证前6个字节是否正确,再验证CRC是否正确

                byte[] b = GetByteArray(response, 0, 6);

                byte[] crc = Crc16(b, 6);

                return ByteArrayEquals(GetByteArray(SendCommand.array, 0, 6), b) && crc[0] == response[6] && crc[1] == response[7];

            }

            return false;
        }

        #endregion

        #region 预置多个寄存器 功能码10H

        //浮点型   Int32  UInt32    浮点型数组   Int32数组  UInt32数组  浮点型/int16/uint16

        public bool PreSetMultiByteArray(int iDevAdd, int iAddress, byte[] SetValue)
        {
            if (SetValue == null || SetValue.Length == 0 || SetValue.Length % 2 == 1)
            {
                return false;
            }

            int RegLength = SetValue.Length / 2;

            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x10, (byte)(iAddress / 256), (byte)(iAddress % 256) });

            //寄存器数量
            SendCommand.Add(new byte[] { (byte)(RegLength / 256), (byte)(RegLength % 256) });

            //字节数量
            SendCommand.Add((byte)SetValue.Length);

            //具体字节
            SendCommand.Add(SetValue);

            //CRC
            SendCommand.Add(Crc16(SendCommand.array, 7 + SetValue.Length));

            //第二步:发送报文  接受报文

            byte[] response = new byte[8];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步:报文验证   验证前6个字节是否正确,再验证CRC是否正确

                byte[] b = GetByteArray(response, 0, 6);

                byte[] crc = Crc16(b, 6);

                return ByteArrayEquals(GetByteArray(SendCommand.array, 0, 6), b) && crc[0] == response[6] && crc[1] == response[7];
            }
            else
            {
                return false;
            }
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, float SetValue)
        {
            return PreSetMultiByteArray(iDevAdd, iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, int SetValue)
        {
            return PreSetMultiByteArray(iDevAdd, iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, uint SetValue)
        {
            return PreSetMultiByteArray(iDevAdd, iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, float[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();

            foreach (float item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }

            return PreSetMultiByteArray(iDevAdd, iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, int[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();

            foreach (int item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }

            return PreSetMultiByteArray(iDevAdd, iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, short[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();

            foreach (short item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }

            return PreSetMultiByteArray(iDevAdd, iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, uint[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();

            foreach (uint item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }

            return PreSetMultiByteArray(iDevAdd, iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iDevAdd, int iAddress, ushort[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();

            foreach (ushort item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }

            return PreSetMultiByteArray(iDevAdd, iAddress, bSetValue.array);
        }


        #endregion

        #region 通用发送报文并接受
        /// <summary>
        /// 通用发送报文并接受
        /// </summary>
        /// <param name="sendByte"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        private bool SendData(byte[] sendByte, ref byte[] response)
        {
            //上锁
            InteractiveLock.Enter();

            try
            {
                //发送报文
                MyCom.Write(sendByte, 0, sendByte.Length);

                //定义一个Buffer
                byte[] buffer = new byte[1024];

                //定义一个内存
                MemoryStream ms = new MemoryStream();

                //定义读取开始时间
                DateTime start = DateTime.Now;

                //【1】获取当前缓冲区的值,判断是否有值,如果有值,读取过来,放到内存中

                //接着再去判断,如果当前缓冲区的值为0,说明读完了

                //【2】如果每次读取都读不到,我们用超时时间来做判断

                while (true)
                {
                    Thread.Sleep(SleepTime);

                    if (MyCom.BytesToRead >= 1)
                    {
                        int spcount = MyCom.Read(buffer, 0, buffer.Length);
                        ms.Write(buffer, 0, spcount);
                    }
                    else
                    {
                        //判断是否超时
                        if ((DateTime.Now - start).TotalMilliseconds > this.ReceiveTimeOut)
                        {
                            ms.Dispose();
                            return false;
                        }
                        else if (ms.Length > 0)
                        {
                            break;
                        }
                    }

                }

                response = ms.ToArray();
                ms.Dispose();

                return true;
            }
            catch (Exception)
            {
                return false;
            }

            finally
            {
                //解锁
                InteractiveLock.Leave();
            }

        }

        #endregion

        #region 自定义截取字节数组

        /// <summary>
        /// 自定义截取字节数组
        /// </summary>
        /// <param name="dest">目标字节数组</param>
        /// <param name="offset">偏移</param>
        /// <param name="count">数量</param>
        /// <returns></returns>
        private byte[] GetByteArray(byte[] dest, int offset, int count)
        {
            byte[] res = new byte[count];
            if (dest != null && dest.Length >= offset + count)
            {
                for (int i = 0; i < count; i++)
                {
                    res[i] = dest[offset + i];
                }
                return res;
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 判断两个数组是否完全一样

        private bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return false;
            if (b1.Length != b2.Length) return false;
            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i])
                {
                    return false;
                }
            }
            return true;
        }


        #endregion

        #region 布尔数组转换成字节数组

        private byte[] BoolArrayToByteArray(bool[] val)
        {
            if (val == null && val.Length == 0) return null;

            int iByteArrLen = 0;

            if (val.Length % 8 == 0)
            {
                iByteArrLen = val.Length / 8;
            }
            else
            {
                iByteArrLen = val.Length / 8 + 1;
            }

            byte[] result = new byte[iByteArrLen];

            //遍历每个字节
            for (int i = 0; i < iByteArrLen; i++)
            {
                int total = val.Length < 8 * (i + 1) ? val.Length - 8 * i : 8;

                //遍历当前字节的每个位赋值
                for (int j = 0; j < total; j++)
                {
                    result[i] = SetbitValue(result[i], j + 1, val[8 * i + j]);
                }
            }
            return result;
        }

        #endregion

        #region 字节某个位赋值

        private byte SetbitValue(byte data, int index, bool val)
        {
            if (index > 8 || index < 1)
                return 0;
            int v = index < 2 ? index : (2 << (index - 2));
            return val ? (byte)(data | v) : (byte)(data & ~v);
        }

        #endregion

        #region 16位类型转换成字节数组

        private byte[] GetByteArrayFrom16Bit(short SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);
            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }

        private byte[] GetByteArrayFrom16Bit(ushort SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);
            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }

        #endregion

        #region 32位类型转换成字节数组

        private byte[] GetByteArrayFrom32Bit(float SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
            }
            return Res;
        }

        private byte[] GetByteArrayFrom32Bit(int SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
            }
            return Res;
        }

        private byte[] GetByteArrayFrom32Bit(uint SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
            }
            return Res;
        }


        #endregion

        #region  CRC校验

        private static 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
         };
        private static 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
         };
        private byte[] Crc16(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };

            UInt16 iIndex = 0x0000;

            while (usLen-- > 0)
            {
                iIndex = (UInt16)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }

        #endregion

    }
}

ByteArray:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DAL
{
    public class ByteArray
    {
        List<byte> list = null;
        //初始化
        public ByteArray()
        {
            list = new List<byte>();
        }
        //添加单个字节
        public void Add(byte item)
        {
            list.Add(item);
        }
        //添加数组
        public void Add(byte[] item)
        {
            list.AddRange(item);
        }
        //清除
        public void Clear()
        {
            list = new List<byte>();
        }
        //获取数组
        public byte[] array
        {
            get { return list.ToArray(); }
        }
    }
}

TCP:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DAL
{
    public class ModbusTcp
    {

        #region 属性字段

        //ModbusTcp:Modbus协议在TCP/IP网络上的应用

        //创建通信对象
        private Socket tcpClient;

        //发送超时
        public int SendTimeOut { get; set; } = 2000;

        public int ReceiveTimeOut { get; set; } = 2000;

        //从站地址
        public int SlaveAddress { get; set; } = 1;

        public int MaxCycleTimer { get; set; } = 5;

        //编码格式
        public DataFormat DataFormat { get; set; } = DataFormat.ABCD;

        //互斥锁
        private SimpleHybirdLock InteractiveLock = new SimpleHybirdLock();

        #endregion

        #region 建立及断开连接
        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        public void Connect(string ip, string port)
        {
            //实例化Socket
            tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //设置Socket属性
            tcpClient.SendTimeout = this.SendTimeOut;

            tcpClient.ReceiveTimeout = this.ReceiveTimeOut;

            //封装一个EndPoint对象
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));

            //建立连接
            tcpClient.Connect(endPoint);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
            }
        }
        #endregion

        #region 读取输出线圈  功能码0x01

        public byte[] ReadOutputStatus(int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 1 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步:验证报文
            int length = 0;

            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x01 && rcv[8] == length)
                {
                    // 第五步:解析报文
                    return GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取输入线圈  功能码0x02

        public byte[] ReadInputStatus(int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 2 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });


            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步:验证报文
            int length = 0;

            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x02 && rcv[8] == length)
                {
                    // 第五步:解析报文
                    return GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取保持寄存器   功能码0x03

        public byte[] ReadKeepReg(int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 3 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步:验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步:解析报文
                    return GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取输入寄存器   功能码0x04

        public byte[] ReadInputReg(int iAddress, int iLength)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 4 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步:验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步:解析报文
                    return GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 预置单线圈   功能码0x05

        /// <summary>
        /// 强制单个线圈
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool ForceCoil(int iAddress, bool value)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 5 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            if (value)
            {
                SendCommand.Add(new byte[] { (byte)(0xFF), (byte)(0x00) });
            }
            else
            {
                SendCommand.Add(new byte[] { (byte)(0x00), (byte)(0x00) });
            }

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步:验证报文

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }


        #endregion

        #region 预置单个寄存器   功能码0x06

        public bool PreSetSingleReg(int iAddress, short SetValue)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetSingleReg(int iAddress, ushort SetValue)
        {
            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 预置多个线圈   功能码0x0F

        public bool ForceMultiCoil(int iAddress, bool[] SetValue)
        {
            byte[] iSetValue = BoolArrayToByteArray(SetValue);

            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0});

            int byteLength = 7 + iSetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength %256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress,0x0F});

            //起始线圈
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //线圈数量

            SendCommand.Add((byte)(SetValue.Length / 256));

            SendCommand.Add((byte)(SetValue.Length % 256));

            //字节计数
            SendCommand.Add((byte)(iSetValue.Length));

            //具体字节
            SendCommand.Add(iSetValue);

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;

                return ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }

        }

        #endregion

        #region 预置多个寄存器

        //float   Int32  Uint32  float[]  Int32[]  UInt32[]         A  float  B int32  C  Uint32

        //字节数组

        public bool PreSetMultiByteArray(int iAddress, byte[] SetValue)
        {
            //首先判断一下字节数组是否正确

            if (SetValue == null || SetValue.Length == 0 || SetValue.Length % 2 == 1)
            {
                return false;
            }

            int RegLength = SetValue.Length / 2;

            //第一步:拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0 });

            int byteLength = 7 + SetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength % 256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress, 0x10 });

            //起始寄存器
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //寄存器数量

            SendCommand.Add((byte)(RegLength / 256));

            SendCommand.Add((byte)(RegLength % 256));

            //字节计数
            SendCommand.Add((byte)(SetValue.Length));

            //具体字节
            SendCommand.Add(SetValue);

            //第二步:发送报文

            //第三步:接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;
                return ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetMultiReg(int iAddress, float SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));      
        }

        public bool PreSetMultiReg(int iAddress, int SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, uint SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        /// <summary>
        /// 写入浮点型数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, float[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入int数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, int[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入uint数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, uint[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        // short  ushort  short[]  ushort[]  

        public bool PreSetMultiReg(int iAddress, short SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, ushort SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, short[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iAddress, ushort[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        #endregion

        #region 自定义截取字节数组
        /// <summary>
        /// 自定义截取字节数组
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        private byte[] GetByteArray(byte[] dest, int offset, int count)
        {
            if (dest != null && dest.Length >= offset + count)
            {
                byte[] result = new byte[count];
                Array.Copy(dest, offset, result, 0, count);
                return result;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region 判断两个字节数组是否完全一致

        private bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return false;
            if (b1.Length == 0 || b2.Length == 0 || b1.Length != b2.Length) return false;
            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i])
                {
                    return false;
                }
            }
            return true;
        }

        #endregion

        #region 布尔数组转换成字节数组

        private byte[] BoolArrayToByteArray(bool[] val)
        {
            if (val == null || val.Length == 0) return null;

            int iByteArrLen = 0;

            if (val.Length % 8 == 0)
            {
                iByteArrLen = val.Length / 8;

            }
            else
            {
                iByteArrLen = val.Length / 8 + 1;
            }

            byte[] result = new byte[iByteArrLen];

            for (int i = 0; i < iByteArrLen; i++)
            {
                //比如我们布尔的长度是20,  1-8表示第一个字节,9-16表示第二个字节,剩下的4个布尔

                int total = i == iByteArrLen - 1 ? val.Length - 8 * i : 8;

                for (int j = 0; j < total; j++)
                {
                    result[i] = SetbitValue(result[i], j + 1, val[8 * i + j]);
                }
            }
            return result;
        }

        #endregion

        #region 给字节某个位赋值
        private byte SetbitValue(byte data, int index, bool val)
        {
            if (index > 8 || index < 1)
                return 0;
            int v = index < 2 ? index : (2 << (index - 2));
            return val ? (byte)(data | v) : (byte)(data & ~v);
        }

        #endregion

        #region 16位整型转换字节数组

        private byte[] GetByteArrayFrom16Bit(short SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;
            
                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }
        private byte[] GetByteArrayFrom16Bit(ushort SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;

                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }

        #endregion

        #region 32位数据转换字节数组

        private byte[] GetByteArrayFrom32Bit(float SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }


        private byte[] GetByteArrayFrom32Bit(int SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }


        private byte[] GetByteArrayFrom32Bit(uint SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }

        #endregion

        #region 读取并接受

        /// <summary>
        /// 发送并接受
        /// </summary>
        /// <param name="SendByte"></param>
        /// <returns></returns>
        private byte[] SendAndReceive(byte[] SendByte)
        {
            InteractiveLock.Enter();
            try
            {
                tcpClient.Send(SendByte);
                return ReadMessage();
            }
            catch (Exception)
            {
                return null;
            }
            finally
            {
                InteractiveLock.Leave();
            }
        }

        /// <summary>
        /// 读取缓冲区值
        /// </summary>
        /// <returns></returns>
        private byte[] ReadMessage()
        {
            int count = tcpClient.Available;
            int cycletimer = 0;
            while (count == 0)
            {
                count = tcpClient.Available;
                cycletimer++;
                Thread.Sleep(20);
                if (cycletimer > MaxCycleTimer)
                {
                    break;
                }
            }
            if (count == 0)
            {
                return null;
            }
            else
            {
                byte[] buffer = new byte[count];
                tcpClient.Receive(buffer, count, SocketFlags.None);
                return buffer;
            }
        }
        #endregion
    }
}

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think> 我们正在回答用户的问题:如何在博途PLC中实现Modbus TCP通信?根据用户提供的引用之前的历史对话,我们将重点放在博途PLC(S7-1200/1500)上。 引用[1]中提到了MBUS_CLIENT(在S7-200 SMART中)作为MODBUS TCP客户端,但在博途PLC中,我们使用的是不同的指令。 引用[3]提到博途1200间MODBUS TCP通信,并提到了MB_CLIENT指令,其中RemoteAddress的设置(0.0.0.0表示响应任何客户端,指定IP则只响应该IP的请求)。 因此,在博途PLC中,实现Modbus TCP通信主要使用以下两个指令: 1. MB_CLIENT:用于作为Modbus TCP客户端(主站) 2. MB_SERVER:用于作为Modbus TCP服务器(从站) 我们将按照以下步骤进行说明: 1. 硬件软件准备 2. 在TIA Portal中配置PLC通信模块(以太网) 3. 使用MB_CLIENTMB_SERVER指令进行编程 4. 示例程序(梯形图) 5. 调试与优化 注意:由于用户之前已经了解了Modbus-RTU,因此我们重点放在TCP的实现上。 步骤: 1. **硬件软件准备** - 使用S7-1200或S7-1500系列PLC,并确保具有以太网端口(或扩展以太网模块)。 - 安装TIA Portal软件(版本至少V13 SP1以上,建议使用较新版本)。 2. **配置PLC网络** - 在TIA Portal中创建项目,添加PLC设备。 - 配置PLC的IP地址(例如192.168.0.1)子网掩码(255.255.255.0)。 - 如果使用多个PLC,确保它们在同一个网络中。 3. **使用Modbus TCP指令库** - 在TIA Portal中,Modbus TCP的指令块位于“指令”选项卡下的“通信”->“其他”中(具体位置可能因版本略有不同)。 - 主要指令: - `MB_CLIENT`:用于主站(客户端)发起通信请求。 - `MB_SERVER`:用于从站(服务器)响应请求。 4. **编程示例(客户端)** - 作为客户端(主站)时,使用`MB_CLIENT`指令向服务器发送请求。 - 参数配置: - `REQ`:触发请求(上升沿触发一次请求) - `DISCONNECT`:断开连接(0表示保持连接,1断开) - `CONNECT`:指向连接参数的结构(需要提前定义) - `MB_MODE`:功能码(0-读,1-写,2-其他) - `MB_DATA_ADDR`:Modbus寄存器地址(如40001) - `MB_DATA_LEN`:数据长度(要读写的寄存器数量) - `DATA_PTR`:指向数据缓冲区的指针(用于存储读取数据或写入数据) - 连接参数结构(CONNECT)需要定义如下(DB块或M区): ```ini IP_ADDR: ARRAY [1..4] OF BYTE // 服务器IP地址,如[192,168,0,2] PORT: WORD // 端口号,默认502 ...(其他参数见文档) ``` - 示例梯形图网络(读取10个保持寄存器): ```ladder // 网络1:触发读取请求 A I0.0 // 触发信号 FP EdgeBit // 上升沿检测 = RequestBit // 请求使能 CALL "MB_CLIENT" REQ := RequestBit, // 请求信号(上升沿触发) DISCONNECT := FALSE, // 保持连接 CONNECT := P#DB1.DBX0.0 BYTE 16, // 指向连接参数DB块 MB_MODE := 0, // 0=读取 MB_DATA_ADDR := 40001, // 起始地址 MB_DATA_LEN := 10, // 读取10个寄存器 DATA_PTR := P#DB2.DBX0.0 WORD 10, // 数据存储区(DB2中10个字) DONE => DoneBit, // 完成标志 ERROR => ErrorBit, // 错误标志 STATUS => StatusWord; // 状态字 ``` 5. **编程示例(服务器)** - 作为服务器(从站)时,使用`MB_SERVER`指令等待并响应客户端的请求。 - 参数配置: - `CONNECT`:连接参数(与客户端类似,但IP地址可以设置为0.0.0.0表示接受任何客户端的连接) - `MB_HOLD_REG`:指向保持寄存器的指针(通常是一个DB块) - 示例梯形图网络: ```ladder // 网络1:服务器持续运行 CALL "MB_SERVER" CONNECT := P#DB3.DBX0.0 BYTE 16, // 连接参数(DB3中定义) MB_HOLD_REG := P#DB4.DBX0.0 WORD 100, // 保持寄存器区域(100个字) DONE => ServerDone, // 完成标志(通常不使用) ERROR => ServerError, // 错误标志 STATUS => ServerStatus; // 状态 ``` 6. **调试与优化** - 使用状态机轮询:避免频繁调用,可以设置轮询多个从站的状态机。 - 错误处理:检查STATUS字,根据错误代码排查问题(如连接失败、超时等)。 - 连接管理:对于客户端,如果需要轮询多个服务器,每次轮询后可以短暂断开再连接下一个(设置DISCONNECT为TRUE,然后重新触发)。 7. **注意事项** - Modbus TCP的寄存器地址在指令中直接使用Modbus地址(如40001),不需要转换。 - 数据格式:注意大端序(与Modbus-RTU一致)。 - 服务器指令`MB_SERVER`只需要调用一次(通常放在循环中断中或主程序循环调用)。 引用说明: [^1]: 博途PLC1200/1500PLC ModbusTcp通信梯形图篇(轮询) [^3]: 博途1200间MODBUS TCP通信 相关问题: 1. 如何配置多个Modbus TCP客户端(主站)轮询多个服务器? 2. 在Modbus TCP通信中如何处理大端序小端序的数据转换? 3. 如何诊断Modbus TCP通信中的连接错误? 4. 博途PLC作为Modbus TCP服务器时,如何设置允许连接的客户端IP地址? </think>### 博途PLC实现Modbus TCP通信的方法 在博途PLC(S7-1200/S7-1500)中实现Modbus TCP通信,需通过TIA Portal软件配置硬件编程指令。以下是详细步骤: --- ### 1. **通信基础与指令选择** Modbus TCP基于以太网通信,采用客户端-服务器架构(主从模式): - **客户端(主站)**:主动发起请求(如读写数据),使用 `MB_CLIENT` 指令。 - **服务器(从站)**:被动响应请求,使用 `MB_SERVER` 指令。 - **关键参数**: - IP地址:PLC的本地IP(如 `192.168.0.1`) - 端口号:默认 `502` - 寄存器地址:直接使用Modbus地址(如 `40001` 对应保持寄存器)[^1][^3]。 --- ### 2. **配置步骤** #### (1) **硬件组态** 1. 在TIA Portal中添加PLC(如CPU 1215C)。 2. 配置以太网接口: - 设置PLC的IP地址(如 `192.168.0.1`)子网掩码(`255.255.255.0`)。 - 启用“激活模块的IP地址”选项。 #### (2) **导入指令库** - 在**指令列表** > **通信** > **其他** 中找到 `MB_CLIENT`(客户端 `MB_SERVER`(服务器)指令块。 - 若需多设备轮询,使用状态机管理多个 `MB_CLIENT` 调用[^1]。 --- ### 3. **编程示例** #### (1) **客户端(主站)读取数据** ```ladder // 网络1:初始化连接参数 CALL "MB_CLIENT" REQ := I0.0, // 触发信号(上升沿) DISCONNECT := FALSE, // 保持连接 CONNECT := P#DB1.DBX0.0, // 指向连接参数DB块 MB_MODE := 0, // 0=读取 MB_DATA_ADDR := 40001, // 起始寄存器地址 MB_DATA_LEN := 5, // 读取5个寄存器 DATA_PTR := P#DB2.DBX0.0, // 数据存储区(DB2) DONE => M0.0, // 完成标志 ERROR => M0.1; // 错误标志 // DB1中定义连接参数(16字节): // - IP地址:ARRAY[1..4] OF BYTE = [192,168,0,2](服务器IP) // - 端口号:502 // - 其他字段默认0 ``` #### (2) **服务器(从站)响应请求** ```ladder // 网络1:启用服务器 CALL "MB_SERVER" CONNECT := P#DB3.DBX0.0, // 连接参数DB块 MB_HOLD_REG := P#DB4.DBX0.0, // 保持寄存器区域(DB4) ERROR => M1.0; // 错误标志 // DB3中设置: // - RemoteAddress = 0.0.0.0(接受任意客户端请求)[^3] ``` --- ### 4. **关键配置说明** | **参数** | **客户端(MB_CLIENT)** | **服务器(MB_SERVER)** | |------------------|-------------------------------|-------------------------------| | **IP地址** | 目标服务器IP | 0.0.0.0(允许任意IP访问) | | **端口号** | 502(默认) | 502(默认) | | **触发方式** | REQ上升沿触发 | 持续使能 | | **数据存储** | DATA_PTR指向接收缓冲区 | MB_HOLD_REG指向共享数据区 | --- ### 5. **调试与优化** 1. **连接测试**: - 使用Modbus调试工具(如ModScan)模拟客户端/服务器发送请求。 - 检查PLC诊断缓冲区中的错误代码(如超时、连接拒绝)[^1]。 2. **性能优化**: - **轮询机制**:用状态机管理多个从站请求,避免阻塞: ```ladder // 状态机示例:空闲→请求1→等待响应→请求2... CASE State OF 0: IF NOT Busy THEN REQ1 := TRUE; State := 1; END_IF; 1: IF DONE THEN REQ1 := FALSE; State := 2; END_IF; // ...其他状态 END_CASE; ``` - **超时处理**:设置 `MB_CLIENT` 的 `DISCONNECT` 参数在超时后重连。 3. **数据格式**: - 寄存器数据为大端序(高位在前),需在PLC中处理字节交换(如 `SWAP` 指令)。 --- ### 6. **常见问题解决** - **连接失败**: - 检查防火墙是否开放502端口。 - 确认PLC与设备在同一网段。 - **数据错误**: - 验证寄存器地址是否匹配(如40001对应DB块偏移0)。 - 检查字节序是否一致[^2]。 - **多从站轮询**: - 为每个从站创建独立的 `MB_CLIENT` 调用连接参数DB块[^1]。 > **提示**:详细指令参数参考TIA Portal帮助文档(搜索“MB_CLIENT”)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潘诺西亚的火山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值