使用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
    }
}

  • 28
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

潘诺西亚的火山

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

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

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

打赏作者

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

抵扣说明:

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

余额充值