前面連續寫了關於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服務在哪里。所以如下代碼就水到渠成了。
- public bool Open(string ip,int port) {
- try {
- tcpClient = new TcpClient();
- tcpClient.Connect(IPAddress.Parse(ip), port);
- return true;
- }catch(SocketException e){
- string m = string.Format("modbus Client服務器連接錯誤:{0},ip:{1},port:{2}", e.Message, ip, port);
- LogHelper.WriteLog(m);
- return false;
- }
- }
其中LogHelper代碼這里就不寫,因為與主題無關,如果想運行上面代碼的話,注釋就行了,我個人推薦這樣的代碼也沒必要去運行一下,看看能明白就行了。去運行這個事情,有事會耽誤太多的時間。這里IP和端口號都是由設備方提供的。真實程序一般都把這兩個參數寫配置文件中。
設備連上以后,下一步當然就是讀取數據。Modbus的基本原理就是程序向設備請求,需要讀取哪個數據,設備就會返回相應的數據。我們知道機器或者說是電腦是只認識01001這樣的字符串的。所以所謂的Modbus協議,說得簡單一點,就是規定這樣一個0101字符各代表什么含義。
- /// <summary>
- /// 讀取數據 Modbus
- /// </summary>
- /// <param name="rData">結果</param>
- /// <param name="id">設備號</param>
- /// <param name="address">設備地址</param>
- /// <param name="len">長度-多少個設備</param>
- /// <returns>數據讀取結果 是否成功</returns>
- public bool ReceiveData(ref short[] rData, short id, short address, short len)
- {
- try
- {
- short m = Convert.ToInt16(new Random().Next(2, 20));
- rData = null;
- byte[] bs = Receive(m, id, address, len);
- byte[] b = TrimModbus(bs, m, id, len);
- if (b==null) { return false; }
- List<short> data = new List<short>(255);
- for (int i = 0; i < b.Length-1; i++)
- {
- if (!Convert.ToBoolean(i & 1))
- {
- byte[] temp = new byte[] { b[i+1], b[i] };
- data.Add(BitConverter.ToInt16(temp, 0));
- }
- }
- rData = data.ToArray();
- return true;
- }
- catch (Exception e) {
- LogHelper.WriteLog("返回Modbus數據錯誤"+ e.Message);
- return false;
- }
- }
這個其實更多的是處理數據異常,LogHelper與前面一樣,核心好像還不在了,就是那個Receive方法。
- /// <summary>
- /// 讀取 Modbus
- ///00 00 00 00 00 0d 01 03 0A 14 00 14 00 14 00 14 00 14 00
- /// </summary>
- /// <param name="m">標示</param>
- /// <param name="id">設備碼</param>
- /// <param name="address">開始地址</param>
- /// <param name="len">設備數量</param>
- /// <returns></returns>
- private byte[] Receive(short m, short id, short address, short len)
- {
- try
- {
- if (tcpClient == null || !tcpClient.Connected) { return null; }
- byte[] data = GetSrcData(m, id, address, len);
- //00 00 00 00 00 06 01 03 00 00 00 05
- tcpClient.Client.Send(data, data.Length, SocketFlags.None);
- int size = len * 2 + 9;
- byte[] rData = new byte[size];
- tcpClient.Client.Receive(rData, size, SocketFlags.None);
- //string t1 = TranBytes(rData);
- return rData;
- }catch(SocketException e){
- if (e.ErrorCode != 10004)
- {
- LogHelper.WriteLog(e.Message);
- }
- if (tcpClient != null) {
- tcpClient.Close();
- tcpClient = null;
- }
- return null;
- }
- }
- #endregion
- //發送
- //00 00 00 00 00 06 01 03 00 00 00 05
- /// <summary>
- /// 發送字節數
- /// </summary>
- /// <param name="m"></param>
- /// <param name="len"></param>
- /// <param name="id"></param>
- /// <param name="address"></param>
- /// <returns></returns>
- private byte[] GetSrcData(short m, short id, short add, short len)
- {
- List<byte> data = new List<byte>(255);
- data.AddRange(ValueHelper.Instance.GetBytes(m)); // 00 01
- data.AddRange(new byte[] { 0x00, 0x00 }); // 00 00
- data.AddRange(ValueHelper.Instance.GetBytes(Convert.ToInt16(6))); //字節數 00 06
- data.Add(Convert.ToByte(id)); //路由碼 01
- data.Add(Convert.ToByte(3)); //功能碼 3-讀 03
- data.AddRange(ValueHelper.Instance.GetBytes(add)); //開始地址 00 00
- data.AddRange(ValueHelper.Instance.GetBytes(len)); //設備數量 00 05
- return data.ToArray();
- }
好,到這里基本上搞定了。其實很多邏輯都在代碼中,說簡單的就是某個ID設備,從哪個地址開始,讀幾個設備的值,這里需要注意是short 不要用32位的int去替換,結果會不一樣的。仔細看看估計大家都能明白,也沒有什么神秘的東西。
哦,對了還ValueHelper代碼
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace Modbus {
- public class ValueHelper
- {
- #region 大小端判斷
- public static bool LittleEndian = false;
- static ValueHelper()
- {
- unsafe
- {
- int tester = 1;
- LittleEndian = (*(byte*)(&tester)) == (byte)1;
- }
- }
- #endregion
- #region Factory
- public static ValueHelper _Instance = null;
- internal static ValueHelper Instance
- {
- get
- {
- if (_Instance == null)
- {
- _Instance = LittleEndian ? new LittleEndianValueHelper() : new ValueHelper();
- //_Instance = new ValueHelper();
- }
- return _Instance;
- }
- }
- #endregion
- protected ValueHelper()
- {
- }
- public virtual Byte[] GetBytes(short value)
- {
- return BitConverter.GetBytes(value);
- }
- public virtual Byte[] GetBytes(int value)
- {
- return BitConverter.GetBytes(value);
- }
- public virtual Byte[] GetBytes(float value)
- {
- return BitConverter.GetBytes(value);
- }
- public virtual Byte[] GetBytes(double value)
- {
- return BitConverter.GetBytes(value);
- }
- public virtual short GetShort(byte[] data)
- {
- return BitConverter.ToInt16(data, 0);
- }
- public virtual int GetInt(byte[] data)
- {
- return BitConverter.ToInt32(data, 0);
- }
- public virtual float GetFloat(byte[] data)
- {
- return BitConverter.ToSingle(data, 0);
- }
- public virtual double GetDouble(byte[] data)
- {
- return BitConverter.ToDouble(data, 0);
- }
- }
- internal class LittleEndianValueHelper : ValueHelper
- {
- public override Byte[] GetBytes(short value)
- {
- return this.Reverse(BitConverter.GetBytes(value));
- }
- public override Byte[] GetBytes(int value)
- {
- return this.Reverse(BitConverter.GetBytes(value));
- }
- public override Byte[] GetBytes(float value)
- {
- return this.Reverse(BitConverter.GetBytes(value));
- }
- public override Byte[] GetBytes(double value)
- {
- return this.Reverse(BitConverter.GetBytes(value));
- }
- public virtual short GetShort(byte[] data)
- {
- return BitConverter.ToInt16(this.Reverse(data), 0);
- }
- public virtual int GetInt(byte[] data)
- {
- return BitConverter.ToInt32(this.Reverse(data), 0);
- }
- public virtual float GetFloat(byte[] data)
- {
- return BitConverter.ToSingle(this.Reverse(data), 0);
- }
- public virtual double GetDouble(byte[] data)
- {
- return BitConverter.ToDouble(this.Reverse(data), 0);
- }
- private Byte[] Reverse(Byte[] data)
- {
- Array.Reverse(data);
- return data;
- }
- }
- }
不好意思,有個方法給忘記了
- private byte[] TrimModbus(byte[] d, short m, short id, short len)
- {
- int size = Convert.ToInt32(len) * 2;
- int dLen = size + 9;
- if (d == null || d.Length != dLen || m != Convert.ToInt16(d[1]) || id != Convert.ToInt16(d[6]))
- {
- return null;
- }
- byte[] n = new byte[size];
- Array.Copy(d, 9, n, 0, size);
- return n;
- }
