Modbus Poll工具的開發實現

2021-03-06 Zhuo隨手記

前言:Modbus Poll和Modbus Slave 、VSPD(虛擬串口)是工控行業經常會用到的測試工具,這裡就嘗試著做下Modbus Poll工具的實現

一。思路

    實現Modbus Poll,首先要先寫一個Modbus通信庫出來,而要寫Modbus通信庫,則要先分析Modbus不同功能碼、寄存器的發送/接收報文。

二。報文分析

    此處內容過多,所以單獨拿出03H讀保持寄存器來看:

主站詢問報文分析:此報文是由用戶配置的從站地址+功能碼+起始地址高+起始地址低+寄存器數量高+寄存器數量低+CRC高+CRC低,報文的字節數是固定的8個字節,不需要過多處理,就比較簡單,直接拼接報文後實例 SerialPort對象發送即可

從站應答報文分析:由圖可得出此應答報文是由從站地址+功能碼+字節計數+CRC高+CRC低這5個固定字節+寄存器數量*2的變化字節組成的。

三。數據處理

我們讀取寄存器,獲取的值是存放在返回的報文當中的,首先我們需要驗證下返回的報文和發送的報文,從而得出此報文是正確的帶有寄存器數值的報文,而不是返回的錯誤報文後我們再進行報文解析,將獲取的值一個個對應拿出來並顯示。

(部分)代碼貼:

首先實例一個串口對象並將其打開準備使用:

private SerialPort MyCom = new SerialPort();

打開串口方法體  

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();

        }

其次拼接報文準備發送:

這裡做了一個報文拼接的集合類,方便調用拼接

ByteArray SendCommand = new ByteArray();        

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

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

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

發送並接收返回的報文:

發送方法體:

 private bool SendData(byte[] sendByte, ref byte[] response)//ref inout參數

        {

            try

            {

                //發送

                MyCom.Write(sendByte, 0, sendByte.Length);

                //緩衝區

                byte[] buffer = new byte[1024];

                //內存

                MemoryStream ms = new MemoryStream();

                DateTime start = DateTime.Now;

                //獲取接收緩衝區的值 將值存放到內存中

                //注意這裡獲取返回報文時有兩種情況,一種是一直讀完為止,一種是讀不到值一直讀到超時為止,也就是我們做項目測試的時候經常遇到的超時錯誤

                while (true)

                {

                    

                        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;

            }

        }

傳入實參:

byte[] response = new byte[5 + iLength*2];//上面解析報文時說過了,返回報文的長度計算

            if (SendData(SendCommand.array, ref response))

                //驗證

                if (response[1] == 0x03 && response[2] == byteLength)

                {

                    return GetByteArray(response, 3, byteLength);//截取寄存器值

                }

                else

                {

                    return null;

                }

            }

            else

            {

                return null;

            }

貼一下CRC校驗方法體和截取數組方法體:

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

         };

        //低CRC字節表

        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

         };

        /// <summary>

        /// 查表獲取CRC高低字節方法體

        /// </summary>

        /// <param name="pucFrame">要發送的報文</param>

        /// <param name="usLen">截取 報文CRC之前的所有字節數</param>

        /// <returns></returns>

        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;

        }

截取數組:

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];

                }

            }

            else

            {

                return null;

            }

            return res;

        }

以上就是ModbusRTU中03H功能碼的一個實現,以下我們將獲取的數據解析下:

解析數據的時候我們先要區分下我們要獲取的是什麼類型的保持寄存器:比如Short、UShort、Int、UInt、Float等數據,所以需要先對不同類型的數據進行字節拼接

Short、UShort拼接:

 private byte [] Get16tByteArray(byte[] byteArray,int start,DataFormat type)

        {

            byte[] Res = new byte[2];

            if (byteArray !=null&&byteArray .Length >=start+2)

            {

                byte[] ResTemp = new byte[2];

                for (int i = 0; i < 2; i++)

                {

                    ResTemp[i] = byteArray[i + start];

                }  

                switch (type)

                {

                    case DataFormat.ABCD:

                        Res[0] = ResTemp[1];

                        Res[1] = ResTemp[0];

                        break;

                    case DataFormat.BADC:

                        Res = ResTemp;

                        break;

                    case DataFormat.CDAB:

                        Res[0] = ResTemp[1];

                        Res[1] = ResTemp[0];

                        break;

                    case DataFormat.DCBA:

                        Res = ResTemp;

                        break;

                }

                return Res;

            }

            else

            {

                return null;

            }

        }

Int、UInt、Float拼接:

 private byte[] Get32tByteArray(byte[] byteArray, int start, DataFormat type)

        {

            byte[] Res = new byte[4];

            if (byteArray != null && byteArray.Length >= start + 4)

            {

                byte[] ResTemp = new byte[4];

                for (int i = 0; i < 4; i++)

                {

                    ResTemp[i] = byteArray[i + start];

                }

                switch (type)

                {

                    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 = ResTemp;

                        break;

                }

                return Res;

            }

            else

            {

                return null;

            }

        }

如果獲取的是Short的寄存器:

調用我們之前寫好的03H的庫發送並獲取到截取後的寄存器值的字節數組

byte[] result= objModbusRTU.ReadInputReg(DevAdd, Address, Length);

進行數據解析

 string shortString = string.Empty;

                    if (result !=null&&result .Length ==Length *2)

                    {

                        for (int i = 0; i < result.Length; i+=2)//2個字節一個Short

                        {

                            byte[] res = Get16tByteArray(result, i, objModbusRTU.dataFormat);

                            shortString += BitConverter.ToInt16(res, 0).ToString() + " ";//轉換為16位int16數值

                        }

                        AddLog(0, "讀取成功,結果為" + shortString.Trim());

                    }

                    else

                    {

                        AddLog(1, "讀取失敗,請檢查地址、類型或連接狀態");

                    }

如果獲取的是UShort的寄存器:

byte[] result= objModbusRTU.ReadInputReg(DevAdd, Address, Length);

進行數據解析

 string ushortString = string.Empty;

                    if (result !=null&&result .Length ==Length *2)

                    {

                        for (int i = 0; i < result.Length; i+=2)

                        {

                            byte[] res = Get16tByteArray(result, i, objModbusRTU.dataFormat);

                            shortString += BitConverter.ToUInt16(res, 0).ToString() + " ";

                        }

                        AddLog(0, "讀取成功,結果為" + ushortString.Trim());

                    }

                    else

                    {

                        AddLog(1, "讀取失敗,請檢查地址、類型或連接狀態");

                    }

如果獲取的是Int的寄存器:

byte[] result= objModbusRTU.ReadInputReg(DevAdd, Address, Length*2);

進行數據解析

 string intString = string.Empty;

                    if (result !=null&&result .Length ==Length *4)

                    {

                        for (int i = 0; i < result.Length; i+=4)

                        {

                            byte[] res = Get16tByteArray(result, i, objModbusRTU.dataFormat);

                            shortString += BitConverter.ToInt32(res, 0).ToString() + " ";

                        }

                        AddLog(0, "讀取成功,結果為" + intString.Trim());

                    }

                    else

                    {

                        AddLog(1, "讀取失敗,請檢查地址、類型或連接狀態");

                    }

如果獲取的是UInt的寄存器:

byte[] result= objModbusRTU.ReadInputReg(DevAdd, Address, Length*2);

進行數據解析

 string uintString = string.Empty;

                    if (result !=null&&result .Length ==Length *4)

                    {

                        for (int i = 0; i < result.Length; i+=4)

                        {

                            byte[] res = Get16tByteArray(result, i, objModbusRTU.dataFormat);

                            shortString += BitConverter.ToUInt32(res, 0).ToString() + " ";

                        }

                        AddLog(0, "讀取成功,結果為" + uintString.Trim());

                    }

                    else

                    {

                        AddLog(1, "讀取失敗,請檢查地址、類型或連接狀態");

                    }

如果獲取的是Float的寄存器:

byte[] result= objModbusRTU.ReadInputReg(DevAdd, Address, Length*2);

進行數據解析

 string floatString = string.Empty;

                    if (result !=null&&result .Length ==Length *4)

                    {

                        for (int i = 0; i < result.Length; i+=4)

                        {

                            byte[] res = Get16tByteArray(result, i, objModbusRTU.dataFormat);

                            shortString += BitConverter.ToSingle32(res, 0).ToString() + " ";

                        }

                        AddLog(0, "讀取成功,結果為" + floatString.Trim());

                    }

                    else

                    {

                        AddLog(1, "讀取失敗,請檢查地址、類型或連接狀態");

                    }

到此為止就已經將數據獲取到並且轉換為字符可以顯示到文本框中了。

至於其他的01H(讀取輸出線圈) 02H(讀取輸入信號) 04H(讀取輸入寄存器) 05H(寫單個輸出線圈) 06H(寫單個寄存器) 0FH(寫多個輸出線圈) 10H(寫多個寄存器),其中04H和我上面寫的03H非常相似,因為它們的報文除了功能碼之外基本一致。而01H和02H主要需要注意的是它們返回的線圈的值在報文中都是以字節數組形式保存的,我們解析的時候要將其解析為字符數組來一一對應線圈。05H和06H其實它們在0FH和10H中已經分別包含進去了,所以最主要的還是0FH和10H兩個的報文和數據解析,因為一個是多寫後報文的長度會非常的長,再一個就是多寫的時候會存在一個往不同類型的寄存器中去寫值的一個判斷,這個後面再更。

運行測試:

首先使用VSPD虛擬一對串口COM5-COM6,從站使用COM5,主站使用COM6。

隨筆記。

相關焦點

  • Modbus測試小工具
    由於工作需要,碰到很多客戶調試modbus通訊的問題。針對在空調項目中,Modbus RTU通訊測試,這裡發一個modbus測試小工具小教程。1,首先是用什麼工具,工欲善其事必先利其器。1.1 軟體部分這裡推薦兩個小工具:Modbbus poll(傳送門:https://www.modbustools.com/download.html)大小只有2mb的工具。
  • Modbus測試工具ModbusPoll與Modbus Slave使用方法
    用來幫助開發人員測試Modbus從設備,或者其它Modbus協議的測試和仿真。它支持多文檔接口,即,可以同時監視多個從設備/數據域。每個窗口簡單地設定從設備ID,功能,地址,大小和輪詢間隔。你可以從任意一個窗口讀寫寄存器和線圈。如果你想改變一個單獨的寄存器,簡單地雙擊這個值即可。或者你可以改變多個寄存器/線圈值。提供數據的多種格式方式,比如浮點、雙精度、長整型(可以字節序列交換)。
  • 濤哥帶你學習 ModBus TCP 協議 (7) – 調試工具
    編者:Modbus TCP 是比較普及的一種標準通信協議,協議格式簡單易懂,特別適合學習底層通信協議開發。
  • Modbus與第三方系統通訊
    因為第三方系統使用的系統不同,目前比較多的如ABB,施耐德,GE,上海新華,和利時,浙大中控等,為了實現和這些系統做的控制系統,就要有一個比較合理的通訊方式,目前採用的是opc方式通訊。目前的技術需求:而針對一爐一機這種數據量小,要求速度快的通訊特點,modbus通訊方式更合適,modbus是比較早的通訊協議,大多數的智能儀表都支持,控制系統也都支持,所以不存在無法通訊的難點。為了實現這個通訊,我於2014年的年底在江蘇阜寧澳洋熱電廠做了測試。
  • linux開發各種I/O操作簡析,以及select、poll、epoll機制的對比
    多路復用I/O多路復用I/O就是我們說的 select,poll,epoll 等操作,復用的好處就在於 單個進程 就可以同時處理 多個 網絡連接的I/O,能實現這種功能的原理就是 select、poll、epoll 等函數會不斷的 輪詢 它們所負責的所有 socket
  • Modbus和TCP有什麼區別?
    它們的相結合,不但實現了基於modbus/tcp/IP協議遠程網絡通訊,促進了modbus在其對應領域中的使用。modbus協議主要應用於電氣自動化和過程控制,一般採用RS-232或RS-485的通信接口。不足之處,就是其傳輸距離短、速度慢,導致應用受到局限。TCP/IP協議主要應用領域lnternet或lntranet中,它的優勢就是傳輸距離遠、傳輸速度快,應用範圍廣泛。
  • 單片機通信uart和modbus有什麼區別
    modbus簡介   Modbus是由Modicon(現為施耐德電氣公司的一個品牌)在1979年發明的,是全球第一個真正用於工業現場的總線協議。   ModBus網絡是一個工業通信系統,由帶智能終端的可編程序控制器和計算機通過公用線路或局部專用線路連接而成。
  • 西門子博途的MODBUS RTU通訊實例
    二、案例實現項目背景:本項目採用的S7-1200的擴展modbus卡,作為MODBUS RTU的接口,採集電磁流量計的瞬時流量和累計流量。2.1 硬體接線    該流量計設置modbus 地址為004,瞬時流量寄存器地址為4113,長度為2個字,瞬時流量存儲值為DB57.DBD0。(4)輪詢觸發程序,通訊完成下一步
  • 手把手教你寫Modbus-RTU協議(實戰篇)
    好了,下面開始進入我們今天的主題,今天為大家帶來Modbus-RTU協議的實現,作者前面發布過兩篇modbus的理論篇《手把手教你寫Modbus-RTU協議(理論篇)》和《向Modbus協議說"So Easy"!》(可在文末推薦閱讀中跳轉),大家可以先閱讀這兩篇文章掌握一些基本理論。
  • 基於Python的Modbus RTU應用示例
    為何不直接用RS232進行modbus通信呢,因為RS232只支持兩個設備之間一對一的通信,只有將RS232轉換成RS485 才能具備多個設備站點組網的能力,才可以實現遠程多機通信。python代碼中會用到modbus_tk庫,如何安裝modbus_tk請翻看《使用Python操作Ge Fanuc PLC》內容。同時代碼中需要使用到serial模塊,是一個Python操作串行埠的庫,一般安裝python時會默認安裝。
  • 工業控制系統安全之——Modbus學習筆記
    5.3 modbus TCP協議簡述modbus TCP和modbus RTU基本相同,但是也存在一些區別a.從機地址變得不再重要,多數情況下忽略。從某種意義上說從機地址被IP位址取代b.CRC校驗變得不再重要,甚至可以忽略。由於TCP數據包中已經存在校驗,為了不重複造輪子,modbus TCP乾脆取消了CRC校驗。
  • (二十五)深入淺出TCPIP之 epoll和select,poll的區別
    在實際網絡開發中,我們經常會和epoll,select,poll打交道,select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
  • Exit poll?
    Reader question:Please explain 「exit poll」 in this sentence: At least one exit poll has HillaryLiterally, 「exit poll」 means a poll at the exit of the polls.Sorry about the confusion.
  • Modbus 的RTU、ASCII、TCP傻傻搞不清楚?一文最全解讀
    6.1 主機和從機、服務端和客戶端【在modbus協議中】主機發送modbus請求,從機根據請求內容向主機返迴響應。6.3 modbus TCP協議簡述modbus TCP和modbus RTU基本相同,但是也存在一些區別a.從機地址變得不再重要,多數情況下忽略。從某種意義上說從機地址被IP位址取代b.CRC校驗變得不再重要,甚至可以忽略。由於TCP數據包中已經存在校驗,為了不重複造輪子,modbus TCP乾脆取消了CRC校驗。
  • 三菱PLC的另類MODBUS通訊
    考慮到成本問題,不打算更換硬體,最後經過查詢資料和驗證後,最終實現MODBUS協議通訊。以下是大概方法,供各位朋友交流參考,不對之處請指正。      首先要了解下什麼是無協議通訊和MODBUS協議通訊。根據度娘所說:      所謂無協議通訊就是說通信網絡的兩個或多個終端通過通信網絡實現數據的傳輸,而不必遵循共同的規定或規則。
  • 命令行工具開發:如何快速實現命令行提示?
    阿里妹導讀:對於稍微複雜一些的命令行工具,命令行的提示功能必不可少。那麼對於不同語言的開發者,有沒有一種簡單快捷的實現方式呢?本文分享一種快速實現的方法,使用YAML文件定義命令行工具的使用規範,再通過工具自動生成各種shell的命令行提示腳本,最後分享一些至關重要的命令行解析器。
  • (二十四)深入淺出TCPIP之 如何理解poll
    poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制
  • select、pselect和poll函數的區別及用法
    下面我們說一下select、pselect和poll函數的具體用法及區別• select
  • 三菱FX5U通過485接口與IAI電缸進行modbus通訊實例講解
    右側的FX5U是外購設備的控制器,看它的485接口就閒置的,就研究了一下IAI電缸的modbus通訊,下面介紹下怎麼用。PLC編程因為我是IO控制的,接線如下,選擇輸出指令No.1控制器就會走到我示教好的位置1,它是27.23mm的位置主要是靠IO選擇,為了實現方便調試功能,我只要通過modbus通訊實現2個功能就行了。
  • People unhappy with the rich: Poll
    There is growing dissatisfaction toward rich people, according to a new online poll.The poll by the China Youth Daily in collaboration with Sina.com has highlighted the apparent discontent over the country's widening income gap.