日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

modbus tcp 入门详解

發(fā)布時間:2024/1/17 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 modbus tcp 入门详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Modbus tcp 格式說明 通訊機制 附C#測試工具用于學習,測試

前言:


?之前的博客介紹了如何用C#來讀寫modbus tcp服務(wù)器的數(shù)據(jù),文章:http://www.cnblogs.com/dathlin/p/7885368.html

當然也有如何創(chuàng)建一個服務(wù)器文章:http://www.cnblogs.com/dathlin/p/7782315.html

但是上面的兩篇文章是已經(jīng)封裝好的API,只要調(diào)用就可以實現(xiàn)功能了,對于想了解modbus tcp的原理的人可能就不適合了,最近有不少網(wǎng)友的想要了解這個協(xié)議,所以在這里再寫一篇介紹Modbus tcp的文章,不過這篇文章是簡易版本的,未來我再研究深入的話,再開一篇高級版,在簡易版中,就略去了成功標志位及其他數(shù)據(jù)標志,這些到等到后面再說。

先分享一下,我自己學習的地址來源:http://blog.csdn.net/thebestleo/article/details/52269999? 聲明:本文并非轉(zhuǎn)載,并非照搬原文章,是在我參照原博客的基礎(chǔ)上,理解了基本的modbus通訊,并結(jié)合自己的理解,重新寫一篇更好入門的文章,此處貼出原作者的帖子以示尊重知識產(chǎn)權(quán),原文章有些地方有一點錯誤,而且早就停止更新了,也沒有提供方便的測試工具,官方的modbus 測試工具是試驗版本的,需要購買序列號才可以,所以此處提供我自己的測試工具,地址如下,下面的介紹的例子都是基于這個工具來實現(xiàn)的。

https://github.com/dathlin/ModBusTcpTools/raw/master/download/download.zip

關(guān)于該測試工具也是開放源代碼的,如果想要查看源代碼:https://github.com/dathlin/ModBusTcpTools

技術(shù)支持QQ群:592132877

?

準備條件:


在上面的測試工具下載之前,需要一些額外的知識補充,此處不管你是學習什么語言的,對于socket通信層來說,其實是一樣的,下面的講解的內(nèi)容是直接基于底層的,無關(guān)語法的操作。

但是需要你對字節(jié)概念非常清晰,一般都是byte數(shù)組,一個byte有8個位,這個也要非常的清晰,如果連byte是什么都搞不清楚,那么對本文下面的內(nèi)容理解會非常的吃力,那么還是建議你再看看計算機原理這些書,對于socket通信,每種語言都有不同的寫法,但是所有的語言都有兩個共同點,都能實現(xiàn)把數(shù)據(jù)發(fā)送到socket和從socket接收數(shù)據(jù),至于這個如果去做,就參照你自己需要使用的語言了,此處不做這方面的說明了。

關(guān)于十六進制文本,在本文的下面的內(nèi)容上,所有的byte字節(jié)數(shù)組都表示成十六進制形式,比如 FF 10 代表2個字節(jié),一個是255,另一個是16。

1 2 3 byte[] temp =?new?byte[2]; temp[0] = 0xFF; temp[1] = 0x10;

如果將上述的temp看作是讀取到的線圈的數(shù)據(jù),那么轉(zhuǎn)換規(guī)則如下:

先將上述數(shù)據(jù)轉(zhuǎn)化成二進制 :?1111 1111????(第一個byte,我們從高位寫到地位)??????????? ?0001 0000?? (第二個byte,我們從高位寫到地位)

對應(yīng)的線圈就是,線圈7-線圈0,,,,第二個byte對應(yīng)的線圈是,線圈15-線圈8?????這里一定要好好理解,從byte上來說,temp[0]是地位,temp[1]是高位,深入到每個byte里面的二進制,高位在前,低位在后。

在C#里等同于下面的代碼,和C語言,java也是非常的相近,還算比較好理解。

如果我說,發(fā)送 00 00 00 00 00 06 FF 01 00 00 00 01 到socket上去,那么也就是:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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);

先不要管上面的數(shù)據(jù)是什么含義,知道上面的代碼是啥含義就行了。接下來就是下載上面的測試工具,開始真正的學習modbus tcp協(xié)議了!

?

測試工具初始化


先運行Server.exe文件,端口里輸入502,然后點擊啟動服務(wù)即可,如下:

然后運行Client.exe程序,在Ip地址里輸入127.0.0.1,端口里輸入502,點擊配置即可,我們看到,如果你的服務(wù)器程序運行在了別的電腦上,甚至是云端,只要客戶端的ip修改成服務(wù)器的ip,端口號對應(yīng)上,就可以訪問到服務(wù)器的數(shù)據(jù)了。

特殊測試不用去管,和我們現(xiàn)在學習的東西不一致。

?

功能碼詳細解釋


對于modbus來說,涉及的功能碼也就是0x01,0x02,0x03,0x05,0x06,0x0F,0x10了,其實分類來說,就只有兩種,線圈和寄存器,也就是位讀寫和字讀寫,首先需要清楚的是功能碼不一樣,對應(yīng)數(shù)據(jù)的解析規(guī)則也不一樣,下面就針對不同的情況來說。

首先說明的是,modbus協(xié)議呢,最終目的還是為了實現(xiàn)數(shù)據(jù)交互,既然是數(shù)據(jù)交互,那就是包含了數(shù)據(jù)讀和寫,我們把我們的想法轉(zhuǎn)化成一串數(shù)據(jù),發(fā)送給設(shè)備(或者叫服務(wù)器),它返回一串數(shù)據(jù),根據(jù)規(guī)則解析出來,這樣就得到了我們真正想要的數(shù)據(jù)。下面就來第一個想法實現(xiàn)吧。

另外,在modbus服務(wù)器端,數(shù)據(jù)是使用地址的方式來公開的,這很好理解,服務(wù)器端保存了很多數(shù)據(jù),你想要訪問某個數(shù)據(jù)肯定需要指定唯一的身份標識,從連續(xù)的地址來區(qū)分數(shù)據(jù)是最常用的做法,不僅好理解,還便于擴展,比如你還可以讀取連續(xù)地址的數(shù)據(jù)塊。如果采用字符串名字來標識數(shù)據(jù),就沒有這個特點。

對于位操作來說(各種線圈和離散量),一個地址代表了一個bool變量,即 0 和 1,要么通要么斷,就好比一些普通的開關(guān)。

對于寄存器來說,一個地址代表了2個byte,共有65536種方式,可以滿足大多數(shù)日常使用了,比如我們讀取地址0的寄存器,返回 00 00 及代表寄存器0數(shù)據(jù)為0,如果返回 01 00 ,那么代表寄存器0數(shù)據(jù)為 256?

?

功能碼0x01:


我不直接上一串數(shù)據(jù),這樣看著也累,我們從例子出發(fā),現(xiàn)在我們需要讀取線圈(離散量)操作,我想讀取地址0的線圈是否是通還是斷的。我們有了這個功能需求后,就可以根據(jù)需求來寫出特殊的指令了。根據(jù)協(xié)議指定,需要填寫長度為12的byte數(shù)組

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]???????????? : 消息號---------隨便指定,服務(wù)器返回的數(shù)據(jù)的前兩個字和這個一樣

byte[2]?? byte[3]????????????? :modbus標識,強制為0即可

byte[4]???byte[5]????????????? ?:指示排在byte[5]后面所有字節(jié)的個數(shù),也就是總長度-6

byte[6]:?????????????????????? 站號,隨便指定,00? -- FF 都可以

byte[7] :???????????????????? 功能碼,這里就需要填入我們的真正的想法了

byte[8]? byte[9]?????????????? :起始地址,比如我們想讀取地址0的數(shù)據(jù),就填 00 00 ,如果我們想讀取地址1000的數(shù)據(jù),怎么辦,填入 03 E8 ,也就是將1000轉(zhuǎn)化十六進制填進去。

byte[10]? byte[11]????????????? :指定想讀取的數(shù)據(jù)長度,比如我們就想讀取地址0的一個數(shù)據(jù),這里就寫 00 01,如果我們想讀取地址0-999共計一個數(shù)據(jù)的長度,就寫 03 E8。和起始地址是一樣的。

?

有了上面的格式之后,接下來我們就按照格式來填寫數(shù)據(jù)吧,我們需要讀取地址0的數(shù)據(jù),那么指定如下

00 00 00 00 00 06 FF 01 00 00 00 01

消息號設(shè)為0,站號FF,功能碼01,地址01,長度01:將上面的指令在客戶端程序里進行輸入,點擊發(fā)送,這樣就在下面的響應(yīng)框里接收到服務(wù)器反饋的數(shù)據(jù),我們最終需要的信息就在反饋的數(shù)據(jù)里了。

前面是接收到數(shù)據(jù)的時間,自動忽略,那么返回的數(shù)據(jù)就是 00 00 00 00 00 04 FF 01 01 00 共計10個字節(jié)的數(shù)據(jù),ok,這玩意到底是什么意思呢,我們來分別解析下:

byte[0]? byte[1] :???????????? 消息號,我們之前寫發(fā)送指令的時候,是多少,這里就是多少。

byte[2]? byte[3]:?????????? 必須都為0,代表這是modbus 通信

byte[4]? byte[5]:?????????? 指示byte[5]后面的所有字節(jié)數(shù),你數(shù)數(shù)看是不是4個?所以這里是00 04,如果后面共有100個,那么這里就是 00 64

byte[6]:??????????????????????? 站號,之前我們寫了FF,那么這里也就是FF

byte[7]:??????????????????????? 功能碼,我們之前寫了01的功能碼,這里也是01,和我們發(fā)送的指令是一致的

byte[8]:??????????????????????? 指示byte[8]后面跟隨的字節(jié)數(shù)量,因為跟在byte[8]后面的就是真實的數(shù)據(jù),我們最終想要的結(jié)果就在byte[8]后面

byte[9]:??????????????????????? 真實的數(shù)據(jù),哈哈,這肯定就是我們真正想要的東西了,我們知道一個byte有8位,但是我們只讀取了一個位數(shù)據(jù),所有這里的有效值只是byte[9]的最低位,二進制為 0000 0000 我們看到最低位為0,所以最終我們讀取的地址0的線圈為斷。

?

假設(shè)我們讀取地址10,開始的共10個線圈呢,那么會返回什么?所以我們發(fā)送 00 00 00 00 00 06 FF 01 00 0A 00 0A

我們接收到了:00 00 00 00 00 05 FF 01 02 79 01????? 前面的8個字節(jié)的信息參照上面的分析,是一致的,我們就針對后面三個字節(jié)著重分析。我們讀取了10個位,那么一個字節(jié)可以表示8個位,那么我們的結(jié)果至少需要2個byte才能表示完,所以最終的數(shù)據(jù)肯定是2個字節(jié),那么02就是后面的字節(jié)數(shù)量,也就是真實的數(shù)據(jù)長度。

要想從 79 01 數(shù)據(jù)中分析出我們真實想要的數(shù)據(jù),還需要經(jīng)過最后一次數(shù)據(jù)轉(zhuǎn)換。先轉(zhuǎn)為二進制:

0111 1001???????? ? 0000 0001

第二步:按每八位進行分割,上述其實已經(jīng)分割好了,中間空格多的是分割,以字為單位,將二進制順序顛倒:

1001 1110???????? 1000000

第三步:最終數(shù)據(jù)就是??? 線圈10-線圈19的通斷情況是:通,斷,斷,通,通,通,通,斷,通,斷???? 再后面的0都是無效的

至此我們獲取到了我們最終的數(shù)據(jù)!因為此處服務(wù)器都是0,所以所有的線圈都是斷,等會可以結(jié)合05功能碼寫線圈進行聯(lián)合測試。

?

功能碼0x02:


這個功能碼和上面的一致,在本服務(wù)器里不支持這個功能碼。發(fā)送和解析規(guī)則和上面的一致,不再贅述。

?

功能碼0x05:


我們先講解05功能碼,這個功能碼是實現(xiàn)數(shù)據(jù)寫入,它能實現(xiàn)什么功能呢,我們可以利用這個功能碼來指定某個線圈通或斷,具體怎么操作呢,有了之前01功能碼的經(jīng)驗,下面的代碼看起來就順利多了。

比如我要指定地址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是規(guī)定的數(shù)據(jù),如果你想地址線圈通,就填這個值,想指定線圈為斷,就填 00 00 ,其他任何的值都對結(jié)果無效。

然后我們看看寫入的操作服務(wù)器返回了什么 ???我們看到也是??00 00 00 00 00 06 FF 05 00 00 FF 00???因為在你寫入的操作中,是不帶讀取數(shù)據(jù)的,所以服務(wù)器會直接復(fù)制一遍你的指令并返回。

?

下面再舉例一些方便理解(我們只需要指定地址及是否通斷的情況即可):

寫入地址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:


我們已經(jīng)實現(xiàn)了0x05來單個的線圈寫入,我們可以指定線圈100為通,其實就兩個信息需要指定,線圈地址是什么,通還是端,然后我們就可以自然而然的寫出指令碼了,但是現(xiàn)在我們需要實現(xiàn)一個功能時,將地址0-999共計1000個線圈全部為off,這怎么搞?

按照我們之前的經(jīng)驗,可以發(fā)送一千次的0x05功能碼的指令來實現(xiàn),大不了寫1000次么。。。。。(寫到第100次的時候估計已經(jīng)吐血了)

所以我們就繼續(xù)研究有沒有其他的功能碼來實現(xiàn),突然發(fā)現(xiàn)0x0F這一個神奇的功能碼,這個功能碼是什么意思呢,就是為了批量寫入而存在的,就比如上面的例子0-999都為off,那么指令是什么呢。

00 00 00?00 00?84 FF 0F 00 00 03?E8?7D?...(后面跟125個byte,都是00)?

上面的指令就實現(xiàn)了我們的需求,現(xiàn)在來詳細解釋下,它怎么就實現(xiàn)了我們的需求。分析之前,我們發(fā)現(xiàn)不同的功能碼,的前8個字節(jié)的規(guī)律是一模一樣了,都是標識號+modbus號+長度+站號,后面基本是跟地址和長度,或是直接是地址和數(shù)據(jù)。

00 00???????? 消息標識號,隨便寫什么,反正你寫什么數(shù)據(jù),服務(wù)器就復(fù)制一遍而已。

00 00?????????modbus標志號,都是00 就對了。

00 84?????????我們先轉(zhuǎn)化為十進制,0x0084轉(zhuǎn)化十進制就是132,也就是說,00 84(不包含00 84)后面跟了132個字節(jié)

FF????????????? 站號,其實也是隨便寫,反正服務(wù)器返回一樣的

00 00???????? 起始地址,此處就是0,如果起始地址為100,那么就寫00 64,如果起始地址為1000,那么就寫03 E8

03 E8?????????我們需要寫的數(shù)據(jù)長度,因為我們需要寫1000個線圈,就是03 E8,如果我們寫999個線圈,那么就是03 E7。

7D???????????? 這個字節(jié)代表后面跟隨的真實寫入的數(shù)據(jù)的長度,為125個字節(jié)。

125個字節(jié)? 真實的數(shù)據(jù),我們寫1000個位,那么一個字節(jié)為8位,那么剛好125個字節(jié)可以塞完數(shù)據(jù),那么問題來了,如果我們想實現(xiàn)000-998共計999個地址都是off。那怎么搞。?

那么指令為 00 00 00 00 00 84 FF 0F 00 00 03 E7 7D ...(后面125個byte,都是00)咦,怎么還是125個,原來無論寫多少個,比如x個,如果是8的倍數(shù),剛好x/8個byte,如果除不盡怎么辦,就是x/8+1個字節(jié),這樣才能裝滿我們需要寫的數(shù)據(jù)。

既然后面都是125個字節(jié),那么寫1000個還是999個,那么區(qū)分的關(guān)鍵就在于長度,03 E8還是03 E7。


大致的數(shù)據(jù)在上面已經(jīng)說明了,具體怎么寫數(shù)據(jù)看下面,比如我們寫入地址10-地址19共計10個長度的線圈,要求的結(jié)果分別是,On,Off,Off,On,On,On,On,Off,On,Off,也就是 通,斷,斷,通,通,通,通,斷,通,斷,接下來轉(zhuǎn)換0和1,如下:

1001111010

接下來就是關(guān)鍵了,怎么轉(zhuǎn)化成真實的byte,這樣我們就可以最終寫出來指令了。

第一步:以8個8個為單位進行切割,結(jié)果為?10011110??? 10

第二步:第一步的字單位,每個單位前后順序顛倒,不然不足8位,前面補零,結(jié)果為???0111 1001????????? 0000?0001

第三步:這下可以寫成真實的數(shù)據(jù)了,79 01

?

那么最接下來我們就可以寫最終的指令了,實現(xiàn)寫入地址10-19為:通,斷,斷,通,通,通,通,斷,通,斷??也即?00 00 00 00 00 09 FF 0F 00 0A 00 0A 02 79 01???????? (地址10-19的線圈分別為 通,斷,斷,通,通,通,通,斷,通,斷)

注意上述第二步為什么要順序顛倒,那是因為在計算機的單個byte存儲中,高位在前,地位在后,而對于多個連續(xù)的byte來說,地位在前,高位在后,所以需要顛倒,如果還是不明白,就先死記,終有一天會恍然大悟。

現(xiàn)在應(yīng)該能實現(xiàn)任何的連續(xù)線圈的寫入了吧。


寫入之后,看看了服務(wù)器返回了什么:

00 00 00 00 00 06 FF 0F 00?0A?00 0A????:現(xiàn)在再來看這個數(shù)據(jù)就很簡單了,就是返回了我們寫入數(shù)據(jù)的前12分字節(jié),然后把00 09長度更改為實際的長度 00 06,因為是寫入操作,所以返回的數(shù)據(jù)沒什么意義。

?

?

功能碼0x03:


該功能碼實現(xiàn)寄存器的數(shù)據(jù)讀取,我們需要知道的是,一個寄存器占2個byte,而且是高位在前,地位在后,那么如果寄存器0的數(shù)據(jù)為1000,那么我們讀取到的數(shù)據(jù)就是03 E8,這是我們最終想要的東西,03功能碼和01功能碼很接近,就是功能碼替換一下,返回的數(shù)據(jù)解析不一樣而已,比如我們需要讀取地址0的寄存器數(shù)據(jù):

00 00 00 00 00 06 FF 03 00 00 00 01?????????????? 是不是很熟悉?,當你看到這個的時候,腦力里馬上就是功能碼03,讀取寄存器,地址0,長度1??????? 返回如下:

00 00 00 00 00 05 FF 03 02 03?E8??????????????????? 主要就是看功能碼后面的數(shù)據(jù)了,我們想要的真實數(shù)據(jù)肯定藏在后面,也就是?02 03 E8,不是說一個寄存器返回2個字節(jié)嘛,怎么就變成3個了?事實上第一個字節(jié)不是代表數(shù)據(jù),而是代表后面的字節(jié)長度是2個字節(jié),那么03 E8就是真實的數(shù)據(jù)了,代表了寄存器0存儲了1000這個數(shù)據(jù)。

?

未完待續(xù)...

轉(zhuǎn)載于:https://www.cnblogs.com/duanweishi/p/9351916.html

總結(jié)

以上是生活随笔為你收集整理的modbus tcp 入门详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。