C#讀取Modbus數據


前面連續寫了關於SOCKET編程的東西,似乎有點高大上,為了學習而學習。因此這里我們來整點實際應用的東西。C#如何讀取Modbus數據,Modbus很多人可能一點都不知道,也正常,隔行如隔山嘛。Modbus在自動化行業就不一樣,屬於路人皆知的東西,很多設備、程序都與Modbus息息相關。

        Modbus這個東西,本人也是個二把刀,只有半瓶水,所以在這里晃盪,寫點Modbus東西,也是讓自己能理解得更深一點,入門級別的東西,希望能幫助到那些像我一樣不太了解Modbus,但是又想了解Modbus的同學。

        至於高手,可以吐槽,當然最好是直接繞過。

        閑話少說,書歸正傳。何謂Modbus

        Modbus通訊協議,ModBus網絡是一個工業通信系統,由帶智能終端的 可編程序控制器和計算機通過公用線路或局部專用線路連接而成。其 系統結構既包括硬件、亦包括 軟件。它可應用於各種數據采集和過程監控。
      Modbus通訊協議是施耐德電氣公司......
        Modbus 協議是應用於電子控制器上的一種通用語言。通過此協議,控制器相互之間、控制器經由網絡(例如以太網)和其它設備之間可以通信。它已經成為一通用工業標准。有了它,不同廠商生產的控制設備可以連成工業網絡,進行集中監控......
      這些都是百度來的,呵呵,別吐槽。網上資料很多,有興趣的可以自己擼,看多了感覺好像都差不多。
      個人認為所謂的通訊協議,機器、設備或者程序之間相互通訊的一種方式。人與人交流不也有不同的語言或者文字嘛,機器之間當然可以相互發送信息,只要定好規則即可。而Modbus就是與自動化設備用來交流的語言。
      這些理解了也好,不明白其實也不要緊。因為Modbus對於我們碼農來說,我們可以大大的縮小理解范圍,我們只需要關心與編碼有關系東西,對於硬件的那些什么針腳、電纜、信號位都不用太關心,當然如果想成為為一個Modbus方面的專家,那就不同了。
     Modbus分兩種模式,一種串口模式,一種是TCP/IP模式。串口模式感覺越來越少,現在大多都是TCP/IP模式,所以這里就暫時寫TCP/IP模式
     首先要做就是SOCKET客戶端與設備建立連接,因為前面的文章中,關於SOCKET,我們已經說過了,那么下面的代碼就非常easy了。說簡單點,先不要去想什么Modbus,就認為有一台服務器,開了SOCKET服務在哪里。所以如下代碼就水到渠成了。
   
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public bool Open(string ip,int port) {  
  2.            try {  
  3.                tcpClient = new TcpClient();  
  4.   
  5.                tcpClient.Connect(IPAddress.Parse(ip), port);  
  6.   
  7.                return true;  
  8.            }catch(SocketException e){  
  9.                string m = string.Format("modbus Client服務器連接錯誤:{0},ip:{1},port:{2}", e.Message, ip, port);  
  10.                LogHelper.WriteLog(m);  
  11.                return false;  
  12.            }  
  13.        }  
         其中LogHelper代碼這里就不寫,因為與主題無關,如果想運行上面代碼的話,注釋就行了,我個人推薦這樣的代碼也沒必要去運行一下,看看能明白就行了。去運行這個事情,有事會耽誤太多的時間。這里IP和端口號都是由設備方提供的。真實程序一般都把這兩個參數寫配置文件中。
       設備連上以后,下一步當然就是讀取數據。Modbus的基本原理就是程序向設備請求,需要讀取哪個數據,設備就會返回相應的數據。我們知道機器或者說是電腦是只認識01001這樣的字符串的。所以所謂的Modbus協議,說得簡單一點,就是規定這樣一個0101字符各代表什么含義。
    
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /// <summary>  
  2.         /// 讀取數據 Modbus  
  3.         /// </summary>  
  4.         /// <param name="rData">結果</param>  
  5.         /// <param name="id">設備號</param>  
  6.         /// <param name="address">設備地址</param>  
  7.         /// <param name="len">長度-多少個設備</param>  
  8.         /// <returns>數據讀取結果 是否成功</returns>  
  9.         public bool ReceiveData(ref short[] rData, short id, short address, short len)  
  10.         {  
  11.             try  
  12.             {  
  13.                 short m = Convert.ToInt16(new Random().Next(2, 20));  
  14.                 rData = null;  
  15.   
  16.                 byte[] bs = Receive(m, id, address, len);  
  17.                 byte[] b = TrimModbus(bs, m, id, len);  
  18.   
  19.                 if (b==null) { return false; }  
  20.   
  21.                 List<short> data = new List<short>(255);  
  22.                 for (int i = 0; i < b.Length-1; i++)  
  23.                 {  
  24.                    if (!Convert.ToBoolean(i & 1))  
  25.                    {  
  26.                         byte[] temp = new byte[] { b[i+1], b[i] };  
  27.                         data.Add(BitConverter.ToInt16(temp, 0));  
  28.                    }  
  29.                 }  
  30.                 rData = data.ToArray();  
  31.   
  32.                 return true;  
  33.             }  
  34.             catch (Exception e) {  
  35.                 LogHelper.WriteLog("返回Modbus數據錯誤"+ e.Message);  
  36.                 return false;  
  37.             }  
  38.         }  
這個其實更多的是處理數據異常,LogHelper與前面一樣,核心好像還不在了,就是那個Receive方法。
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /// <summary>  
  2. /// 讀取 Modbus  
  3. ///00 00 00 00 00 0d  01  03  0A 14 00  14 00  14 00  14 00  14 00  
  4. /// </summary>  
  5. /// <param name="m">標示</param>  
  6. /// <param name="id">設備碼</param>  
  7. /// <param name="address">開始地址</param>  
  8. /// <param name="len">設備數量</param>  
  9. /// <returns></returns>  
  10. private byte[] Receive(short m, short id, short address, short len)  
  11. {  
  12.     try  
  13.     {  
  14.         if (tcpClient == null || !tcpClient.Connected) { return null; }  
  15.   
  16.         byte[] data = GetSrcData(m, id, address, len);  
  17.           
  18.         //00 00 00 00 00 06 01 03 00 00 00 05  
  19.         tcpClient.Client.Send(data, data.Length, SocketFlags.None);  
  20.   
  21.         int size = len * 2 + 9;  
  22.   
  23.         byte[] rData = new byte[size];  
  24.   
  25.         tcpClient.Client.Receive(rData, size, SocketFlags.None);  
  26.   
  27.         //string t1 = TranBytes(rData);  
  28.   
  29.         return rData;  
  30.   
  31.     }catch(SocketException e){  
  32.         if (e.ErrorCode != 10004)  
  33.         {  
  34.             LogHelper.WriteLog(e.Message);  
  35.         }  
  36.   
  37.         if (tcpClient != null) {  
  38.             tcpClient.Close();  
  39.             tcpClient = null;  
  40.         }  
  41.   
  42.         return null;  
  43.     }  
  44. }  
  45. #endregion  
上面的代碼可以說是Modbus協議核心,其實就是SOCKET發送數據和接受數據,發送是告訴主機需要取那些的數據。接受就是把主機返回來的數據接受過來。
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. //發送  
  2.         //00 00 00 00 00 06 01 03 00 00 00 05  
  3.         /// <summary>  
  4.         /// 發送字節數  
  5.         /// </summary>  
  6.         /// <param name="m"></param>  
  7.         /// <param name="len"></param>  
  8.         /// <param name="id"></param>  
  9.         /// <param name="address"></param>  
  10.         /// <returns></returns>  
  11.         private byte[] GetSrcData(short m, short id, short add, short len)  
  12.         {  
  13.             List<byte> data = new List<byte>(255);  
  14.   
  15.             data.AddRange(ValueHelper.Instance.GetBytes(m));                     //             00 01  
  16.             data.AddRange(new byte[] { 0x00, 0x00 });                            //             00 00  
  17.             data.AddRange(ValueHelper.Instance.GetBytes(Convert.ToInt16(6)));    //字節數       00 06  
  18.             data.Add(Convert.ToByte(id));                                        //路由碼       01  
  19.             data.Add(Convert.ToByte(3));                                         //功能碼 3-讀  03  
  20.             data.AddRange(ValueHelper.Instance.GetBytes(add));                   //開始地址     00 00  
  21.             data.AddRange(ValueHelper.Instance.GetBytes(len));                   //設備數量     00 05  
  22.             return data.ToArray();  
  23.         }  
 好,到這里基本上搞定了。其實很多邏輯都在代碼中,說簡單的就是某個ID設備,從哪個地址開始,讀幾個設備的值,這里需要注意是short 不要用32位的int去替換,結果會不一樣的。仔細看看估計大家都能明白,也沒有什么神秘的東西。
哦,對了還ValueHelper代碼
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.   
  5. namespace Modbus {  
  6.     public class ValueHelper  
  7.     {  
  8.         #region 大小端判斷  
  9.   
  10.         public static bool LittleEndian = false;  
  11.   
  12.         static ValueHelper()  
  13.         {  
  14.             unsafe  
  15.             {  
  16.                 int tester = 1;  
  17.                 LittleEndian = (*(byte*)(&tester)) == (byte)1;  
  18.             }  
  19.         }  
  20.         #endregion  
  21.  
  22.         #region Factory  
  23.         public static ValueHelper _Instance = null;  
  24.         internal static ValueHelper Instance  
  25.         {  
  26.             get  
  27.             {  
  28.                 if (_Instance == null)  
  29.                 {  
  30.                     _Instance = LittleEndian ? new LittleEndianValueHelper() : new ValueHelper();  
  31.                     //_Instance = new ValueHelper();  
  32.                 }  
  33.                 return _Instance;  
  34.             }  
  35.         }  
  36.         #endregion  
  37.   
  38.         protected ValueHelper()  
  39.         {  
  40.   
  41.         }  
  42.   
  43.         public virtual Byte[] GetBytes(short value)  
  44.         {  
  45.             return BitConverter.GetBytes(value);  
  46.         }  
  47.   
  48.         public virtual Byte[] GetBytes(int value)  
  49.         {  
  50.             return BitConverter.GetBytes(value);  
  51.         }  
  52.   
  53.         public virtual Byte[] GetBytes(float value)  
  54.         {  
  55.             return BitConverter.GetBytes(value);  
  56.         }  
  57.   
  58.         public virtual Byte[] GetBytes(double value)  
  59.         {  
  60.             return BitConverter.GetBytes(value);  
  61.         }  
  62.   
  63.         public virtual short GetShort(byte[] data)  
  64.         {  
  65.             return BitConverter.ToInt16(data, 0);  
  66.         }  
  67.   
  68.         public virtual int GetInt(byte[] data)  
  69.         {  
  70.             return BitConverter.ToInt32(data, 0);  
  71.         }  
  72.   
  73.         public virtual float GetFloat(byte[] data)  
  74.         {  
  75.             return BitConverter.ToSingle(data, 0);  
  76.         }  
  77.   
  78.         public virtual double GetDouble(byte[] data)  
  79.         {  
  80.             return BitConverter.ToDouble(data, 0);  
  81.         }  
  82.     }  
  83.   
  84.     internal class LittleEndianValueHelper : ValueHelper  
  85.     {  
  86.         public override Byte[] GetBytes(short value)  
  87.         {  
  88.             return this.Reverse(BitConverter.GetBytes(value));  
  89.         }  
  90.   
  91.         public override Byte[] GetBytes(int value)  
  92.         {  
  93.             return this.Reverse(BitConverter.GetBytes(value));  
  94.         }  
  95.   
  96.         public override Byte[] GetBytes(float value)  
  97.         {  
  98.             return this.Reverse(BitConverter.GetBytes(value));  
  99.         }  
  100.   
  101.         public override Byte[] GetBytes(double value)  
  102.         {  
  103.             return this.Reverse(BitConverter.GetBytes(value));  
  104.         }  
  105.   
  106.         public virtual short GetShort(byte[] data)  
  107.         {  
  108.             return BitConverter.ToInt16(this.Reverse(data), 0);  
  109.         }  
  110.   
  111.         public virtual int GetInt(byte[] data)  
  112.         {  
  113.             return BitConverter.ToInt32(this.Reverse(data), 0);  
  114.         }  
  115.   
  116.         public virtual float GetFloat(byte[] data)  
  117.         {  
  118.             return BitConverter.ToSingle(this.Reverse(data), 0);  
  119.         }  
  120.   
  121.         public virtual double GetDouble(byte[] data)  
  122.         {  
  123.             return BitConverter.ToDouble(this.Reverse(data), 0);  
  124.         }  
  125.   
  126.         private Byte[] Reverse(Byte[] data)  
  127.         {  
  128.             Array.Reverse(data);  
  129.             return data;  
  130.         }  
  131.     }  
  132. }  
不好意思,有個方法給忘記了
[csharp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. private byte[] TrimModbus(byte[] d, short m, short id, short len)  
  2.        {  
  3.            int size = Convert.ToInt32(len) * 2;  
  4.            int dLen = size + 9;  
  5.   
  6.            if (d == null || d.Length != dLen || m != Convert.ToInt16(d[1]) || id != Convert.ToInt16(d[6]))  
  7.            {  
  8.                return null;  
  9.            }  
  10.            byte[] n = new byte[size];  
  11.            Array.Copy(d, 9, n, 0, size);  
  12.            return n;  
  13.        }  
代碼都貼大概都貼出來,但是如果實在想運行的,也是需要簡單整理的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM