freemodbus modbus TCP 學習筆記


1.前言
    使用modbus有些時間了,期間使用過modbus RTU也使用過modbus TCP,通過博文和大家分享一些MODBUS TCP的東西。在嵌入式中實現TCP就需要借助一個以太網協議棧,在這里我選擇最簡單的uIP協議棧。uIP協議棧簡單易用方便上手,相比於LwIP無論是移植還是使用難度都低些,這樣就可以把更多的精力花在modbus tcp協議本身而不必花大量的時間研究以太網協議棧。modbus協議棧為freemodbus
 
【其他有用的博文】
    【1】 uIP學習筆記
 
【工程代碼】
    示例代碼托管於GitHub——【 Github Clone
    如果有問題我會及時更新。
【使用說明】
    【1】工具鏈為IAR 6.5
    【2】從機IP為固定IP 192.168.1.15,請保證從機和路由器位於同一個網段中。
    【3】modbus tcp的偵聽端口號為502
 
2.MODBUS TCP注意點
2.1 主機和從機、服務端和客戶端
圖1 MODBUS請求響應模型
【在modbus協議中】
主機發送modbus請求,從機根據請求內容向主機返回響應。在modbus協議中,主機總是主動方,從機總是被動方。
【在網絡應用中】
在網絡應用中存在客戶端和服務器端,客戶端(例如瀏覽器)發送請求到服務器,服務器向客戶端返回內容(例如HTML文本)。
【在modbus tcp中】
主機是客戶端,而從機是服務器端。千萬不要以為服務器端重要,主機也重要。
【在modbus rtu中】
主機(Master)是客戶端(Client),而從機(Slave)是服務器端(Server)。
 
2.2 是否可以多主機
    通過前面的分析,主機為客戶端那么modbus tcp支持多個主機,在一個局域網中可存在多個主機和多個從機。從機的連接能力(連接主機的數量)由uIP的最大TCP連接個數決定。
 
2.3 modbus TCP協議簡述
modbus TCP和modbus RTU基本相同,但是也存在一些區別
    【1】從機地址變得不再重要,多數情況下忽略。從某種意義上說從機地址被IP地址取代
    【2】CRC校驗變得不再重要,甚至可以忽略。由於TCP數據包中已經存在校驗,為了不重復造輪子,modbus TCP干脆取消了CRC校驗。
 
modbus TCP和modbus RTU的區別可使用下圖概括
圖2 modbus TCP數據包和modbus RTU數據包比較
 
在modbus TCP中包含一個MBAP頭,該頭包含以下幾個部分
區域
長度
描述
客戶端
服務器
傳輸標志
2字節
MODBUS 請求和響應傳輸過程中
序列號
客戶端生成
應答時復制該值
協議標志
2字節
Modbus協議默認為0
客戶端生成
應答時復制該值
長度
2字節
剩余部分的長度
客戶端生成
應答時由服務器端生成
單元標志
1字節
從機標志(從機地址)
客戶端生成
應答時復制該值
【注意】
【1】傳輸標志可理解為序列號,防止MODBUS TCP通信錯位,例如后發生的響應先到了主機,而早發生的響應后到主機
【2】單元標志可理解為從機地址,此時已經不再重要
 
2.4 modbus tcp 和 TCP IP的關系
    modbus TCP可以理解為發生在TCP上的應用層協議,既然是TCP協議那么一個完整的MODBUS TCP報文必然包括TCP首部,IP首部和Ethernet首部。
    下面就通過uIP協議棧來實現modbus TCP
 
3.代碼實現
3.1 偵聽502端口
[cpp]  view plain  copy
 
  1. BOOL  
  2. xMBTCPPortInit( USHORT usTCPPort )  
  3. {  
  4.     BOOL bOkay = FALSE;  
  5.      
  6.     USHORT usPort;  
  7.     if( usTCPPort == 0 )  
  8.     {  
  9.         usPort = MB_TCP_DEFAULT_PORT;  
  10.     }  
  11.     else  
  12.     {  
  13.         usPort = (USHORT)usTCPPort;  
  14.     }  
  15.      
  16.     // 偵聽端口 502端口  
  17.     uip_listen(HTONS(usPort));  
  18.      
  19.     bOkay = TRUE;  
  20.     return bOkay;  
  21. }  
    【代碼說明】
    【1】uip_listen(HTONS(usPort)) 偵聽502端口,注意大小端變化。
 
3.2 uIP循環處理——porttcp.c
[cpp]  view plain  copy
 
  1. void uip_modbus_appcall(void)  
  2. {  
  3.     if(uip_connected())  
  4.     {  
  5.         PRINTF("connected!\r\n");  
  6.     }  
  7.      
  8.     if(uip_closed())  
  9.     {  
  10.         PRINTF("closed\r\n");  
  11.     }  
  12.      
  13.     if(uip_newdata())  
  14.     {  
  15.         PRINTF("request!\r\n");  
  16.         // 獲得modbus請求  
  17.         memcpy(ucTCPRequestFrame, uip_appdata, uip_len );  
  18.         ucTCPRequestLen = uip_len;  
  19.         // 向 modbus poll發送消息  
  20.         xMBPortEventPost( EV_FRAME_RECEIVED );  
  21.     }  
  22.      
  23.     if(uip_poll())  
  24.     {  
  25.         if(bFrameSent)  
  26.         {  
  27.             bFrameSent = FALSE;  
  28.             // uIP發送Modbus應答數據包  
  29.             uip_send( ucTCPResponseFrame , ucTCPResponseLen );  
  30.         }  
  31.     }  
  32. }  
    【代碼說明】
    【1】uip_newdata()返回為True表示存在新的數據
    【2】復制uip_appdate中的數據到ucTCPRequestFrame,該變量為全局變量,通過該全部變量”中轉“到modbus處理的緩沖區中。然后向modbus協議棧發送消息,消息內容為EV_FRAME_RECEIVED 。由於沒有使用操作系統
    【3】如果處理完成則通過uip_send發送響應。
    【4】ucTCPRequestFrame和ucTCPResponseFrame均為全局數組,用於和modbus緩沖區交換數據。
static UCHAR ucTCPRequestFrame[MB_TCP_BUF_SIZE];
static USHORT ucTCPRequestLen;
static UCHAR ucTCPResponseFrame[MB_TCP_BUF_SIZE];
static USHORT ucTCPResponseLen;
 
3.3 modbus 接收處理
[cpp]  view plain  copy
 
  1. BOOL  
  2. xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )  
  3. {  
  4.     *ppucMBTCPFrame = &ucTCPRequestFrame[0];  
  5.     *usTCPLength = ucTCPRequestLen;  
  6.      
  7.     /* Reset the buffer. */  
  8.     ucTCPRequestLen = 0;  
  9.     return TRUE;  
  10. }  
    【代碼說明】
    【1】** ppucMBTCPFrame為一個指向數據的指針,而*ppucMBTCPFrame可以指向一個數組,在這里可把ucTCPRequestFrame復制給該變量,配合usTCPLength,那么從uIP接收到的內容就”轉移“到freemodbus中。
 
3.4 modbus 發送處理
[cpp]  view plain  copy
 
  1. BOOL  
  2. xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )  
  3. {  
  4.     memcpy( ucTCPResponseFrame , pucMBTCPFrame , usTCPLength);  
  5.     ucTCPResponseLen = usTCPLength;  
  6.      
  7.     bFrameSent = TRUE; // 通過uip_poll發送數據  
  8.     return bFrameSent;  
  9. }  
【代碼說明】
【1】把傳入的內容 pucMBTCPFrame復制給ucTCPResponseFrame,並設置bFrameSent為True,那么在下一次uip_poll時便會把響應發送會主機。
 
4.測試與分析
    【1】連接從機
    選擇IP地址為192.168.1.15,端口號為502
圖3 打開modbus tcp連接
    【2】嘗試讀出保持寄存器
圖4 讀取保持寄存器
    【3】抓包分析
    請使用ip.addr == 192.168.1.15 表達式過濾報文,其中192.168.1.100為PC機,此處為modbus 主機
    【簡單分析】
    【1】115行為modbus主機請求,此時傳輸標志為25.
    【2】116行為modbus從機給出的TCP應答,TCP應答為TCP協議規定的內容,TCP應答中不包含modbus 響應
    【3】117行為modbus從機響應,此時傳輸標志依然為25.
    【4】118行為modbus主機 TCP應答,同16行。
圖5 抓包分析
轉摘:http://blog.csdn.net/xukai871105/article/details/21652287


免責聲明!

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



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