標籤:lan 輸入 api strong 分享圖片 第一個 支援人員 logs 用戶端程式
前言:
之前的部落格介紹了如何用C#來讀寫modbus tcp伺服器的資料,文章:http://www.cnblogs.com/dathlin/p/7885368.html
當然也有如何建立一個伺服器文章:http://www.cnblogs.com/dathlin/p/7782315.html
但是上面的兩篇文章是已經封裝好的API,只要調用就可以實現功能了,對於想瞭解modbus tcp的原理的人可能就不適合了,最近有不少網友的想要瞭解這個協議,所以在這裡再寫一篇介紹Modbus tcp的文章,不過這篇文章是簡易版本的,未來我再研究深入的話,再開一篇進階版,在簡易版中,就略去了成功標誌位及其他資料標誌,這些到等到後面再說。
先分享一下,我自己學習的地址來源:http://blog.csdn.net/thebestleo/article/details/52269999 聲明:本文並非轉載,並非照搬原文章,是在我參照原部落格的基礎上,理解了基本的modbus通訊,並結合自己的理解,重新寫一篇更好入門的文章,此處貼出原作者的文章以示尊重智慧財產權,原文章有些地方有一點錯誤,而且早就停止更新了,也沒有提供方便的測試載入器,官方的modbus 測試載入器是實驗版本的,需要購買序號才可以,所以此處提供我自己的測試載入器,地址如下,下面的介紹的例子都是基於這個工具來實現的。
https://github.com/dathlin/ModBusTcpTools/raw/master/download/download.zip
關於該測試載入器也是開放原始碼的,如果想要查看原始碼:https://github.com/dathlin/ModBusTcpTools
支援人員QQ群:592132877
準備條件:
在上面的測試載入器下載之前,需要一些額外的知識補充,此處不管你是學習什麼語言的,對於socket通訊層來說,其實是一樣的,下面的講解的內容是直接基於底層的,無關文法的操作。
但是需要你對位元組概念非常清晰,一般都是byte數組,一個byte有8個位,這個也要非常的清晰,如果連byte是什麼都搞不清楚,那麼對本文下面的內容理解會非常的吃力,那麼還是建議你再看看電腦原理這些書,對於socket通訊,每種語言都有不同的寫法,但是所有的語言都有兩個共同點,都能實現把資料發送到socket和從socket接收資料,至於這個如果去做,就參照你自己需要使用的語言了,此處不做這方面的說明了。
關於十六進位文本,在本文的下面的內容上,所有的byte位元組數組都表示成十六進位形式,比如 FF 10 代表2個位元組,一個是255,另一個是16。
byte[] temp = new byte[2];temp[0] = 0xFF;temp[1] = 0x10;
如果將上述的temp看作是讀取到的線圈的資料,那麼轉換規則如下:
先將上述資料轉化成二進位 : 1111 1111 (第一個byte,我們從高位寫到地位) 0001 0000 (第二個byte,我們從高位寫到地位)
對應的線圈就是,線圈7-線圈0,,,,第二個byte對應的線圈是,線圈15-線圈8 這裡一定要好好理解,從byte上來說,temp[0]是地位,temp[1]是高位,深入到每個byte裡面的二進位,高位在前,低位在後。
在C#裡等同於下面的代碼,和C語言,java也是非常的相近,還算比較好理解。
如果我說,發送 00 00 00 00 00 06 FF 01 00 00 00 01 到socket上去,那麼也就是:
byte[] temp = new temp[12];temp[0] = 0x00;temp[1] = 0x00;temp[2] = 0x00;temp[3] = 0x00;temp[4] = 0x00;temp[5] = 0x06;temp[6] = 0xFF;temp[7] = 0x01;temp[8] = 0x00;temp[9] = 0x00;temp[10] = 0x00;temp[11] = 0x01;socket.Send(temp);
先不要管上面的資料是什麼含義,知道上面的代碼是啥含義就行了。接下來就是下載上面的測試載入器,開始真正的學習modbus tcp協議了!
測試載入器初始化
先運行Server.exe檔案,連接埠裡輸入502,然後點擊啟動服務即可,如下:
然後運行Client.exe程式,在Ip地址裡輸入127.0.0.1,連接埠裡輸入502,點擊配置即可,我們看到,如果你的伺服器程式運行在了別的電腦上,甚至是雲端,只要用戶端的ip修改成伺服器的ip,連接埠號碼對應上,就可以訪問到伺服器的資料了。
特殊測試不用去管,和我們現在學習的東西不一致。
功能碼詳細解釋
對於modbus來說,涉及的功能碼也就是0x01,0x02,0x03,0x05,0x06,0x0F,0x10了,其實分類來說,就只有兩種,線圈和寄存器,也就是位讀寫和字讀寫,首先需要清楚的是功能碼不一樣,對應資料的解析規則也不一樣,下面就針對不同的情況來說。
首先說明的是,modbus協議呢,最終目的還是為了實現資料互動,既然是資料互動,那就是包含了資料讀和寫,我們把我們的想法轉化成一串資料,發送給裝置(或者叫伺服器),它返回一串資料,根據規則解析出來,這樣就得到了我們真正想要的資料。下面就來第一個想法實現吧。
另外,在modbus伺服器端,資料是使用地址的方式來公開的,這很好理解,伺服器端儲存了很多資料,你想要訪問某個資料肯定需要指定唯一的身份標識,從連續的地址來區分資料是最常用的做法,不僅好理解,還便於擴充,比如你還可以讀取連續地址的資料區塊。如果採用字串名字來標識資料,就沒有這個特點。
對於位操作來說(各種線圈和離散量),一個地址代表了一個bool變數,即 0 和 1,要麼通要麼斷,就好比一些普通的開關。
對於寄存器來說,一個地址代表了2個byte,共有65536種方式,可以滿足大多數日常使用了,比如我們讀取地址0的寄存器,返回 00 00 及代表寄存器0資料為0,如果返回 01 00 ,那麼代表寄存器0資料為 256
功能碼0x01:
我不直接上一串資料,這樣看著也累,我們從例子出發,現在我們需要讀取線圈(離散量)操作,我想讀取地址0的線圈是否是通還是斷的。我們有了這個功能需求後,就可以根據需求來寫出特殊的指令了。根據協議指定,需要填寫長度為12的byte數組
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11]
byte[0] byte[1] : 訊息編號---------隨便指定,伺服器返回的資料的前兩個字和這個一樣
byte[2] byte[3] :modbus標識,強製為0即可
byte[4] byte[5] :指示排在byte[5]後面所有位元組的個數,也就是總長度-6
byte[6]: 站號,隨便指定,00 -- FF 都可以
byte[7] :功能碼,這裡就需要填入我們的真正的想法了
byte[8] byte[9] :起始地址,比如我們想讀取地址0的資料,就填 00 00 ,如果我們想讀取地址1000的資料,怎麼辦,填入 03 E8 ,也就是將1000轉化十六進位填進去。
byte[10] byte[11] :指定想讀取的資料長度,比如我們就想讀取地址0的一個資料,這裡就寫 00 01,如果我們想讀取地址0-999共計一個資料的長度,就寫 03 E8。和起始地址是一樣的。
有了上面的格式之後,接下來我們就按照格式來填寫資料吧,我們需要讀取地址0的資料,那麼指定如下
00 00 00 00 00 06 FF 01 00 00 00 01
訊息編號設為0,站號FF,功能碼01,地址01,長度01:將上面的指令在用戶端程式裡進行輸入,點擊發送,這樣就在下面的響應框裡接收到伺服器反饋的資料,我們最終需要的資訊就在反饋的資料裡了。
前面是接收到資料的時間,自動忽略,那麼返回的資料就是 00 00 00 00 00 04 FF 01 01 00 共計10個位元組的資料,ok,這玩意到底是什麼意思呢,我們來分別解析下:
byte[0] byte[1] : 訊息編號,我們之前寫發送指令的時候,是多少,這裡就是多少。
byte[2] byte[3]:必須都為0,代表這是modbus 通訊
byte[4] byte[5]:指示byte[5]後面的所有位元組數,你數數看是不是4個?所以這裡是00 04,如果後面共有100個,那麼這裡就是 00 64
byte[6]:站號,之前我們寫了FF,那麼這裡也就是FF
byte[7]:功能碼,我們之前寫了01的功能碼,這裡也是01,和我們發送的指令是一致的
byte[8]:指示byte[8]後面跟隨的位元組數量,因為跟在byte[8]後面的就是真實的資料,我們最終想要的結果就在byte[8]後面
byte[9]:真實的資料,哈哈,這肯定就是我們真正想要的東西了,我們知道一個byte有8位,但是我們唯讀取了一個位元據,所有這裡的有效值只是byte[9]的最低位,二進位為 0000 0000 我們看到最低位為0,所以最終我們讀取的地址0的線圈為斷。
假設我們讀取地址10,開始的共10個線圈呢,那麼會返回什嗎?所以我們發送 00 00 00 00 00 06 FF 01 00 0A 00 0A
我們接收到了:00 00 00 00 00 05 FF 01 02 00 00 前面的8個位元組的資訊參照上面的分析,是一致的,我們就針對後面三個位元組著重分析。我們讀取了10個位,那麼一個位元組可以表示8個位,那麼我們的結果至少需要2個byte才能表示完,所以最終的資料肯定是2個位元組,那麼02就是後面的位元組數量,也就是真實的資料長度。
最後2個位元組就是我們最終的想要的資料了!接下來就是怎麼去解析了,我們還是需要先寫成二進位先,才能一個一個分析:00 00 二進位如下
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
線圈17 線圈16 線圈15 線圈14 線圈13 線圈12 線圈11 線圈10 無效 無效 無效 無效 無效 無效 線圈19 線圈18
至此我們擷取到了我們最終的資料!因為此處伺服器都是0,所以所有的線圈都是斷,等會可以結合05功能碼寫線圈進行聯合測試。
功能碼0x02:
這個功能碼和上面的一致,在本伺服器裡不支援這個功能碼。發送和解析規則和上面的一致,不再贅述。
功能碼0x05:
我們先講解05功能碼,這個功能碼是實現資料寫入,它能實現什麼功能呢,我們可以利用這個功能碼來指定某個線圈通或斷,具體怎麼操作呢,有了之前01功能碼的經驗,下面的代碼看起來就順利多了。
比如我要指定地址0的寄存器為通: 00 00 00 00 00 06 FF 05 00 00 FF 00 前面的含義都是一致的,我們就分析 05 00 00 FF 00
05 是功能碼, 00 00 是我們指定的地址,如果我們想寫地址1000為通,那麼就為 03 E8,至於FF 00是規定的資料,如果你想地址線圈通,就填這個值,想指定線圈為斷,就填 00 00 ,其他任何的值都對結果無效。
然後我們看看寫入的動作伺服器返回了什麼 ? 我們看到也是 00 00 00 00 00 06 FF 05 00 00 FF 00 因為在你寫入的操作中,是不帶讀取資料的,所以伺服器會直接複製一遍你的指令並返回。
下面再舉例一些方便理解(我們只需要指定地址及是否通斷的情況即可):
寫入地址100為通: 00 00 00 00 00 06 FF 05 00 64 FF 00
寫入地址1000為斷:00 00 00 00 00 06 FF 05 03 E8 00 00
功能碼0x0F:
未完待續,,,
Modbus tcp 格式說明 通訊機制 附C#測試載入器用於學習,測試