IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装) -> 正文阅读

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

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

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-09 12:08:14  更:2021-09-09 12:09:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:39:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码