自從開源了我們自己開發(fā)的Modbus協(xié)議棧之后,有很多朋友建議我針對性的做幾個示例。所以我們就基于平時我們的應(yīng)用整理了幾個簡單但可以說明基本的應(yīng)用方法的示例,這一篇中我們來簡述如何使用協(xié)議棧實現(xiàn)一個Modbus TCP服務(wù)器應(yīng)用。
1 、何為TCP服務(wù)器
Modbus協(xié)議是一個主從協(xié)議,那肯定就有主站和從站之分,在Modbus TCP中亦稱之為客戶端與服務(wù)器。所謂TCP客戶端其功能基本與RTU主站一樣,RTU主站會向從站發(fā)起數(shù)據(jù)請求,同樣的TCP客戶端也會向服務(wù)器發(fā)起請求。也就是說在Modbus TCP模式下客戶端亦是發(fā)起通訊的一方。
對于TCP客戶端來說,自己并不會產(chǎn)生數(shù)據(jù),它的數(shù)據(jù)均是從服務(wù)器獲取,為了得到數(shù)據(jù)就必須向服務(wù)器發(fā)起數(shù)據(jù)請求。在Modbus TCP協(xié)議中,服務(wù)器一般也不會主動向外發(fā)送數(shù)據(jù),服務(wù)器需要根據(jù)客戶端的數(shù)據(jù)請求來決定是否發(fā)送數(shù)據(jù)、發(fā)送哪些數(shù)據(jù)。這一過程如下圖所示:
從上圖我們不難看出,首先客戶端要主動發(fā)起數(shù)據(jù)請求,客戶端發(fā)起的數(shù)據(jù)請求需要告訴服務(wù)器它請求的數(shù)據(jù)有哪些。服務(wù)器收到這個數(shù)據(jù)請求后,服務(wù)器解析客戶端的請求并按照客戶端的請求返回數(shù)據(jù)??蛻舳耸盏綌?shù)據(jù)響應(yīng)后解析數(shù)據(jù),這樣就完成了客戶端與服務(wù)器之間的一次數(shù)據(jù)通訊。
需要注意的是,Modbus TCP與Modbus RTU不同的是有一個專用的MBAP報文頭來識別Modbus應(yīng)用數(shù)據(jù)單元。這一報文頭由7個字節(jié)組成:
這種MBAP報文頭雖然也是用來識別Modbus數(shù)據(jù)域,但還是與串行鏈路上使用的MODBUS RTU應(yīng)用數(shù)據(jù)單元有一些差別,具體如下:
( 1 ) 、用MBAP報文頭中的單個字節(jié)單元標(biāo)識符取代MODBUS串行鏈路上通常使用的MODBUS從地址域。這個單元標(biāo)識符用于設(shè)備的通信,這些設(shè)備使用單個 IP 地址支持多個獨立MODBUS 終端單元,例如:網(wǎng)橋、路由器和網(wǎng)關(guān)。
( 2 ) 、使用接收者可以驗證的方式來構(gòu)造所有MODBUS請求和響應(yīng)。對于MODBUS PDU有固定長度的功能碼來說,僅功能碼就足夠了。對于在請求或響應(yīng)中攜帶一個可變數(shù)據(jù)的功能碼來說,數(shù)據(jù)域包括字節(jié)數(shù)。
( 3 ) 、使用TCP上傳送MODBUS數(shù)據(jù)域時,即使將報文分成多個信息包來傳輸,可在MBAP報文頭上攜帶附加長度信息,這樣接收者就能夠識別報文的完整性。
2 、如何實現(xiàn)TCP服務(wù)器
我們已經(jīng)簡單的描述了基于TCP/IP的Modbus數(shù)據(jù)通訊,在此基礎(chǔ)上我們將進(jìn)一步描述基于協(xié)議棧的Modbus TCP服務(wù)器的實現(xiàn)。
在協(xié)議棧中,我們已經(jīng)實現(xiàn)了Modbus TCP服務(wù)器的基本功能,如數(shù)據(jù)的管理及響應(yīng)客戶端的請求等。Modbus TCP服務(wù)器作為數(shù)據(jù)的生產(chǎn)者,管理者四類數(shù)據(jù):線圈量、狀態(tài)量、輸入寄存器和保持寄存器。所以在Modbus TCP服務(wù)器中我們要為這四種數(shù)據(jù)定義相應(yīng)的地址,以便客戶端能夠?qū)?yīng)的訪問。所以設(shè)計一個Modbus TCP服務(wù)器我們先來設(shè)計它的數(shù)據(jù)地址。在我們的例子中,出于操作方便,我們規(guī)定了每類數(shù)據(jù)類型的數(shù)量為10,我們以用的最多的保持寄存器為例,定義寄存器地址為40001到40010。
在我們的協(xié)議棧中實現(xiàn)了0x01、0x02、0x03、0x04、0x05、0x06、0x0F以及0x10等功能碼。也就是說客戶端對象會生成面向這些功能碼的Modbus TCP服務(wù)器數(shù)據(jù)請求。Modbus TCP服務(wù)器收到請求后,解析請求并根據(jù)請求生成響應(yīng)的數(shù)據(jù)響應(yīng)。可以表示為下圖所示:
從上圖我們明白協(xié)議棧中已經(jīng)實現(xiàn)了對收到的主站數(shù)據(jù)請求進(jìn)行解析以及根據(jù)解析生成對應(yīng)的響應(yīng)的函數(shù)。我們使用協(xié)議棧時,主要需要做兩個方面的事情:解析數(shù)據(jù)請求和生成數(shù)據(jù)響應(yīng)。
在協(xié)議棧中定義了一個解析函數(shù),該函數(shù)將收到的數(shù)據(jù)請求消息解析,并根據(jù)解析的結(jié)果生成返回的數(shù)據(jù)響應(yīng)。該函數(shù)的原型如下:
/ 解析接收到的信息,返回響應(yīng)命令的長度 /
uint16_t ParsingClientAccessCommand(uint8_t receivedMessage,uint8_trespondBytes)
這個函數(shù)有2個參數(shù):uint8_t receivedMessage是收到的數(shù)據(jù)請求消息; uint8_trespondBytes是返回的數(shù)據(jù)響應(yīng)消息,也是函數(shù)需要生成的;而函數(shù)的返回值則是生成的數(shù)據(jù)響應(yīng)詳細(xì)的長度。
在解析的過程中,該函數(shù)判斷消息的完整性,并根據(jù)不同的功能碼調(diào)用不同的回調(diào)函數(shù)來實現(xiàn),包括設(shè)置本地數(shù)據(jù)和獲取本地數(shù)據(jù)的相關(guān)回調(diào)函數(shù),在后續(xù)將討論它們的實現(xiàn)。
3 、 TCP****服務(wù)器編碼
到這里其實我們已經(jīng)很清楚,使用協(xié)議棧實現(xiàn)Modbus TCP服務(wù)器只需要在TCP/IP收到客戶端請求后調(diào)用sendLen = ParsingClientAccessCommand(buffer,sendBuf);函數(shù)解析收到的請求命令。并根據(jù)請求執(zhí)行相應(yīng)的操作就可以了。那需要實現(xiàn)哪些操作呢?在協(xié)議棧中定義了8個回調(diào)函數(shù),分別是獲取線圈量、獲取狀態(tài)量、獲取輸入寄存器和獲取保持寄存器,以及預(yù)置單個線圈量、預(yù)置多個線圈量、預(yù)置單個保持寄存器和預(yù)置多個保持寄存器。函數(shù)原型定義如下:
/*獲取想要讀取的Coil量的值*/
__weak void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool*statusList)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*獲取想要讀取的InputStatus量的值*/
__weak void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool*statusValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*獲取想要讀取的保持寄存器的值*/
__weak void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t*registerValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*獲取想要讀取的輸入寄存器的值*/
__weak void GetInputRegister(uint16_t startAddress,uint16_tquantity,uint16_t *registerValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*設(shè)置單個線圈的值*/
__weak void SetSingleCoil(uint16_t coilAddress,bool coilValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*設(shè)置單個寄存器的值*/
__weak void SetSingleRegister(uint16_t registerAddress,uint16_tregisterValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*設(shè)置多個線圈的值*/
__weak void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool*statusValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
/*設(shè)置多個寄存器的值*/
__weak void SetMultipleRegister(uint16_t startAddress,uint16_tquantity,uint16_t *registerValue)
{
//如果需要Modbus TCP Server/RTU Slave應(yīng)用中實現(xiàn)具體內(nèi)容
}
這些函數(shù)就是我們要根據(jù)我們的Modbus TCP服務(wù)器功能設(shè)計實現(xiàn)的。對于我們這個測試?yán)游覀冎恍枰獙崿F(xiàn)讀取保持寄存器就可以了。具體實現(xiàn)如下:
/*獲取想要讀取的保持寄存器的值*/
void GetHoldingRegister(uint16_t startAddress,uint16_t quantity, uint16_t* registerValue)
{
uint16_t start;
uint16_t count;
/*先判斷地址是否處于合法范圍*/
start =(startAddress > 0) ? ((startAddress <= 9) ? startAddress : 9) : 0;
count =((start + quantity - 1) <= 9) ? quantity : (9 - start);
for(int i = 0; i < count; i++)
{
registerValue[i] = holdingRegister[start + i];
}
}
這個例子中我們實現(xiàn)了讀取40001到40010保持寄存器的值。
4 、 TCP****服務(wù)器小結(jié)
我們在TCP服務(wù)器的基礎(chǔ)上使用我們的協(xié)議棧實現(xiàn)一個Modbus TCP服務(wù)器應(yīng)用。其實使用協(xié)議棧實現(xiàn)Modbus TCP服務(wù)器應(yīng)用是很簡單的,我們需要使用如ModPoll這樣的軟件來測試一下它。
我們讀取10個保持寄存器,值分別為對應(yīng)位固定的1到10,如上圖讀出的結(jié)果與預(yù)期一致。我們還可以采用TCP&UDP測試工具來看一下報文,具體如下:
同樣的,在同一臺設(shè)備上只需實現(xiàn)一個Modbus TCP服務(wù)器,哪怕是通過不同的網(wǎng)絡(luò)端口來訪問。這一點與客戶端是不一樣的,原因是Modbus TCP服務(wù)器的數(shù)據(jù)是自己產(chǎn)生,而且只需被動響應(yīng)客戶端的數(shù)據(jù)請求。
接下來我們來總結(jié)一下使用協(xié)議棧實現(xiàn)Modbus TCP服務(wù)器的工作流程,或者說實現(xiàn)的步驟。首先Modbus TCP服務(wù)器要解析從客戶端送來的數(shù)據(jù)請求。在協(xié)議棧中已經(jīng)封裝了數(shù)據(jù)請求的解析函數(shù)、所以我們實現(xiàn)Modbus TCP服務(wù)器時首先就是調(diào)用這一函數(shù)來解析接收到的數(shù)據(jù)請求消息。
然后將解析函數(shù)返回的數(shù)據(jù)響應(yīng)消息發(fā)送到客戶端就可以了。也就是說使用協(xié)議棧,只需要調(diào)用一下這個函數(shù)Modbus TCP服務(wù)器功能就實現(xiàn)了。這是因為這個函數(shù)實現(xiàn)了整個Modbus TCP服務(wù)器的響應(yīng)過程,大致分三個步驟:第一步,解析收到的客戶端數(shù)據(jù)請求消息;第二步,根據(jù)解析的結(jié)果預(yù)置數(shù)據(jù)或者獲取數(shù)據(jù),預(yù)置和獲取數(shù)據(jù)由8個回調(diào)函數(shù)實現(xiàn);第三步,生成Modbus TCP服務(wù)器數(shù)據(jù)響應(yīng)消息。說到這里我們已經(jīng)清楚,Modbus TCP服務(wù)器必須實現(xiàn)這些回調(diào)函數(shù),其它工作則全由協(xié)議棧完成。
源碼下載:https://download.csdn.net/download/foxclever/12838885
-
MODBUS
+關(guān)注
關(guān)注
28文章
1824瀏覽量
77349 -
服務(wù)器
+關(guān)注
關(guān)注
12文章
9331瀏覽量
86131 -
TCP
+關(guān)注
關(guān)注
8文章
1379瀏覽量
79341 -
協(xié)議棧
+關(guān)注
關(guān)注
2文章
144瀏覽量
33710
發(fā)布評論請先 登錄
相關(guān)推薦
使用協(xié)議棧實現(xiàn)Modbus TCP客戶端應(yīng)用
![使用<b class='flag-5'>協(xié)議</b><b class='flag-5'>棧</b><b class='flag-5'>實現(xiàn)</b><b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>客戶端應(yīng)用](https://file.elecfans.com/web2/M00/83/36/poYBAGOYNQ6AS1IZAACJ76uaPuw504.png)
linux平臺實現(xiàn)modbus主機(jī)協(xié)議棧的動態(tài)庫libMbpoll
協(xié)議棧介紹--TCP/IP
Modbus庫開發(fā)筆記之九:利用協(xié)議棧開發(fā)Modbus TCP Server應(yīng)用
Modbus協(xié)議中文版
在uIP協(xié)議棧實現(xiàn)基于AJAX和CGI的動態(tài)Web服務(wù)器
怎么實現(xiàn)的基于TCP/IP協(xié)議棧的簡易服務(wù)器?
如何快速實現(xiàn)Modbus RTU和Modbus TCP協(xié)議轉(zhuǎn)換?
基于RT-Thread實現(xiàn)的Agile Modbus協(xié)議棧
Modbus通訊協(xié)議的幾種實現(xiàn)方式
嵌入式WEB服務(wù)器中TCP/IP協(xié)議棧的設(shè)計與實現(xiàn)
Microchip TCP/IP協(xié)議棧
![Microchip <b class='flag-5'>TCP</b>/IP<b class='flag-5'>協(xié)議</b><b class='flag-5'>棧</b>](https://file.elecfans.com/web1/M00/4F/6B/pIYBAFrZn3OAYUcCAAAt8in1lwE043.jpg)
STM32+ENC28J60+UIP協(xié)議棧實現(xiàn)WEB服務(wù)器示例
![STM32+ENC28J60+UIP<b class='flag-5'>協(xié)議</b><b class='flag-5'>棧</b><b class='flag-5'>實現(xiàn)</b>WEB<b class='flag-5'>服務(wù)器</b>示例](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
Arduino供電的I/O Modbus/TCP設(shè)備服務(wù)器
![Arduino供電的I/O <b class='flag-5'>Modbus</b>/<b class='flag-5'>TCP</b>設(shè)備<b class='flag-5'>服務(wù)器</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論