C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装)

2023-05-16

C#使用Modbus-TCP协议读取汇川PLC,Modbus读写是按照MW地址来处理的

【寄存器单位是字WORD,占用两个字节,类似于C#中的ushort(UInt16)】,实际测试发现字符串是按照字节颠倒的

本文封装函数为基础数据类型读写,字节流读写、长字符串读写。

主要封装函数有:

public int WriteValue<T>(int startAddress, T value, out string msg) where T : struct

public int WriteValue(int startAddress, byte[] buffer, out string msg)

public int WriteString(int startAddress, int length, string barcode, out string msg)

public int WriteLongString(int startAddress, string longString, out string msg)

public int ReadValue<T>(int startAddress, out T value, out string msg) where T : struct

public int ReadValue(int startAddress, int length, out byte[] values, out string msg)

public int ReadLongString(int startAddress, int length, out string longString, out string msg)

源程序如下:

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

namespace InovancePlcDemo
{
    /// <summary>
    /// 汇川PLC读写地址表
    /// 斯内科 20210907
    /// </summary>
    public class InovanceTcp
    {
        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool IsConnected;
        /// <summary>
        /// 字节存放类型,汇川PLC默认为CDAB
        /// </summary>
        public StoreByteCategory StoreByteCategory { get; set; }
        /// <summary>
        /// 是否字符串颠倒,相邻两个字符交换位置【比如:ABCD存放是BADC】
        /// </summary>
        public bool IsStringReverse = true;
        string ip;
        int port;
        /// <summary>
        /// TCP客户端对象
        /// </summary>
        TcpClient msender;
        /// <summary>
        /// TCP连接成功后生成的发送和接收Session会话对象
        /// </summary>
        Socket msock;
        /// <summary>
        /// 记录Modbus的发送和接收数据包事件
        /// 第一个参数是发送的数据包,第二个参数是响应的数据包,第三个参数的获取到响应数据包所花费的时间(ms)
        /// </summary>
        public event Action<byte[], byte[], double> RecordDataEvent;

        /// <summary>
        /// 汇川PLC的Modbus协议Tcp连接的构造函数(PLC的IP地址、PLC端口号、本机节点)
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号,默认502</param>
        /// <returns>实例化一个Modbus协议Tcp连接</returns>
        public InovanceTcp(string ip, int port, StoreByteCategory storeByteCategory = StoreByteCategory.CDAB, bool isStringReverse = true)
        {
            this.ip = ip;
            this.port = port;
            this.StoreByteCategory = storeByteCategory;
            this.IsStringReverse = isStringReverse;
            IsConnected = false;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            msender?.Close();
            msock?.Close();
            msock?.Dispose();
            IsConnected = false;
        }

        /// <summary>
        /// 连接PLC
        /// </summary>
        public void Connect()
        {
            if (!IsConnected)
            {
                msender = new TcpClient(ip, port);
                msock = msender.Client;
                msock.ReceiveTimeout = 3000;
                IsConnected = true;
            }
        }

        /// <summary>
        /// 写基本数据类型到PLC,如bool,short,int,uint,float等
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如MW32000</param>
        /// <param name="value"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteValue<T>(int startAddress, T value, out string msg) where T : struct
        {
            byte[] datas = new byte[0];
            if (typeof(T) == typeof(bool))
            {
                datas = BitConverter.GetBytes(Convert.ToBoolean(value));
            }
            else if (typeof(T) == typeof(sbyte))
            {
                datas = new byte[1] { (byte)Convert.ToSByte(value) };
            }
            else if (typeof(T) == typeof(byte))
            {
                datas = new byte[1] { Convert.ToByte(value) };
            }
            else if (typeof(T) == typeof(short))
            {
                datas = BitConverter.GetBytes(Convert.ToInt16(value));
            }
            else if (typeof(T) == typeof(ushort))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt16(value));
            }
            else if (typeof(T) == typeof(int))
            {
                datas = BitConverter.GetBytes(Convert.ToInt32(value));
            }
            else if (typeof(T) == typeof(uint))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt32(value));
            }
            else if (typeof(T) == typeof(long))
            {
                datas = BitConverter.GetBytes(Convert.ToInt64(value));
            }
            else if (typeof(T) == typeof(ulong))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt64(value));
            }
            else if (typeof(T) == typeof(float))
            {
                datas = BitConverter.GetBytes(Convert.ToSingle(value));
            }
            else if (typeof(T) == typeof(double))
            {
                datas = BitConverter.GetBytes(Convert.ToDouble(value));
            }
            else
            {
                //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
                msg = $"写Modbus数据暂不支持其他类型:{value.GetType()}";
                return -1;
            }
            byte[] rcvBuffer = new byte[0];
            return SendByte(startAddress, false, 0, ref rcvBuffer, out msg, datas);
        }

        /// <summary>
        /// 写入连续的字节数组
        /// </summary>
        /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如MW32000</param>
        /// <param name="buffer"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteValue(int startAddress, byte[] buffer, out string msg)
        {
            byte[] rcvBuffer = new byte[0];
            return SendByte(startAddress, false, 0, ref rcvBuffer, out msg, buffer);
        }
        /// <summary>
        /// 写指定长度的字符串(比如条码)到汇川PLC,最大240个字符
        /// 将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后重新填充
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="length">字符串的最大长度【范围1~240】,超过240个字符将直接返回错误</param>
        /// <param name="barcode">实际字符串,长度可能小于最大长度length</param>
        /// <returns></returns>
        public int WriteString(int startAddress, int length, string barcode, out string msg)
        {
            if (barcode == null)
            {
                barcode = string.Empty;
            }
            //将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后填充
            //防止出现 上一次写 【ABCD】,本次写 【12】, 读取字符串 结果是【12CD】 的问题
            barcode = barcode.PadRight(length, '\0');
            return WriteValue(startAddress, Encoding.ASCII.GetBytes(barcode), out msg);
        }

        /// <summary>
        /// 写长字符串
        /// 一个寄存器地址可以存放两个字节【两个字符】,设定每次最多写入200个字符,
        /// 分多次写入,每一次写入的起始寄存器地址偏移100
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="longString">长字符串</param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteLongString(int startAddress, string longString, out string msg)
        {
            msg = string.Empty;
            if (longString == null)
            {
                longString = string.Empty;
            }
            int cycleCount = 200;//每20个字符就进行分段
            int maxLength = longString.Length;
            int pageSize = (maxLength + cycleCount - 1) / cycleCount;
            int errorCode = -1;
            for (int i = 0; i < pageSize; i++)
            {
                int writeLength = cycleCount;
                if (i == pageSize - 1)
                {
                    //最后一次
                    writeLength = maxLength - i * cycleCount;
                }
                //分段字符串,每次最多200个
                string segment = longString.Substring(i * cycleCount, writeLength);
                //寄存器地址是字,所以需要 字节个数 除以2
                errorCode = WriteString(startAddress + (i * cycleCount / 2), writeLength, segment, out msg);
                if (errorCode != 0)
                {
                    return errorCode;
                }
            }
            return errorCode;
        }

        /// <summary>
        /// 读取基本数据类型
        /// </summary>
        /// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int ReadValue<T>(int startAddress, out T value, out string msg) where T : struct
        {
            value = default(T);
            int length = 0;//读取的连续字节个数
            if (typeof(T) == typeof(bool) || typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte))
            {
                length = 1;
            }
            else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
            {
                length = 2;
            }
            else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float))
            {
                length = 4;
            }
            else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double))
            {
                length = 8;
            }
            else
            {
                //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
                msg = $"读Modbus数据暂不支持其他类型:{value.GetType()}";
                return -1;
            }
            byte[] rcvBuffer = new byte[0];
            int errorCode = SendByte(startAddress, true, length, ref rcvBuffer, out msg);
            if (errorCode != 0)
            {
                return errorCode;
            }
            if (typeof(T) == typeof(bool))
            {
                value = (T)(object)Convert.ToBoolean(rcvBuffer[0]);
            }
            else if (typeof(T) == typeof(sbyte))
            {
                value = (T)(object)(sbyte)rcvBuffer[0];
            }
            else if (typeof(T) == typeof(byte))
            {
                value = (T)(object)rcvBuffer[0];
            }
            else if (typeof(T) == typeof(short))
            {
                value = (T)(object)BitConverter.ToInt16(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(ushort))
            {
                value = (T)(object)BitConverter.ToUInt16(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(int))
            {
                value = (T)(object)BitConverter.ToInt32(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(uint))
            {
                value = (T)(object)BitConverter.ToUInt32(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(long))
            {
                value = (T)(object)BitConverter.ToInt64(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(ulong))
            {
                value = (T)(object)BitConverter.ToUInt64(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(float))
            {
                value = (T)(object)BitConverter.ToSingle(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(double))
            {
                value = (T)(object)BitConverter.ToDouble(rcvBuffer, 0);
            }
            return 0;
        }

        /// <summary>
        /// 从起始地址开始,读取一段字节流
        /// </summary>
        /// <param name="startAddress">起始寄存器地址</param>
        /// <param name="length">读取的字节个数,最多250个字节,最多125个寄存器</param>
        /// <param name="values">返回的字节流数据</param>
        /// <returns>true:读取成功 false:读取失败</returns>
        public int ReadValue(int startAddress, int length, out byte[] values, out string msg)
        {
            values = new byte[length];
            return SendByte(startAddress, true, length, ref values, out msg);
        }

        /// <summary>
        /// 读取(长)字符串,设定每次最多读取200个字符,
        /// 分多次读取,每一次读取的起始寄存器地址偏移100
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="length"></param>
        /// <param name="longString"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int ReadLongString(int startAddress, int length, out string longString, out string msg)
        {
            msg = string.Empty;
            longString = string.Empty;
            int cycleCount = 200;
            int pageSize = (length + cycleCount - 1) / cycleCount;
            int errorCode = -1;
            for (int i = 0; i < pageSize; i++)
            {
                int readLength = cycleCount;
                if (i == pageSize - 1)
                {
                    //最后一次
                    readLength = length - i * cycleCount;
                }
                byte[] values;
                errorCode = ReadValue(startAddress + (i * cycleCount / 2), readLength, out values, out msg);
                if (errorCode != 0)
                {
                    return errorCode;
                }
                longString += Encoding.ASCII.GetString(values);
            }
            return errorCode;
        }

        /// <summary>
        /// 用于排他锁,确保在多线程调用该接口时,不会同时调用。确保在处理当前命令时,其他命令请等待
        /// </summary>
        static int lockedValue = 0;

        #region 关键的Modbus报文处理
        /// <summary>
        /// 报文处理:发送命令并等待响应结果
        /// 确保不能同时读写同一个地址,需要加一个锁对象
        /// </summary>
        /// <param name="startAddress">要读写的汇川PLC的内存寄存器地址,只考虑M区的MW起始寄存器地址,如果是MB请除以2后传入;如果是MD请乘以2后传入</param>
        /// <param name="isRead">读(true)还是写(false)</param>
        /// <param name="length">读取的字节个数,最多250个字节,最多125个寄存器。如果是写入,该参数无意义,直接按datas.Length处理</param>
        /// <param name="rev">返回的字节流数据,直接是用户所需的字节流数据。写寄存器时,此参数无意义</param>
        /// <param name="msg">异常信息描述</param>
        /// <param name="datas">要写入的字节流数据,读取时【isRead=true】忽略该参数</param>
        /// <returns>返回错误号,0为处理成功</returns>
        private int SendByte(int startAddress, bool isRead, int length, ref byte[] rev, out string msg, byte[] datas = null)
        {
            msg = string.Empty;
            if (!IsConnected)
            {
                msg = $"【未建立连接】尚未连接汇川PLC成功【{ip}:{port}】,请检查配置和网络,当前起始地址【{startAddress}】";
                return 1000;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                msg = $"【参数非法】汇川PLC的Modbus协议的起始地址必须在0~65535之间,当前起始地址【{startAddress}】";
                return 1001;
            }
            //读保持寄存器0x03读取的寄存器数量的范围为 1~125。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~250
            if (isRead && (length < 1 || length > 250))
            {
                msg = $"【参数非法】读取的字节数组的长度范围为1~250,当前起始地址【{startAddress}】,读取长度【{length}】";
                return 1002;
            }
            if (!isRead && (datas == null || datas.Length < 1 || datas.Length > 240))
            {
                msg = $"【参数非法】写入的字节数组的不能为空,也不能写入超过120个寄存器【240个字节】,当前起始地址【{startAddress}】";
                return 1003;
            }
            //添加锁
            while (Interlocked.Exchange(ref lockedValue, 1) != 0)
            {
                //此循环用于等待当前捕获current的线程执行结束
                //Thread.Sleep(50);
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            byte[] SendByte = new byte[12];
            //读取的寄存器个数: 如果length为偶数 则为 length/2 如果length为奇数,则为(length+1)/2。因整数相除,结果不考虑余数,所以如下通用:
            byte registerCount = (byte)((length + 1) / 2);
            if (isRead)
            {
                /*
                 * 发送的请求【读多个保持寄存器 0x03】Modbus字节流说明:
            * byte[0] byte[1] 随便指定,PLC返回的前两个字节完全一致
            * byte[2]=0 byte[3]=0 固定为0 代表Modbus标识
            * byte[4] byte[5] 排在byte[5]后面所有字节的个数,也就是总长度6
            * byte[6] 站号(从站标识),随便指定,00--FF都可以,PLC返回的保持一致
            * byte[7] 功能码,读保持寄存器 0x03:
            * byte[8] byte[9] 起始地址,如起始地址为整数20 则为 0x00 0x14,再如起始地址为整数1000,则为 0x03 0xE8
            * byte[10] byte[11] 寄存器个数【Word】,读取的数据长度【以字Word为单位】,读取Int32或Float就是两个字,读取byte或short就是一个字 范围【1~125 即 0x0001~0x007D】
                */
                SendByte = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, addrArray[1], addrArray[0], 0x00, registerCount };
            }
            else
            {
                //生成写入寄存器命令
                SendByte = GenerateWriteCommand(addrArray, datas);
            }
            System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            try
            {
                msock.Send(SendByte, SocketFlags.None);
            }
            catch (SocketException ex)
            {
                IsConnected = false;
                msg = $"【网络异常】发送命令失败,当前起始地址【{startAddress}】,套接字错误【{ex.SocketErrorCode}】,【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1004;
            }
            catch (Exception ex)
            {
                IsConnected = false;
                msg = $"【处理异常】发送命令失败,当前起始地址【{startAddress}】【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1005;
            }

            byte[] buffer = new byte[2048];
            int rcvCount = 0;//收到的字节数
            try
            {
                rcvCount = msock.Receive(buffer);
                RecordDataEvent?.Invoke(SendByte, new ArraySegment<byte>(buffer, 0, rcvCount).ToArray(), stopwatch.Elapsed.TotalMilliseconds);
            }
            catch (SocketException ex)
            {
                IsConnected = false;
                msg = $"【网络异常】接收数据失败,当前起始地址【{startAddress}】,套接字错误【{ex.SocketErrorCode}】,【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1006;
            }
            catch (Exception ex)
            {
                IsConnected = false;
                msg = $"【处理异常】接收数据失败,当前起始地址【{startAddress}】【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1007;
            }
            if (rcvCount < 9 || buffer[0] != SendByte[0] || buffer[1] != SendByte[1] || buffer[2] != SendByte[2] || buffer[3] != SendByte[3] || buffer[7] != SendByte[7])
            {
                msg = $"【数据非法】接收数据非法或者处理失败,当前起始地址【{startAddress}】接收的数据【{string.Join(",", new ArraySegment<byte>(buffer, 0, rcvCount))}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1008;
            }
            if (isRead)
            {
                /*
                 * * 接收的内容解析:【读多个保持寄存器 0x03】【Modbus响应】
            * byte[0] byte[1] 与发送的一致
            * byte[2]=0 byte[3]=0 固定为0 代表Modbus标识
            * byte[4] byte[5] 排在byte[5]后面所有字节的个数
            * byte[6] 站号,与发送的一致
            * byte[7] 功能码,与发送的一致
            * byte[8] 表示byte[8]后面跟随的字节数【发送的寄存器个数 * 2】
            * byte[9] byte[10] byte[11] byte[12] byte[...] 真实数据的字节流,字节流的总个数就是byte[8]
                */
                int receiveLength = buffer[8];
                if (receiveLength != registerCount * 2)
                {
                    //接收到的实际数据字节个数:buffer[8]
                    msg = $"【数据非法】解析接收数据非法,读取后接收的实际数据长度【{receiveLength}】不是读取寄存器数量【{registerCount}】的2倍.当前起始地址【{startAddress}】接收的数据【{string.Join(",", new ArraySegment<byte>(buffer, 0, rcvCount))}】";
                    Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                    return 1009;
                }
                rev = new byte[receiveLength];
                //相邻两个数据交换 【索引0和索引1交换,索引2与索引3交换,...】
                for (int i = 0; i < receiveLength; i++)
                {
                    if (i % 2 == 0)
                    {
                        rev[i] = buffer[9 + i + 1];
                    }
                    else
                    {
                        rev[i] = buffer[9 + i - 1];
                    }
                }
            }
            else
            {
                if (buffer[7] == 0x10 && rcvCount > 11 && buffer[11] != SendByte[11])
                {
                    //如果是写多个寄存器 并且 写入的寄存器数量不匹配
                    msg = $"【数据非法】解析接收数据非法,写多个寄存器时,返回的写入寄存器数量【{buffer[11]}】与请求的寄存器数量【{SendByte[11]}】不一致,地址【{startAddress}】接收的数据【{string.Join(",", new ArraySegment<byte>(buffer, 0, rcvCount))}】";
                    Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                    return 1010;
                }
            }            
            //释放锁
            Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
            return 0;            
        }
        #endregion

        /// <summary>
        /// 生成写入寄存器命令
        /// </summary>
        /// <param name="addrArray">起始地址字节数组,其实就是ushort地址转化后的两个字节</param>
        /// <param name="datas">要写入的字节数组</param>
        /// <returns></returns>
        private byte[] GenerateWriteCommand(byte[] addrArray, byte[] datas)
        {
            byte[] SendByte = new byte[12];
            //如果是写PLC寄存器地址
            byte registerCount = (byte)((datas.Length + 1) / 2);
            //实际写入的字节个数:注意buffer数组的长度为奇数时 需要将最后一个寄存器的高位设置为0
            byte writeByteCount = (byte)(registerCount * 2);
            if (registerCount == 1)
            {
                //如果只写入一个寄存器,可以使用0x06命令【写单个保持寄存器】,用于sbyte,byte,short,ushort                    
                SendByte = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, addrArray[1], addrArray[0], 0x00, 0x00 };
                if (datas.Length == 1)
                {
                    SendByte[11] = datas[0];
                }
                else //写入的字节数组长度为2
                {
                    SendByte[10] = datas[1];
                    SendByte[11] = datas[0];
                }
                return SendByte;
            }
            //如果2个或多个寄存器,使用命令0x10【写多个保持寄存器】,用于int,uint,float,long,ulong,double等
            //实际写入的字节个数:注意buffer数组的长度为奇数时 需要将最后一个寄存器的高位设置为0
            /*
             * 发送的请求【写多个保持寄存器 0x10】Modbus字节流说明:【写Int32,UInt32,Float需要两个寄存器 写Int64,UInt64,Double需要四个寄存器】
             * byte[0] byte[1] 随便指定,PLC返回的前两个字节完全一致
             * byte[2]=0 byte[3]=0 固定为0 代表Modbus标识 随便指定也可以
             * byte[4] byte[5] 排在byte[5]后面所有字节的个数
             * byte[6] 站号,随便指定,00--FF都可以,PLC返回的保持一致
             * byte[7] 功能码,0x10:写多个保持寄存器
             * byte[8] byte[9] 起始地址,如起始地址为整数20 则为 0x00 0x14,再如起始地址为整数1000,则为 0x03 0xE8
             * byte[10] byte[11] 寄存器数量【设置长度】,范围1~120【0x78】,因此byte[10]=0, byte[11]为寄存器数量
             * byte[12] 字节个数 也就是【寄存器数量*2】,范围【2~240】
             * byte[13] byte[14] byte[15] byte[16] byte[...]  具体的数据内容 对应 数据一高位 数据一低位 数据二高位 数据二低位
            */
            SendByte = new byte[13 + writeByteCount];
            SendByte[0] = 0x02;
            SendByte[1] = 0x01;
            SendByte[5] = (byte)(7 + writeByteCount);
            SendByte[6] = 0x01;
            SendByte[7] = 0x10;//写多个寄存器标记:0x10
            SendByte[8] = addrArray[1];
            SendByte[9] = addrArray[0];
            SendByte[11] = registerCount;
            SendByte[12] = writeByteCount;
            //交换相邻两个字节 【索引0和索引1交换,索引2与索引3交换,...需考虑datas.Length是奇数时的特殊处理】
            for (int i = 0; i < writeByteCount; i++)
            {
                if (i % 2 == 0)
                {
                    if (i + 1 == datas.Length)
                    {
                        //如果是写入奇数个字节,需要将最后一个寄存器的高位设置为0
                        SendByte[13 + i] = 0;
                    }
                    else
                    {
                        SendByte[13 + i] = datas[i + 1];
                    }
                }
                else
                {
                    SendByte[13 + i] = datas[i - 1];
                }
            }
            return SendByte;
        }
    }

    /// <summary>
    /// 字节存储类型,C#中字节存放顺序是DCBA【低字节在前】
    /// 汇川PLC默认存储是CDAB
    /// </summary>
    public enum StoreByteCategory
    {
        /// <summary>
        /// 顺序,高字节在前,低字节在后
        /// </summary>
        ABCD = 0,
        /// <summary>
        /// 字正序,字节颠倒
        /// </summary>
        BADC = 1,
        /// <summary>
        /// 字颠倒,字节正序
        /// </summary>
        CDAB = 2,
        /// <summary>
        /// 逆序,低字节在前,高字节在后
        /// </summary>
        DCBA = 3
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装) 的相关文章

  • 自己实现strcat函数

    问题描述 xff1a 自己实现一个MyStrcat函数 xff0c 要和C语言库函数的strcat函数完成同样的功能 问题分析 xff1a 首先我们要了解一下strcat函数它到底做了什么事情 1 函数原型 char strcat char
  • 简易文件打包程序

    对指定目录下面的文件进行打包 简易解包程序参考博客另外一篇文章 xff1a http blog csdn net yi ming he article details 77689453 打包方式 xff1a 把目录下面的文件名 xff0c
  • 简易解包程序

    对压缩包进行解压 简易压缩程序请参考博客的另外一篇文章 xff1a http blog csdn net yi ming he article details 77689405 解包方式 xff1a 根据打包建立的索引表 xff0c 找到对
  • linux 挂载错误Transport endpoint is not connected

    mount了mfs后 xff0c 重新挂载之后 xff0c 出现如下错误 xff1a usr local mfs bin mfsmount H 192 168 103 101 mnt fuse bad mount point 96 mnt
  • 新字体的永久注册

    CString GetCurrentModuleDir TCHAR szPath MAX PATH 43 1 61 0 if 0 61 61 GetModuleFileName HMODULE amp ImageBase szPath MA
  • yolov5/v7/v8自动检测多个文件夹及截取锚框

    目前yolo仅支持检测图片或单个文件夹 xff0c 但在很多时候需要对成百上千个文件夹中图片进行检测 xff0c 再根据得到的位置信息txt文件来截取图片 xff0c 如何一步完成呢 xff0c 详情见下文 在detect py中将save
  • 带参数的宏定义、函数与内联函数

    文章目录 前言一 宏定义1 基本用法2 带参数的宏定义 二 函数1 定义与声明2 调用 三 内联函数 inline总结 前言 在实际项目开发 xff0c 尤其是嵌入式软件项目中 xff0c 经常可以看到大量宏定义的分布 xff0c 其中又多
  • C++语言为什么跨平台?

    xfeff xfeff 现在主流的手机平台很多 xff0c 比如 xff1a Windows开发的Windows Phone xff08 WP 34 X 34 xff09 Apple 苹果公司 开发的ios xff0c Google 谷歌
  • CMake 中的list操作

    Cmake 中定义了一系列的数组操作 xff0c 使用方法如下 list INSERT lt list gt lt element index gt lt element gt lt element gt list REMOVE ITEM
  • 解决error while loading shared libraries: libXXX.so.X: cannot open shared object file: No such file

    原文转自CSDN xff0c 本文有删减 一 问题 运行hydra时 xff0c 提示错误 xff1a hydra error span class hljs keyword while span loading span class hl
  • 栈(超简单讲解版

    没错又是我来了 xff08 上一篇DFS还没写好就先来写队列与栈了哈哈哈哈 是很简单的内容呢 xff08 比DFS简单到哪里去了 先来认识一下栈 什么是栈 xff1f 度娘是这样说的 xff1a 栈 xff08 stack xff09 又名
  • ROS下使用stm32 与rosserial进行通信的开发说明及源代码示例

    关于stm32下的ROS开发环境介绍说明 xff0c 此开发环境是在Linux下使用stm32的标准库 STM32F10x StdPeriph Driver3 5 xff0c 进行stm32开发 xff0c 整体开发框架已搭建完成 xff0
  • 【ROS Rikirobot基础-使用系列 第四章节】Rikirobot小车使用激光雷达进行自动导航

    利用激光雷达进行自动导航 这里我们教大家使用的是利用激光雷达导航 xff0c 关于深度摄像头的导航我们后面会教大家使用 1 上电启动小车 xff0c 主控端执行启动小车的命令 xff1a roslaunch rikirobot bringu
  • js函数的四种调用形式以及this的指向

    以函数的 形式调用 xff1a function fun alert this 61 61 window fun 调用成功 xff0c this代表window 以方法的形式调用 var obj 61 name 61 34 hello 34
  • warning: control reaches end of non-void function

    用gcc编译一个程序的时候出现这样的警告 xff1a warning control reaches end of non void function 它的意思是 xff1a 控制到达非void函数的结尾 就是说你的一些本应带有返回值的函数
  • 项目中遇到的问题及解决方案

    1 用到的视频播放插件只支持加载相对路径 xff0c 不能加载绝对路径上的资源 解决方案 xff1a 为tomca t配置 文件 创建索引 xff0c 在 server xml文件中增加配置 lt Context path 61 34 IM
  • Oracle批量更新sql写法

    select from test table for update begin for cur in select id from test table loop update test table set name 61 39 苏晓伟 3
  • JVM 垃圾回收机制

    JVM体系结构概览 xff1a 垃圾回收 xff08 GC xff09 发生在哪个区 xff1a heap xff08 堆 xff09 区 GC是什么 xff1f 分几种 xff1a GC 分代收集算法 次数上频繁收集young区 xff0
  • JAVA 自定义注解

    多说无益 xff0c 直接上代码 import java lang annotation Documented import java lang annotation ElementType import java lang annotat
  • Vuex 学习

    什么是vuex xff1a 专门在Vue中实现集中式状态 xff08 数据 xff09 管理的一个Vue插件 xff0c 对vue应用中多个组件的共享状态进行集中式的管理 xff08 读 写 xff09 xff0c 也是一种组件间通信的方式

随机推荐

  • zookeeper本地安装启动

    下载zookeeper xff1a 链接 xff1a https pan baidu com s 151ZdXYg6QDB A8TRK0wrpw 提取码 xff1a yyds 复制到linux上并解压修改配置文件的名字 xff0c 将 zo
  • zookeeper集群安装

    准备3台服务器 xff0c 安装三个zookeeper xff0c 修改zoo cfg配置 xff0c dataDir 61 opt module zookeeper 3 5 7 zkData 分别在zkData目录下创建一个文件myid
  • zookeeper 启动停止脚本

    bin bash case 1 in 34 start 34 for i in 192 168 66 133 192 168 66 134 192 168 66 129 do echo zookeeper i 启动 ssh i 34 opt
  • ElasticSearch-全文检索

    docker 下载安装 es镜像 docker pull elasticsearch 7 4 2 es的可视化工具 docker pull kibana 7 4 2 mkdir p mydata elasticsearch config m
  • atoi()和stoi()的区别----数字字符串的处理

    相同点 xff1a 都是C 43 43 的字符处理函数 xff0c 把数字字符串转换成int输出 头文件都是 include lt cstring gt 不同点 xff1a atoi 的参数是 const char 因此对于一个字符串str
  • ROS基础教程--CostMap_2D包的一些理解

    本文是在综合了多篇文章的基础之上进行的综合 1 基本概念 Voxel xff1a 体素 xff0c 即顾名思义是体积的像素 用来在三维空间中表示一个显示基本点的单位 类似于二维平面下的pixel xff08 像素 xff09 voxel是三
  • [move_base-24] process has died [exit code -6, cmd lib/move_base/move_base odom:=mobile_base_control

    尝试使用TIAGo机器人进行SLAM时 xff0c 运行 roslaunch tiago 2dnav gazebo tiago mapping launch public sim 61 true 指令时加载TIAGo机器人失败 xff0c
  • geoserver集群搭建及数据共享设置

    Geoserver版本及所需依赖 geoserver 2 16 0geoserver 2 16 SNAPSHOT jms cluster plugingeoserver 2 16 SNAPSHOT activeMQ broker plugi
  • postgresql 9.5 now()函数少8小时

    select now 时获取的时间比系统时间少8小时 xff0c 时区问题 xff0c 可能是postgresql conf中的log timezone timezone没有配置成 PRC SELECT now AT TIME ZONE 3
  • sld样式文件demo

    标注样式为 xff1a 代码为 xff1a lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt StyledLayerDescriptor xmlns 61 34 http w
  • 清理Linux buffer/cache内存的方法

    解决Linux buffer cache内存占用过高的办法 xff08 转载 xff09 Linux中Cache内存占用过高解决办法 xff08 转载 xff09
  • windows10下修改Docker镜像目录

    1 背景需求 Windows 版本 xff08 Windows 10 wsl 2 xff09 docker 默认程序安装到c盘 xff0c 数据存储于C Users 当前用户名 AppData Local Docker wsl data e
  • conda 创建的 python 虚拟环境中安装 gdal

    在 conda 创建的Python虚拟环境中安装 gdal 可以按照以下步骤 xff1a 1 打开Anaconda Prompt或者终端 xff0c 激活创建的虚拟环境 xff0c 比如 xff1a conda activate your
  • Ubuntu远程部署及访问jupyter

    一 Ubuntu下安装jupyter notebook 创建虚拟环境 conda create n myjupyter python 61 3 9 激活虚拟环境 conda activate myjupyter 安装jupyter pip
  • postgresql 日志配置

    配置文件日志部分参数说明 REPORTING AND LOGGING Where to Log log destination 61 39 stderr 39 Valid values are combinations o
  • Postgresql 14配置文件解释说明

    Postgresql 14配置文件postgresql conf的解释说明 Postgresql 14配置文件 xff0c 配置项翻译及解释说明 suntoon 64 postgres14 data sudo nano postgresql
  • C++标准库之迭代器

    迭代器是对指针进行进一步抽象的产物 迭代器是遍历所有容器 xff08 序列 xff09 流的统一界面 xff0c 是标准库泛形算法的基础 迭代器根据其能力分为五种 xff1a categorypropertiesvalid expressi
  • 【STM32】UART串口通信详解

    目录 一 数据通信方式 1 串行与并行通信2 全双工 半双工及单工通讯3 同步通讯与异步通讯 二 串口通讯协议 STM32串口简介1 物理层1 RS232标准2 USB转串口 重点 3原生的串口到串口2 协议层1 xff09 通讯的起始和停
  • jetson tx2 刷机,安装 cuda、opencv 详细教程

    jetson tx2 刷机 xff0c 安装 cuda opencv 详细教程 jetson tx2 的详细介绍和用途可见官网 xff1a Nvidia jetson tx2 接下来主要说明jetson tx2 详细刷机过程以及在过程中踩过
  • C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装)

    C 使用Modbus TCP协议读取汇川PLC xff0c Modbus读写是按照MW地址来处理的 寄存器单位是字WORD xff0c 占用两个字节 xff0c 类似于C 中的ushort xff08 UInt16 xff09 xff0c