因業務需要了解Modbus協議的使用,因此對Modbus的協議,以及相應的C#處理應用進行了解,針對協議的幾種方式(RTU、ASCII、TCPIP)進行了封裝,以及對Modbus的各種功能碼的特點進行了詳細的了解,本篇隨筆基於這些知識進行了一定的梳理和介紹,主要內容包括Modbus協議簡要介紹、Modbus模擬工具使用和Modbus應用開發幾個部分。
1)Modbus協議簡要介紹
Modbus 協議是應用於電子控制器上的一種通用語言。通過此協議,控制器相互之間、控制器經由網絡(例如以太網)和其它設備之間可以通信。它已經成為一通用工業標准。有了它,不同廠商生產的控制設備可以連成工業網絡,進行集中監控。
此協議定義了一個控制器能認識使用的消息結構,而不管它們是經過何種網絡進行通信的。它描述了一控制器請求訪問其它設備的過程,如果回應來自其它設備的請求,以及怎樣偵測錯誤並記錄。它制定了消息域格局和內容的公共格式。
當在一Modbus網絡上通信時,此協議決定了每個控制器須要知道它們的設備地址,識別按地址發來的消息,決定要產生何種行動。如果需要回應,控制器將生成反饋信息並用Modbus協議發出。在其它網絡上,包含了Modbus協議的消息轉換為在此網絡上使用的幀或包結構。這種轉換也擴展了根據具體的網絡解決節地址、路由路徑及錯誤檢測的方法。
Modbus由MODICON公司於1979年開發,是一種工業現場總線協議標准。1996年施耐德公司推出基於以太網TCP/IP的modbus協議:modbusTCP。
Modbus協議是一項應用層報文傳輸協議,包括ASCII、RTU、TCP三種報文類型。
標准的Modbus協議物理層接口有RS232、RS422、RS485和以太網接口,采用master/slave方式通信。
對於串行連接,存在兩個變種,它們在數值數據表示不同和協議細節上略有不同。Modbus RTU是一種緊湊的,采用二進制表示數據的方式,Modbus ASCII是一種人類可讀的,冗長的表示方式。這兩個變種都使用串行通信(serial communication)方式。RTU格式后續的命令/數據帶有循環冗余校驗的校驗和,而ASCII格式采用縱向冗余校驗的校驗和。被配置為RTU變種的節點不會和設置為ASCII變種的節點通信,反之亦然。
對於通過TCP/IP(例如以太網)的連接,存在多個Modbus/TCP變種,這種方式不需要校驗和計算。
對於所有的這三種通信協議在數據模型和功能調用上都是相同的,只有封裝方式是不同的。
Modbus有一個擴展版本Modbus Plus(Modbus+或者MB+),不過此協議是Modicon專有的,和Modbus不同。它需要一個專門的協處理器來處理類似HDLC的高速令牌旋轉。它使用1Mbit/s的雙絞線,並且每個節點都有轉換隔離裝置,是一種采用轉換/邊緣觸發而不是電壓/水平觸發的裝置。連接Modbus Plus到計算機需要特別的接口,通常是支持ISA(SA85),PCI或者PMCIA總線的板卡。
MODBUS 協議定義了一個與基礎通信層無關的簡單協議數據單元(PDU)。
Modbus串行連路上的的PDU如下所示。
錯誤檢驗域是對報文內容執行"冗余校驗" 的計算結果。根據不同的傳輸模式(RTU or ASCII) 使用兩種不同的計算方法。
RTU的報文格式如下所示。
ASCII碼的報文格式如下所示。
在 ASCII 模式, 報文用特殊的字符區分幀起始和幀結束。一個報文必須以一個‘冒號’ ( : ) (ASCII 十六進制3A )起始,以‘回車-換行’ (CR LF) 對(ASCII 十六進制0D 和0A) 結束。
而Modbus TCP數據幀包含報文頭、功能代碼和數據3部分。
MBAP Header長度共7個字節,分別為Transaction identifier(事務標識符),Protocol identifier(協議標識符),Length(長度),
Unitidentifier(單元標識符)組成,具體如下表所示:
請求和響應帶有六個字節的前綴,如下:
byte 0: 事務處理標識符 –由服務器復制 –通常為 0
byte 1: 事務處理標識符 –由服務器復制 –通常為 0
byte 2: 協議標識符= 0
byte 3: 協議標識符= 0
byte 4: 長度字段 (上半部分字節) = 0 (所有的消息長度小於256)
byte 5: 長度字段 (下半部分字節) = 后面字節的數量
byte 6: 單元標識符 (原“從站地址”)
byte 7: MODBUS 功能代碼
byte 8 on: 所需的數據
數據區:數據區是根據不同的功能碼而不同。數據區可以是實際數值、設置點、主機發送給從機或從機發送給主機的地址。
標准的Modicon控制器使用RS232C實現串行的Modbus。Modbus的ASCII、RTU協議規定了消息、數據的結構、命令和就答的方式,數據通訊采用Maser/Slave方式。
Modbus協議需要對數據進行校驗,串行協議中除有奇偶校驗外,ASCII模式采用LRC校驗,RTU模式采用16位CRC校驗.
ModbusTCP模式沒有額外規定校驗,因為TCP協議是一個面向連接的可靠協議。
對於常規的Modbus串口協議,我們來看看03功能碼的讀取寄存器的操作請求和響應代碼了解下。
請求PDU格式如下所示。
響應的PDU格式如下所示。
一個請求讀寄存器108-110 的實例:
可以注意到,很多數據的處理,需要拆分高位低位,高位在前,低位在后的模式。
根據這些RTU、ASCII、TCPIP的Modbus協議的不同,我們可以構建一個通用的處理程序來處理這些操作,在后面的應用開發部分繼續介紹。
2)Modbus模擬工具使用
一般在做Mobus前期的開發的時候,一般不是針對具體的Modbus設備進行寄存器的處理,而是使用Modbus模擬工具來進行調試,一般我們需要配合Modbus Slave、Modbus Poll、Virtual Serial Port Driver這幾個模擬軟件來進行開發的。
Modbus Poll :Modbus主機仿真器,用於測試和調試Modbus從設備。該軟件支持Modbus的RTU、ASCII、TCP/IP。用來幫助開發人員測試Modbus從設備,或者其它Modbus協議的測試和仿真。
Modbus Slave: Modbus從設備仿真器,可以仿真32個從設備/地址域。每個接口都提供了對EXCEL報表的OLE自動化支持。主要用來模擬Modbus從站設備,接收主站的命令包,回送數據包。幫助Modbus通訊設備開發人員進行Modbus通訊協議的模擬和測試,用於模擬、測試、調試Modbus通訊設備。
Virtual Serial Port Driver:虛擬串口工具,不需要串口接線,提供虛擬的串口,適合學習和調試使用。
配合這幾款軟件,我們就可以實現串口Modbus協議的模擬測試了,如果我們使用Modbus的TCPIP協議,那么我們不需要VSPD也可以。
如果我們使用Modbus協議的串口通訊方式,那么我們先要使用VSPD進行串口的配對模擬,模擬出兩個通訊的串口端口,端口配對模擬成功后,我們可以看到設備管理器中增加了兩個端口了。
接着使用從機模擬器,模擬一個Modbus從機供測試,通過菜單【Connection】【Connect】啟動,我們選擇連接方式為串口,端口則選擇我們配對的其中一個端口即可,如下圖所示。
其中模式選擇RTU或者ASCII都可以,這兩個模式協議有所不同,一旦從機選擇RTU模式,那么Modbus主機也需要選擇對應的RTU模式,反之亦然。
其他串口設置,如波特率、數據位、奇偶位、停止位等默認配置即可。
如果我們選擇TCPIP模式,那么對應Modbus主機也需要選擇TCPIP方式。
一旦Modbus從機啟動,就會處理來自Modbus主機的指令請求(如果有的話),並做相應處理,我們可以通過【Display】【Communication】菜單彈出的對話框,了解到對應請求和應答的協議詳細信息。
Modbus主機的啟動和ModBus從機類似,我們根據ModBus從機的配置,選擇對應的主機配置,Modbus模擬主機啟動和查看通訊記錄界面如下所示。
另外我們可以通過【Display】里面選擇內容顯示的進制格式。
在從機的設置里面,我們可以修改從機的定義信息,以便設置對應的從機ID,功能碼,其實地址,長度或者數量的信息,如下界面所示。
我們可以根據實際的寄存器地址和數量,設置對應的數值,如下是顯示4個數據的內容設置和顯示內容。
設置后正常的內容顯示如下。
同時我們也需要設置對應Modbus主機模擬器的地址和數量,正確設置后可以正常顯示。
通過更深一步的設置或者調整,我們可以極大程度的進行模擬Modbus實際設備的處理方式,從而在沒有實際Modbus硬件設備的情況下盡可能通過前期的模擬完成常規功能的測試和准備。
在我們開發Modbus應用的時候,我們對照相應的主從機Modbus協議請求和應答,能夠檢查我們程序的輸出是否正常,從而可以快速的開發Modbus的應用處理功能。
3)Modbus應用開發
為了模擬對接Modbus的RTU、ASCII、TCP/IP協議處理,我根據不同協議的處理方式定義了一個輔助函數,然后統一進行處理,以便達到統一調用的處理便利。
首先我們來看看使用串口模式下(RTU、ASCII)的處理界面效果,這個直接獲取模擬器Modbus Slave從機的數值進行顯示的。
TCPIP網絡方式對接Modbus界面處理效果如下所示。
兩者數據均來源於Modbus Slave從機的數值,只是它們對接的方式不同。
串口的處理,我通過SerialPortUtil類來使用Windows的串口類,處理對應的串口操作,通過定義事件的方式,使得串口收到數據的時候,及時通知調用者進行界面更新處理即可。
//使用字符串參數構造 serial = new SerialPortUtil(portname, this.txtBaudRate.Text, this.txtParity.Text, this.txtDataBits.Text, this.txtStopBit.Text); //收到數據處理的事件 serial.DataReceived += Serial_DataReceived; serial.RTUMode = this.radRTU.Checked;//默認RTU模式為True,否則使用ASCII模式
收到數據后,及時通過委托方式,通知UI進行界面的更新顯示。
/// <summary> /// 收到串口響應事件后,及時進行處理(更新在界面上) /// </summary> /// <param name="e"></param> private void Serial_DataReceived(DataReceivedEventArgs e) { //記錄在日志,方便復制 LogTextHelper.Info(e.DataReceived); //使用委托進行處理界面控件的數據更新 this.txtResponse.Invoke(new MethodInvoker(()=> { //顯示在界面上 this.txtResponse.AppendText(e.DataReceived); this.txtResponse.AppendText(Environment.NewLine); var dataBytes = e.BytesReceived; if(dataBytes != null && dataBytes.Length > 2) { var function = dataBytes[1]; if(function > 0x80)//128 { //Modbus的異常代碼大於128,如果是異常,則可以解析錯誤 var newFunction = function - 0x80; lblTips.Text = "響應有異常,功能代碼:" + newFunction.ToString("D2"); lblTips.Text += ",錯誤描述:" + ((ModBusExceptionCode)newFunction).ToString(); } else { lblTips.Text = "響應正常";//小於128的為正常響應 } } })); }
而對於網絡方式,我們先要定義一個Socket通訊的基類,封裝相關的通訊處理操作。
然后簡單構建一個子類進行使用,如下所示。
/// <summary> /// 通信類子類 /// </summary> public class ModbusClient : BaseSocketClient { public ModbusClient() { this.Name = "ModbusClient"; } }
界面處理的時候,我們只需要初始化一個ModbusClient類來使用即可,如下代碼所示。
client = new ModbusClient(); //收到數據處理的事件 client.DataReceived += Client_DataReceived;
收到數據通知界面進行更新的操作如下所示。
private void Client_DataReceived(DataReceivedEventArgs e) { //記錄在日志,方便復制 LogTextHelper.Info(e.DataReceived); //使用委托進行處理界面控件的數據更新 this.txtResponse.Invoke(new MethodInvoker(() => { this.txtResponse.AppendText(e.DataReceived); this.txtResponse.AppendText(Environment.NewLine); var dataBytes = e.BytesReceived; if (dataBytes != null && dataBytes.Length > 2) { //串口功能碼為第二個字節,TCP/IP功能碼為第8個 var function = dataBytes[7]; if (function > 0x80)//128 { //Modbus的異常代碼大於128,如果是異常,則可以解析錯誤 var newFunction = function - 0x80; lblTips.Text = "響應有異常,功能代碼:" + newFunction.ToString("D2"); lblTips.Text += ",錯誤描述:" + ((ModBusExceptionCode)newFunction).ToString(); } else { lblTips.Text = "響應正常";//小於128的為正常響應 } } })); }
不管是串口的RTU或者ASCII,又或者是TCPIP的協議,我們可以通過定義一個協議封裝的輔助類ModbusQueryHelper來處理協議的具體細節。
/// <summary> /// Modbus查詢消息生成輔助類,可以用於串口RTU/ASCII協議,也可以用於TCPIP協議。 /// 用於生成各種功能代碼的消息內容。 /// </summary> public class ModbusQueryHelper { /// <summary> /// 是否為RTU模式,默認為True,否則為ASCII方式 /// </summary> public ModbusProtocol Protocol { get; set; } = ModbusProtocol.RTU; /// <summary> /// 默認函數 /// </summary> public ModbusQueryHelper() { } /// <summary> /// 參數化構造,指定RTU模式 /// </summary> /// <param name="protocal">Modbus協議:ASCII,RTU, TCP,默認為RTU</param> public ModbusQueryHelper(ModbusProtocol protocal) { this.Protocol = protocal; }
而其中ModbusProtocol是一個枚舉,定義如下所示。
/// <summary> /// 幾種常用的Modbus協議 /// </summary> public enum ModbusProtocol { /// <summary> /// 串口的ASCII模式 /// </summary> ASCII, /// <summary> /// 串口的RTU模式 /// </summary> RTU, /// <summary> /// 網絡TCPIP模式 /// </summary> TCP }
我們通過ModbusQueryHelper 類,可以處理不同協議之間的封裝細節,並可以對各種功能碼的協議進行封裝處理。
以上就是 相關Modbus的應用處理和封裝,對於常規的Modbus協議可以極大簡化對接處理,在實際對接Modbus設備的時候,我們只需要根據對應的說明書,獲取對應的內容,就可以把例如溫度、濕度、轉速等一些設備或者機器人的參數獲得,並記錄在數據庫里面,然后在應用模塊中整合一些圖表展示就可以很好的實現看板功能了。