FX5使用SLMP与上位机通讯测试

以下使用C#进行测试,仅供参考

准备工作

这两天正在测试FX5的Slmp与上位机通讯试验,目前暂时可以实现读写操作,但是还面临很多其它问题,想与大家探讨,共同进步:
通讯结果如下图所示:
在这里插入图片描述

安装VS2019 Community版本

官方网址:https://visualstudio.microsoft.com/zh-hans/vs/community/

  1. 安装步骤略;

  2. 当前Demo应用程序的目标框架为.NET Framework4.6.1;
    在这里插入图片描述

  3. 生成目标平台x64,由于三菱的组件MX Componet4不支持x64,此次的测试目标以x64为主;
    在这里插入图片描述

安装 Gx Works3 1.070Y版本

官方网址:https://mitsubishielectric.yangben.cn/assets/detail/5b553ac0e4040f68ccc99707
1.安装步骤略;
2.连接PLC;
在这里插入图片描述
3.设置以太网端口;
在这里插入图片描述
4.设置对象设备连接配置设置;
在这里插入图片描述
5.反映设置并关闭,点击应用,并写入PLC中,断电重启,使设置生效;

查文档手册

通过三菱提供的MELSEC iQ-FFX5用户手册(SLMP篇)查询重要技术参数,以获取指定报文通过tcp/ip协议以Socket套接字进行发送/接收。

报文格式

在这里插入图片描述

指令

在这里插入图片描述

软元件范围

在这里插入图片描述

方法实现

定义枚举方法

1.定义PLC操作状态(PLCState)

 #region<!--Eps:PLC操作状态(PLCState)-->
    /// <summary>
    /// PLC操作状态(PLCState)
    /// </summary>
    public enum Eps
    {
        Open,
        Close,
        Read,
        ReadRandom,
        ReadBlock,
        Write,
        WriteBit,
        WriteRandom,
        WriteBitRandom,
        WriteBlock,
    }
    #endregion

2.定义软元件类型(SoftType),以下参数可查手册获取:

 #region<!--Est:软元件类型(SoftType)-->
    /// <summary>
    /// 软元件类型(SoftType)
    /// </summary> 
    public enum Est : byte
    {
        /// <summary>
        /// 特殊继电器
        /// </summary>
        SM = 0x91,
        /// <summary>
        /// 特殊寄存器
        /// </summary>
        SD = 0xA9,
        /// <summary>
        /// 输入继电器
        /// </summary>
        X = 0x9C,
        /// <summary>
        /// 输出继电器
        /// </summary>
        Y = 0x9D,
        /// <summary>
        /// 内部继电器
        /// </summary>
        M = 0x90,
        /// <summary>
        /// 锁存继电器
        /// </summary>
        L = 0x92,
        /// <summary>
        /// 报警器
        /// </summary>
        F = 0x93,
        /// <summary>
        /// 变址继电器
        /// </summary>
        V = 0x94,
        /// <summary>
        /// 链接继电器
        /// </summary>
        B = 0xA0,
        /// <summary>
        /// 数据寄存器
        /// </summary>
        D = 0xA8,
        /// <summary>
        /// 链接寄存器
        /// </summary>
        W = 0xB4,
        /// <summary>
        /// 定时器触点
        /// </summary>
        TS = 0xC1,
        /// <summary>
        /// 定时器线圈
        /// </summary>
        TC = 0xC0,
        /// <summary>
        /// 定时器当前值
        /// </summary>
        TN = 0xC2,
        /// <summary>
        /// 计数器触点
        /// </summary>
        CS = 0xC4,
        /// <summary>
        /// 计数器线圈
        /// </summary>
        CC = 0xC3,
        /// <summary>
        /// 计数器当前值
        /// </summary>
        CN = 0xC5,
        /// <summary>
        /// 累加定时器触点
        /// </summary>
        SS = 0xC7,
        /// <summary>
        /// 累加定时器线圈
        /// </summary>
        SC = 0xC6,
        /// <summary>
        /// 累加定时器当前值
        /// </summary>
        SN = 0xC8,
        /// <summary>
        /// 链接特殊继电器
        /// </summary>
        SB = 0xA1,
        /// <summary>
        /// 链接特殊寄存器
        /// </summary>
        SW = 0xB5,
        /// <summary>
        /// 步进继电器
        /// </summary>
        S = 0x98,
        /// <summary>
        /// 文件寄存器
        /// </summary>
        R = 0xAF
    }
    #endregion

3.定义数据类型(DataType)

 #region<!--Edt:数据类型(DataType)-->
    /// <summary>
    /// 数据类型(DataType)
    /// </summary>
    public enum Edt
    {
        /// <summary>
        /// 二进制数
        /// </summary>
        Bit,
        /// <summary>
        /// 16位单精度整型
        /// </summary>
        Short,
        /// <summary>
        /// 32位双精度整型
        /// </summary>
        Int,
        /// <summary>
        /// 32位单精度浮点
        /// </summary>
        Float,
        /// <summary>
        /// 64位双精度浮点
        /// </summary>
        Double
    }
    #endregion

定义Message方法

此方法的目的是为了自动生成报文,通过输入数据类型,数据地址,等必要参数,获取我们想要的报文信息;

namespace Chustange.Functional
{
    public class Message
    {
        public Eps Eps { get; set; }

        public Address Address { get; set; }

        public List<Address> Addresses = new List<Address>();

        public ushort Count { get; set; }
        public Message(Eps Eps, Address Address)
        {
            this.Eps = Eps;
            this.Address = Address;
        }
        public Message(Eps Eps, Address Address, ushort Count)
        {
            this.Eps = Eps;
            this.Address = Address;
            this.Count = Count;
        }
        public Message(Eps Eps, List<Address> Addresses)
        {
            this.Eps = Eps;
            this.Addresses = Addresses;
        }

其中Address是结构体,定义如下:

public struct Address
    {
        public Est SoftType;
        public ushort Index;
        public Edt DataType;
        public object Value;
        public int Decimalplace;
    }

地址使用声明如下,例:定义PLC中的M101,数据类型为bit,软元件为M,索引为101,值默认为1;定义PLC中的D101,数据类型为short,软元件为D,索引为101,值默认为10,保留小数点后0位:

static Address M100 = new Address { DataType = Edt.Bit, SoftType = Est.M, Index = 101, Value = 1 };
static Address D101 = new Address { DataType = Edt.Short, SoftType = Est.D, Index = 101, Value = 10,Decimalplace=0};

定义报文类如下:

internal class Smc
    {
        /// <summary>
        /// 副头部
        /// </summary
        internal readonly List<byte> DeputyHead = new List<byte>() { 0x50, 0x00 };
        /// <summary>
        /// 网络编号
        /// </summary>
        internal readonly List<byte> NetworkNumber = new List<byte>() { 0x00 };
        /// <summary>
        /// 可编程控制器编号
        /// </summary>
        internal readonly List<byte> PLCNumber = new List<byte>() { 0xFF };
        /// <summary>
        /// 请求目标模块IO编号
        /// </summary>
        internal readonly List<byte> RequestDestinationModuleIONumber = new List<byte>() { 0xFF, 0x03 };
        /// <summary>
        /// 请求目标模块站号
        /// </summary>
        internal readonly List<byte> RequestDestinationModulestationNumber = new List<byte>() { 0x00 };
        /// <summary>
        /// 请求数据长度
        /// </summary>
        internal List<byte> RequestDataLenth = new List<byte>();
        /// <summary>
        /// CPU监视定时器
        /// </summary>
        internal readonly List<byte> CPUWatchdogTimer = new List<byte>() { 0x10, 0x00 };
        /// <summary>
        /// 指令
        /// </summary>
        internal List<byte> Command = new List<byte>();
        /// <summary>
        /// 子指令
        /// </summary>
        internal List<byte> SubCommand = new List<byte>() { 0x00, 0x00 };
        /// <summary>
        /// 请求数据
        /// </summary>
        internal List<byte> Data = new List<byte>();
        /// <summary>
        /// 软元件
        /// </summary>
        internal List<byte> Type = new List<byte>();
        /// <summary>
        /// 索引
        /// </summary>
        internal List<byte> Index = new List<byte>();
        /// <summary>
        /// 访问点数
        /// </summary>
        internal List<byte> Typecount = new List<byte>();
    }

根据PLC操作状态(PLCState),获取相关的指令

 private void Command()
        {
            switch (Eps)
            {
                case Eps.Read:
                    Smc.Command = new List<byte>() { 0x01, 0x04 };
                    break;
                case Eps.Write:
                    Smc.Command = new List<byte>() { 0x01, 0x14 };
                    break;
                case Eps.WriteBit:
                    Smc.Command = new List<byte>() { 0x02, 0x14 };
                    break;
                case Eps.ReadRandom:
                    Smc.Command = new List<byte>() { 0x03, 0x04 };
                    break;
                case Eps.WriteRandom:
                    Smc.Command = new List<byte>() { 0x02, 0x14 };
                    break;
                case Eps.ReadBlock:
                    Smc.Command = new List<byte>() { 0x06, 0x04 };
                    break;
                case Eps.WriteBlock:
                    Smc.Command = new List<byte>() { 0x06, 0x14 };
                    break;
            }
        }

获取随机读取的报文

 #region<!--GetRandomReadData:随机读取数据-->
        /// <summary>
        /// 获取随机读取的报文
        /// </summary>
        private void RandomReadData()
        {
            byte singlecount = 0;
            byte doublecount = 0;
            List<byte> _data = new List<byte>();

            for (int i = 0; i < Addresses.Count; i++)
            {
                if (Addresses[i].DataType == Edt.Bit || Addresses[i].DataType == Edt.Short)
                {
                    singlecount++;
                }

                if (Addresses[i].DataType == Edt.Float || Addresses[i].DataType == Edt.Int)
                {
                    doublecount++;
                }

                ///软元件
                List<byte> _type = new List<byte>() { (byte)Addresses[i].SoftType };
                //编号
                byte[] index = BitConverter.GetBytes((short)Addresses[i].Index);
                List<byte> _index = new List<byte>() { index[0], index[1], 0x00 };
                _data = _data.Concat(_index).Concat(_type).ToList();
            }

            List<byte> _count = new List<byte>() { singlecount, doublecount };

            Smc.Data = _count.Concat(_data).ToList();
        }
        #endregion

获取随机写入的报文

  #region<!--GetRandomWriteData:随机写入数据-->
        /// <summary>
        /// 获取随机写入的报文
        /// </summary>
        private void RandomWriteData()
        {
            byte singlecount = 0;
            byte doublecount = 0;
            List<byte> _singledata = new List<byte>();
            List<byte> _doubledata = new List<byte>();
            for (int i = 0; i < Addresses.Count; i++)
            {
                if (Addresses[i].DataType == Edt.Bit || Addresses[i].DataType == Edt.Short)
                {
                    singlecount++;
                }
                else if (Addresses[i].DataType == Edt.Float || Addresses[i].DataType == Edt.Int)
                {
                    doublecount++;
                }
                ///软元件
                List<byte> _type = new List<byte>() { (byte)Addresses[i].SoftType };

                byte[] index = BitConverter.GetBytes((short)Addresses[i].Index);

                List<byte> _index = new List<byte>() { index[0], index[1], 0x00 };

                if (Addresses[i].DataType == Edt.Bit || Addresses[i].DataType == Edt.Short)
                {
                    _singledata = _singledata.Concat(_index).Concat(_type).Concat(BitConverter.GetBytes(Convert.ToInt16(Addresses[i].Value))).ToList();
                }
                else if (Addresses[i].DataType == Edt.Float)
                {
                    _doubledata = _doubledata.Concat(_index).Concat(_type).Concat(BitConverter.GetBytes(Convert.ToSingle(Addresses[i].Value))).ToList();
                }
                else if (Addresses[i].DataType == Edt.Int)
                {
                    _doubledata = _doubledata.Concat(_index).Concat(_type).Concat(BitConverter.GetBytes(Convert.ToInt32(Addresses[i].Value))).ToList();
                }
            }
            List<byte> _count = new List<byte>() { singlecount, doublecount };

            Smc.Data = _count.Concat(_singledata).Concat(_doubledata).ToList();
        }
        #endregion

生成最终报文

 public List<byte> GetCode()
        {
            GetData();
            Command();
            short count = Convert.ToInt16(Smc.CPUWatchdogTimer.Count + Smc.Command.Count + Smc.SubCommand.Count + Smc.Data.Count);
            Smc.RequestDataLenth = BitConverter.GetBytes(count).ToList();
            return Smc.DeputyHead.Concat(Smc.NetworkNumber)
                                 .Concat(Smc.PLCNumber)
                                 .Concat(Smc.RequestDestinationModuleIONumber)
                                 .Concat(Smc.RequestDestinationModulestationNumber)
                                 .Concat(Smc.RequestDataLenth)
                                 .Concat(Smc.CPUWatchdogTimer)
                                 .Concat(Smc.Command)
                                 .Concat(Smc.SubCommand)
                                 .Concat(Smc.Data)
                                 .ToList();
        }

定义Socket方法

获取指定生成的报文,通过socket套接字以tcp/ip的方式发送给PLC。

namespace Chustange.Functional
{
    internal class Socket
    {
        private SOCKET _mySocket;
        /// <summary>
        /// 声明端口号,并默认为8080
        /// </summary>
        internal int Port { get; set; } = 8080;
        internal string IpAddress { get; set; } = "127.0.0.1";
        internal List<byte> ReadCode { get; set; }
        internal List<byte> WriteCode { get; set; }
        internal bool Connected => _mySocket != null && _mySocket.Connected;
        private const int ReadBufferSize = 65535;
        private IPAddress Ip => IPAddress.Parse(IpAddress);
        private IPEndPoint IpEnd => new IPEndPoint(Ip, Port);
        internal byte[] Data { get; private set; } = new byte[ReadBufferSize];

        private readonly ManualResetEvent _openDone = new ManualResetEvent(false);
        private readonly ManualResetEvent _closeDone = new ManualResetEvent(false);

        private readonly ManualResetEvent _sendDone = new ManualResetEvent(false);

        private readonly ManualResetEvent _receiveDone = new ManualResetEvent(false);


        private readonly object _openLock = new object();
        private readonly object _sendLock = new object();
        private readonly object _receiveLock = new object();

        private readonly object _readLock = new object();
        private readonly object _writeLock = new object();

打开套接字方法如下:


        internal void Open()
        {
            try
            {
                lock (_openLock)
                {
                    _mySocket = new SOCKET(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _openDone?.Reset();
                    _mySocket?.BeginConnect(IpEnd, OpenCallback, _mySocket);
                }
            }
            catch (SocketException se)
            {
                ("[Socket]->Open:" + se.StackTrace + Environment.NewLine + se.Message).ToLog();
            }
        }
        private void OpenCallback(IAsyncResult ar)
        {

            try
            {
                var hlSocket = (SOCKET)ar.AsyncState;

                hlSocket?.EndConnect(ar);
            }
            catch (Exception e)
            {
                ("[Socket]->ConnectCallback:" + e.StackTrace + Environment.NewLine + e.Message).ToLog();
            }
            finally
            {
                _openDone?.Set();
            }

        }

读取方法

 internal void Read()
        {
            try
            {
                lock (_readLock)
                {
                    var sd = Task.Factory.StartNew(() => { Send(ReadCode); },
                        TaskCreationOptions.RunContinuationsAsynchronously);

                    var re = Task.Factory.StartNew(Receive, TaskCreationOptions.RunContinuationsAsynchronously);
                    Task.WhenAll(sd, re);
                }
            }
            catch (SocketException se)
            {
                ("[Socket]->SendAndReceive:" + se.StackTrace + Environment.NewLine + se.Message).ToLog();
            }
        }

写入方法

internal void Write()
        {
            try
            {
                lock (_writeLock)
                {
                    Task.Factory.StartNew(() =>
                    {
                        Send(WriteCode);
                    }, TaskCreationOptions.RunContinuationsAsynchronously);
                }
            }
            catch (SocketException se)
            {
                ("[Socket]->SendAndReceive:" + se.StackTrace + Environment.NewLine + se.Message).ToLog();
            }
        }

其中判定报文发送的方法如下:

private void Send(List<byte> code)
        {
            try
            {
                lock (_sendLock)
                {
                    if (code == null) return;
                    if (code.Count <= 0) return;
                    if (_mySocket == null) return;
                    if (!_mySocket.Connected)
                    {
                        Open();
                    }

                    if (!_mySocket.Connected) return;
                    _sendDone?.Reset();
                    _mySocket?.BeginSend(code.ToArray(), 0, code.Count, SocketFlags.None, SendCallback, _mySocket);
                }
            }
            catch (SocketException se)
            {
                ("[Socket]->Send:" + se.StackTrace + Environment.NewLine + se.Message).ToLog();
            }

        }
        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                var socket = (SOCKET)ar.AsyncState;
                socket?.EndSend(ar);
            }
            catch (Exception e)
            {
                ("[Socket]->SendCallback:" + e.StackTrace + Environment.NewLine + e.Message).ToLog();
            }
            finally
            {
                _sendDone?.Set();
            }

        }

接收报文的判定结果如下

 private void Receive()
        {
            try
            {
                lock (_receiveLock)
                {
                    if (_mySocket == null) return;
                    if (!_mySocket.Connected) return;
                    _receiveDone?.Reset();
                    _mySocket?.BeginReceive(Data, 0, Data.Length, SocketFlags.None, ReceiveCallback, _mySocket);
                }
            }
            catch (SocketException ex)
            {
                ("[Socket]->Receive:" + ex.StackTrace + Environment.NewLine + ex.Message + ex.SocketErrorCode).ToLog();
            }
        }
        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                var socket = (SOCKET)ar.AsyncState;
                socket?.EndReceive(ar);
            }
            catch (Exception e)
            {
                ("[Socket]->ReceiveCallback:" + e.StackTrace + Environment.NewLine + e.Message).ToLog();
            }
            finally
            {
                _receiveDone?.Set();
            }

        }

定义SLMP类封装随机读/写方法

将上述的方法进行再一次封装,类比MX Component的WriteDeviceRandom和ReadDeviceRandom的方法,以下是我个人的习惯,如不需要请借鉴以上方法自行处理

namespace Chustange.Mould
{
    public class SLMP
    {
        private readonly Socket Socket = new Socket();
        public int Port { get { return Socket.Port; } set { Socket.Port = value; } }
        public string IPAddress { get { return Socket.IpAddress; } set { Socket.IpAddress = value; } }
        public bool Connected { get { return Socket.Connected; } }
        private byte[] Socketdata { get { return Socket.Data; } }
        public Eps Eps { get; set; }
        public int WriteResult { get; private set; }
        public int ReadResult { get; private set; }

        public void Open() => Socket.Open();

        public void Close() => Socket.Close();

        public Priority Priority { get; private set; } = Priority.Normal;

        public SLMP()
        {

        }
        public SLMP(string IPAddress, int Port)
        {
            this.IPAddress = IPAddress;
            this.Port = Port;
        }

以下是我自行封装的WriteDeviceRandom的方法;

public void WriteDeviceRandom(List<Address> writeAddresses)
        {
            try
            {
                var message = new Message(Eps.WriteRandom, writeAddresses);
                Socket.WriteCode = message.GetCode();
                if (Socket.WriteCode != null)
                {
                    Socket.Write();
                }
            }
            catch (Exception e)
            {
                ("[SLMP]->WriteDeviceRandom:" + e.StackTrace + Environment.NewLine + e.Message).ToLog();
            }

            if (Socketdata[7] == 0x02)
            {
                WriteResult = 0;
            }
            else
            {
                WriteResult = -1;
            }
        }

以下是我封装的ReadDeviceRandom的方法;

  public void ReadDeviceRandom(List<Address> readAddresses, ref List<object> readDatas)
        {
            if (readAddresses == null) return;
            if (readAddresses.Count <= 0) return;
            var message = new Message(Eps.ReadRandom, readAddresses);
            try
            {
                Socket.ReadCode = message.GetCode();
                if (Socket.ReadCode != null)
                {
                    Socket.Read();
                }
            }
            catch (Exception e)
            {

                ("[SLMP]->ReadDeviceRandom:" + e.StackTrace + Environment.NewLine + e.Message).ToLog();
            }
            var socketCount = GetAddressLength(readAddresses);
            if (Socketdata[7] == socketCount + 2)
            {
                var count = 0;
                if (readDatas != null)
                {
                    readDatas.Clear();
                }
                else
                {
                    readDatas = new List<object>();
                }
                for (var i = 0; i < readAddresses.Count; i++)
                {
                    switch (readAddresses[i].DataType)
                    {
                        case Edt.Bit:
                            count += 2;
                            var result = new BitArray(new byte[] { Socketdata[8 + count + 1] })[0];
                            readDatas.Add(result);
                            break;
                        case Edt.Short:
                            count += 2;
                            readDatas.Add((short)(Socketdata[8 + count + 2] << 8) + Socketdata[8 + count + 1]);
                            break;
                        case Edt.Int:
                            count += 2;
                            var itLow = (short)((Socketdata[8 + count + 2] << 8) + Socketdata[8 + count + 1]);
                            count += 2;
                            var itHigh = (short)((Socketdata[8 + count + 2] << 8) + Socketdata[8 + count + 1]);
                            readDatas.Add((itHigh << 16) + itLow);
                            break;
                        case Edt.Float:
                            count += 2;
                            var ftLow = (short)((Socketdata[8 + count + 2] << 8) + Socketdata[8 + count + 1]);
                            count += 2;
                            var ftHigh = (short)((Socketdata[8 + count + 2] << 8) + Socketdata[8 + count + 1]);
                            readDatas.Add(Parse.ShortToFloat(ftLow, ftHigh));
                            break;
                    }
                }
            }
            else
            {
                if (readDatas != null)
                {
                    readDatas.Clear();
                }
                else
                {
                    readDatas = new List<object>();
                }
            }
        }

在Demo中运行

在Demo项目中引用上述生成的动态链接库Chustange.dll,当前名称可自行命名:
在这里插入图片描述
引用及定义如下:

namespace Demo
{
    public partial class MainForm : Form
    {
        uc_First uc_First = new uc_First();
        Us_Second us_Second;
        readonly SLMP slmp = new SLMP("192.168.1.220", 1106);
        //  readonly SLMP slmp2 = new SLMP("192.168.1.220", 1107);

        static Address M100 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 100, Value = 1 };
        static Address D101 = new Address() { DataType = Edt.Short, SoftType = Est.D, Index = 101, Value = 1 };
        static Address M101 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 101, Value = 1 };
        static Address M102 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 102, Value = 1 };
        static Address M103 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 103, Value = 1 };
        static Address M104 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 104, Value = 1 };
        static Address M105 = new Address() { DataType = Edt.Bit, SoftType = Est.M, Index = 105, Value = 1 };
        public bool ReadLock = false;

        static readonly List<Address> ReadAddresses = new List<Address>() { M101, D101 };
        List<object> ReadDatas = new List<object>();
        List<Address> WriteAddresses = new List<Address>() { M101, D101 };

目前测试使用的代码片段如下:

 private void T1_show()
        {
            object lc_t1 = new object();
            int i = 0;
            object[] value;
            while (true)
            {
                lock (lc_t1)
                {
                    if (slmp.Priority == Priority.Normal)
                    {
                        if (!slmp.Connected)
                        {
                            slmp.Open();
                        }
                        if (slmp.Connected)
                        {

                            M101.Value = !Convert.ToBoolean(M101.Value);
                            D101.Value = ++i;
                            WriteAddresses = new List<Address> { M101, D101 };

                            slmp.WriteDeviceRandom(WriteAddresses);
                            Task.Delay(1).Wait();
                            slmp.ReadDeviceRandom(ReadAddresses, ref ReadDatas);
                        }

                        if (ReadDatas != null)
                        {
                            if (ReadDatas.Count == ReadAddresses.Count)
                            {
                                value = ReadDatas.ToArray();
                                txt_t1.Text(value[0].ToString());
                                txt_t2.Text(value[1].ToString());
                                Console.WriteLine($@"M101:{value[0]},D101:{value[1]}");
                                slmp.Running();
                            }
                        }
                        if (i > 1000)
                        {
                            i = 0;
                        }
                    }
                }
                Task.Delay(1).Wait();
            }
            slmp.Close();
            // slmp2.Close();
        }

结果如下图所示:
在这里插入图片描述
控制台输出结果如下:
在这里插入图片描述

结束语

目前测试还存在很多不足,socket中有很多BUG,自动报文生成也有些问题,持续优化中,以上思路仅供思考,完整代码就不拿出来献丑了。

[1]: MELSEC iQ-FFX5用户手册(SLMP篇)

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tang_0427

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

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

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

打赏作者

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

抵扣说明:

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

余额充值