5.1 學習Modbus的快速方法
5.1.1 寄存器速記
作為初學者,你閱讀Modbus協議時會發現它的概念別扭、重復、不易區分,比如線圈狀態(Coil Status)、離散輸入狀態(Discrete Input Status)、保持寄存器(Holding Register)、輸入寄存器(Input Register)。
回到事情的本質,在工業控制PLC領域,涉及數字信號的輸入、輸出,模擬信號的輸入、輸出,如下圖所示:

對于軟件開發而言:
- 想得到按鍵輸入狀態時,讀取到的是一位數據;
- 想控制LED時,需要輸出一位數據,想讀取LED當前狀態時,也可以讀取到一位數據
- 想讀取模擬信號時,讀取到的是多位數據,比如16位數據
- 想輸出模擬信號時,寫入的是多位數據,比如16位數據;也可以讀取“模擬量輸出”的當前值。
在上圖中,“數字量輸入DI”是只讀的,“數字量輸出DO”是可讀可寫的,“模擬量輸入AI”是只讀的,“模擬量輸出AO”是可讀可寫的。
上圖里的“模擬量輸入AI”、“模擬量輸出AO”都表示“多位數值”,這些“多位數值”無需局限于只表示“模擬量”,也可以表示“多位數字量”。把AI、AO的含義擴展后,如下圖所示:

對于軟件開發而言:
- 想得到按鍵輸入狀態時,讀取到的是一位數據;
- 想控制LED時,需要輸出一位數據,想讀取LED當前狀態時,也可以讀取到一位數據
- 想讀取參數時,讀取到的“輸入寄存器”,得到多位數據,比如16位數據
- 想設置參數時,寫的是“保存寄存器”,寫入的是多位數據,比如16位數據;也可以讀“保存寄存器”
在電子系統里,無論是單bit的數值、多bit的數值,都是保存在寄存器里。根據上圖,這些寄存器可以分為4類:
| 寄存器種類 | 說明 | 與PLC類比 | 舉例說明 |
|---|---|---|---|
| 線圈狀態(Coil Status) | 輸出端口。可設定端口輸出狀態,也可以讀取該位的輸出狀態。可分為兩種不同的執行狀態,列如保持型或邊沿觸發型 | DO(數字量輸出) | 電磁閥輸出、MOSFEF輸出、LED顯示等 |
| 離散輸入狀態(Discrete Input Status) | 輸入端口。通過外部設定改變輸入狀態,可讀但不可以寫 | DI(數字量輸入) | 撥碼開關、接近開關等 |
| 保持寄存器(Holding Register) | 輸出參數或保持參數,控制器運行時被設定的某些參數,可讀可寫 | AO(模擬量輸出) | 模擬量輸出設定值,PID運行參數,變量閥輸出大小,傳感器報警上限下限 |
| 輸入寄存器(Input Register) | 輸入參數。控制器運行時從外部設備獲得的參數,但可讀不可寫 | AI(模擬量輸入) | 模擬量輸入 |
在Modbus中,多位操作時都是16位(2bytes)的,總結如下:
- bit操作涉及的寄存器有2類:線圈狀態(可讀可寫)、離散輸入狀態(只讀)
- 16bit操作的寄存器有2類:保存寄存器(可讀可寫)、輸入寄存器(只讀)
一個設備里,可能有多個“線圈狀態”、多個“離散輸入狀態”、多個“保存寄存器”、多個“輸入寄存器”。怎么分辨某類寄存器中的某一個?它們有“寄存器地址”,如下圖所示:
| 寄存器種類 | PLC寄存器地址范圍 | Modbus寄存器地址范圍 | 簡稱 | 讀寫狀態 |
|---|---|---|---|---|
| 線圈狀態 | 00001~09999 | 0000H~FFFFH | 0x | 可讀可寫 |
| 離散輸入狀態 | 10001~19999 | 0000H~FFFFH | 1x | 只讀 |
| 保持寄存器 | 40001~49999 | 0000H~FFFFH | 4x | 可讀可寫 |
| 輸入寄存器 | 30001~39999 | 0000H~FFFFH | 3x | 只讀 |
在上表中,“線圈狀態”的寄存器N、“離散輸入狀態”的寄存器N,是兩個不同的寄存器。
簡單記憶方法:“
- 偶數類的寄存器”是可讀可寫的,比如“0x”和“4x”;
- “奇數類的寄存器”是只讀的,比如“1x”和“3x”;
- “0x”和“1x”是bit寄存器;
- “3x”和“4x”是16bit寄存器。
5.1.2 協議速記
Modbus是一主多從的協議,如下圖所示:

主控發出的數據里,必定含有如下信息:
- 設備地址:你要訪問從設備1,還是訪問從設備2
- 訪問哪類寄存器,是讀還是寫,只訪問1個寄存器,還是多個寄存器:這被稱為功能碼
- 起始寄存器地址、寄存器數量:這在數據里定義
- 為了保證數據傳輸的可靠,還附帶有CRC檢驗碼
以Modbus RTU協議為例,主控發出的數據包格式如下:

功能代碼有哪些?常用的功能碼如下:
- 讀線圈狀態(01)
- 讀離散輸入狀態(02)
- 寫單個線圈(05)、寫多個線圈(15)
- 讀保持寄存器(03)
- 讀輸入寄存器(04)
- 寫單個保存寄存器(06)、寫多個保存寄存器(16)
數據的格式,由功能代碼確定。以“讀線圈狀態”為例,主控發出的請求、從設備返回的響應包,或者從設備返回的錯誤包,格式如下:

上圖中,寄存器起始地址(“Starting Address”)是16位的,先傳輸高字節,再傳輸低字節。線圈數量(“Quantiti of coils”)也是16位的,先傳輸高字節,再傳輸低字節。
響應包回復多少個數據呢(上圖中N為多少)?N = Quantiti of coils / 8,如果余數不等于0,則N再加1。比如Quantiti of coils=9,則返回2個字節。
在《Modbus_Application_Protocol_V1_1b3.pdf》中,列出了如下功能表。根據次表,在結合《5.5 Moubus功能碼詳解》的示例,就可以對Modbus RTU協議有很好的理解了。

5.2 初識Modbus
5.2.1 背景
Modbus誕生于1979年莫迪康(Modicon)公司,后來被施耐德電氣公司收購。Modbus提供通用語言用于彼此通信的設備和設備,是全球第一個真正用于工業現場的總線協議。Modbus已經成為工業領域通信協議的業界標準,并且現在是工業電子設備之間常用的連接方式。Modbus作為目前工業領域應用最廣泛的協議,之后為了更好地普及和推動Modbus基于以太網 (TCP/IP) 的分布式應用,施耐德公司已將Modbus協議的所有權移交給IDA (Interface for Distributed Automation,分布式自動化接口)組織,并成立了Modbus-IDA組織,此組織的成立和發展進一步推動了Modbus協議的廣泛應用。

5.2.2 什么是Modbus?
1. Modbus簡介
Modbus協議是一種已廣泛應用于當今工業控制領域的通用通訊協議。通過此協議,控制器相互之間、或控制器經由網絡(如以太網)可以和其它設備之間進行通信。Modbus協議使用的是主從通訊技術,即由主設備主動查詢和操作從設備。一般將主控設備方所使用的協議稱為Modbus Master,從設備方使用的協議稱為Modbus Slave。典型的主設備包括工控機和工業控制器等;典型的從設備如PLC可編程控制器等。有了它,不同廠商生產的控制設備就可以連接成工業網絡,進行集中監控。Modbus協議定義了一個控制器能夠認識和使用的消息結構,而不管它們是經過何種網絡進行通信的;而且描述了控制器請求訪問其他設備的過程,如何應答來自其他設備的請求,以及怎樣偵測錯誤并記錄,并制定了統一的消息域的結構和內容。 當在Modbus網絡上通信時,Modbus協議決定了每個控制器必須要知道它們的設備地址,識別按地址發來的消息決定要產生何種行為。如果需要回應,則控制器將生成反饋信息并通過Modbus協議發送。
Modbus通訊物理接口可以選用串口(包括RS232、RS485和RS422等),也可以選擇以太網口。其通信遵循以下的過程:
- 主設備向從設備發送請求
- 從設備分析并處理主設備的請求,然后向主設備發送結果
- 如果出現任何差錯,從設備將返回一個異常功能碼
此協議定義了一個控制器能認識使用的消息結構,而不管它們是經過何種網絡進行通信的。它描述了一控制器請求訪問其它設備的過程,如何回應來自其它設備的請求,以及怎樣偵測錯誤并記錄。它制定了消息域格局和內容的公共格式。
當在Modbus網絡上通信時,此協議決定了每個控制器須要知道它們的設備地址,識別按地址發來的消息,決定要產生何種行動。如果需要回應,控制器將生成反饋信息并用Modbus協議發出。在其它網絡上,包含了Modbus協議的消息轉換為在此網絡上使用的幀或包結構。這種轉換也擴展了根據具體的網絡解決節地址、路由路徑及錯誤檢測的方法。
Modbus的工作方式是請求/應答,每次通訊都是主站先發送指令,可以是廣播,或是向特定從站的單播;從站響應指令,并按要求應答,或者報告異常。當主站不發送請求時,從站不會自己發出數據,從站和從站之間不能直接通訊。
MODBUS 是一種應用層消息傳遞協議,位于 OSI 模型的第 7 層。它提供連接在不同類型總線或網絡上的設備之間的客戶端/服務器通信。
Modbus通信棧如下:

2. Modbus特點
Modbus通信協議具有以下幾個特點:
- Modbus協議標準開放、公開發表且無版稅要求。用戶可以免費獲取并使用Modbus協議,不需要交納許可證費,也不會侵犯知識產權。
- Modbus協議支持多種電氣接口,如RS232、RS485、TCP/IP等,還可以在各種介質上傳輸,如雙絞線、光纖、紅外、無線。
- Modbus協議消息幀格式簡單、緊湊、通俗易懂。用戶理解和使用簡單,廠商容易開發和集成,方便形成工業控制網絡。
- 可靠性: Modbus 是最古老的工業自動化通信協議。它使用和編程簡單,因此學習曲線較低。
- 遺留基礎設施:許多制造商在早期自動化方面投入了大量資金。Modbus 對于配置、DLR、節點、子站和其他基礎設施非常友好,這些基礎設施可能會被新的或更先進的協議所淘汰。
- 快速部署: Modbus 可以輕松、立即集成到 SCADA和其他控制系統中
- 靈活性: Modbus 已適應新興技術。例如,Modbus TCP 可以通過話配器進行轉換,以與LAN 和遠程控制系統集成。它還可以利用基于網絡和基于云的平臺。
- 簡單性:由于通信簡單,因此可以輕松擴展到新技術。例如,Modbus TCP/P 由于指令集簡單,部署速度很快。它還可以與以太網配合使用,無需添加芯片或板。
3. Modbus常用術語
| 名詞 | 意義 |
|---|---|
| Master | 主(站) 設備 |
| Slave | 從 (站) 設備 |
| Client | 客戶端 |
| Server | 服務器端 |
| ADU | 應用數據單元(Application Data Unit) |
| PDU | 協議數據單元 (Protocol Data Unit) |
| MSB | 最高有效位(Most Significant Bit) |
| LSB | 最低有效位 (Least Significant Bit) |
| MBAP | Modbus應用協議(Modbus Application Protocol) |
| PLC | 可編程邏輯控制器(Programmable Logic Controller) |
4. Modbus事務處理
Modbus協議允許在各種網終體系結構內進行簡單通信,每種設備 (包括PLC、HMI、控制面板、驅動程序、動作控制、輸入/輸出設備) 都能使用Modbus協議啟動遠程操作。在基于串行鏈路和以太網 (TCP/IP)的Modbus上可以進行相互通信。一些網關允許在幾種使用MODBUS協議的總線或網絡之間進行通訊。
MOUBUS網絡體系結構的實例:

Modbus是一個請求、應答協議,并且提供統一的功能碼用于數據傳輸服務。Modbus功能碼是Modbus請求/應答PDU (Protocol Data Unit,協議數據單元)的元素之一,所謂的PDU其實就是Modbus協議定義的一個與基礎通信層無關的簡單協議數據單元。特定總線或網絡上的Modbus協議映射能夠在ADU (Application Data UInit ,應用數據單元)上引入一些附加域,從而實現完整而準確的數據傳輸。
為了尋求一種簡潔的通信格式,Modbus協議定義了PDU模型,即功能碼+數據的格式,而為了適應多種傳輸模式,又在PDU的基礎上增加了必要的前綴 (如地址域)和后綴(如差錯校驗) ,形成了ADU模型(見下圖)。
通用MODBUS幀如下:

Modbus事務處理過程:
- 主機設備 (或客戶端)創建Modbus應用數據單元形成查詢報文,其中功能碼標識了向從機設備 (或服務器端)指示將要執行的操作。其中功能碼占用1字節,有效的碼字范圍是十進制1 ~ 255 (其中128 ~255為異常響應保留) 。查詢報文創建完畢,主機設備 (或客戶端) 向從機設備 (或服務器端)發送報文,從機設備 (或服務器端)接收報文后根據功能碼做出相應的動作,并將響應報文返回給主機設備 (或客戶端),如圖下所示:
MouBus事務處理(無異常)

- 如果在一個正確接收的Modbus ADU中不出現與請求Modbus功能有關的差錯,那么從機設備 (或服務器端) 將返回正常的響應報文。如果出現與請求Modbus功能有關的差錯,那么響應報文的功能碼域將包括一個異常碼,主機設備(或客戶端)能夠根據異常碼確定下一步執行的操作;對于異常響應,服務器返回一個與原始功能碼等同的碼,設置該原始功能碼的最高有效位為邏輯1,用于通知主設備(客戶端)。如下圖所示:
MouBus事務處理(異常響應)

5.3 Modbus軟件與使用
5.3.1 Modbus軟件簡介
為了更好的學習和理解Modbus,這里推出三個軟件Modbus Poll(主站設備)、Modbus Slave(從站設備)和虛擬串口軟件,借助三款設備我們可以在PC上做一些基礎實驗,更加直觀地觀察通信數據,加深我們的理解,我們將它稱為Modbus學習必備三件套,這是一個很好的入門方法。
5.3.2 Modbus Poll(主站設備)
1. Modbus Poll簡介
Modbus Poll是Modbus主站設備仿真器,用于測試和調試Modbus從設備便于觀察Modbus通信過程中的各種報文數據。該軟件支持ModbusRTU、ASCII、TCP/IP。用來幫助開發人員測試Modbus從設備,或者其它Modbus協議的測試和仿真。它支持多文檔接口,即,可以同時監視多個從設備/數據域。每個窗口簡單地設定從設備ID,功能,地址,大小和輪詢間隔。你可以從任意一個窗口讀寫寄存器和線圈。如果你想改變一個單獨的寄存器,簡單地雙擊這個值即可。或者你可以改變多個寄存器/線圈值。提供數據的多種格式方式,比如浮點、雙精度、長整型(可以字節序列交換)。該軟件支持Modbus RTU、ASCII、TCP/IP等協議模式。
Modbus Poll支持下列協議模式:
| Modbus RTU | Modbus RTU Over TCP/IP |
|---|---|
| Modbus ASCII | Modbus ASCI Over TCP/IP |
| Modbus TCP/IP | Modbus RTU Over UDP/IP |
| Modbus UDP/IP | Modbus ASCII Over UDP/IP |
2. Modbus Poll 使用
點擊鏈接獲取軟件,按照提示安裝即可;鏈接:
https://pan.baidu.com/s/1SpTRz6Z1XlkoCZjDozwqog 提取碼:timc
下載完界面如下:

狀態欄:
- Tx = 0表示向主站發送數據幀次數,圖中為0次;
- Err = 0表示通訊錯誤次數,圖中為0次;
- ID = 1表示模擬的Modbus子設備的設備地址,圖中地址為1;
- F = 03表示所使用的Modbus功能碼,圖中為03功能碼;
- SR = 1000ms表示發送周期,1S一次。
- 紅字部分,表示當前的錯誤狀態,“No Connection”表示未連接狀態。
建立連接:
點擊Connection->Connect進入配置頁面,選擇我們想要的連接,選擇我們虛擬出來的串口,選擇模式,例如:我們選擇串口的連接方式,選則RTU模式,對應我們的Modbus RTU協議;接下來在設置波特率、比特位、校驗位、停止位,如下圖所示:

設置參數:點擊Setup->Read/Write Definition進入配置頁面,配置從機地址、功能碼、地址類型、寄存器地址、訪問數量、輪詢時間,具體配置如下圖:

5.3.3 Modbus Slave(從站設備)
1. Modbus Slave簡介
Modbus從設備仿真器,可以仿真32個從設備/地址域。每個接口都提供了對EXCEL報表的OLE自動化支持。主要用來模擬Modbus從站設備,接收主站的命令包,回送數據包。幫助Modbus通訊設備開發人員進行Modbus通訊協議的模擬和測試,用于模擬、測試、調試Modbus通訊設備,便于觀察Modbus通信過程中的各種報文數據;可以32個窗口中模擬多達32個Modbus子設備。與Modbus Poll的用戶界面相同,支持功能01, 02, 03, 04, 05, 06, 15, 16, 22和23,監視串口數據。
Modbus Slave支持下列協議模式:
| Modbus RTU | Modbus RTU Over TCP/IP |
|---|---|
| Modbus ASCII | Modbus ASCI Over TCP/IP |
| Modbus TCP/IP | Modbus RTU Over UDP/IP |
| Modbus UDP/IP | Modbus ASCII Over UDP/IP |
2. Modbus Slave使用
獲取軟件鏈接同上,下載完后主頁面如圖所示:

建立連接:
點擊Connection->Connect進入配置頁面,選擇我們想要的連接,選擇我們虛擬出來的串口,選擇模式,接下來在設置波特率、比特位、校驗位、停止位,如下圖所示:

設置參數:點擊Setup->Read/Write Definition進入配置頁面,配置從機地址、功能碼、地址類型、寄存器地址、訪問數量,具體配置如下圖:

這里有兩點需要我們注意一下:
- 一是:Function列表框選擇功能中的0x~4x,表示的是存儲區0區、1區、3區、4區
- 輸出線圈
- 輸入線圈
- 保持寄存器
- 輸入寄存器

Modbus協議規定了4個存儲區 分別是0、1、3、4區 其中0區和4區是可讀可寫,1區和3區是只讀。
| 區號 | 名稱 | 讀寫 | 地址范圍 |
|---|---|---|---|
| 0區 | 輸出線圈 | 可讀可寫布爾量 | 00001-09999 |
| 1區 | 輸入線圈 | 只讀布爾量 | 10001-19999 |
| 3區 | 輸入寄存器 | 只讀寄存器 | 30001-39999 |
| 4區 | 保持寄存器 | 可讀可寫寄存器 | 40001-49999 |
- 二是:Address項,這里需要特別強調一下,Address表示Modbus寄存器地址,其取值范圍與設備寄存器地址存在映射關系,如下表所示:
| Device address | Modbus address | Description | Function | R/W |
|---|---|---|---|---|
| 1…10000 | address - 1 | Coils(outputs) | 01 | Read/Write |
| 10001…20000 | address - 10001 | Discrete Inputs | 02 | Read |
| 40001…50000 | address - 40001 | Holding Registers | 03 | Read/Write |
| 30001…40000 | address - 30001 | Input Registers | 04 | Read |
這里我們只簡單介紹下地址和存儲區,下面我們會詳細展開。
5.3.4 虛擬串口軟件
1.軟件簡介
虛擬串口工具,可以創建2個互聯的串口,如下圖所示:

比如Modbus Poll工具使用COM1發送數據給COM2,Modbus Slave從COM2讀到數據。使用虛擬串口,就可以不使用開發板也可以體驗Modbus Poll、Modbus Slave。
軟件在網盤里:

2.虛擬串口的使用
安裝后運行虛擬串口程序“Virtual Serial Port Tools”,安裝下圖創建2個串口:

打開設備管理器,可以看到如下串口:

5.3.5 Modbus Poll 與 Modbus Slave互聯/通
下面我們進行Modbus Poll 與 Modbus Slave互聯互通實驗,通過形象直觀的方式展示Modbus數據流,根據前面的設定我們已將知道了如何運用Modbus學習必備三件套,下面我們就通過三件套來進行實驗,首先打開VSPD虛擬串口軟件,設置虛擬串口,我這里就以上面設訂COM1,COM2為例,接下來我們再來配置我們的Modbus Poll 與 Modbus Slave;
我們首先打開Modbus Slave端,設置連接,連接方式我們選擇Serial Port串口連接,選擇我們設置的串口COM1,模式選擇RTU模式,如下圖所示:
- Modbus Slave連接設定

在設置參數,從機地址我們設定1(你也可以自己隨意設定),Function項我們選擇03 Holding Register(4x),地址類型我們選擇DEC(十進制格式),Address首地址我們設置為0,訪問寄存器數量設置為10,如下圖所示:
- Modbus Slave參數設定

接下來我們再來設置Modbus Poll端,設置方法也是和Modbus Slave端一一對應的,連接設定,參數設定,如下圖所示:
- Modbus Poll連接設定

注意這里串口要選擇我們設定的COM20,其它串口參數必須一一對應。
- Modbus Poll參數設定

設置好后,我們主設備和從設備分別連接了我們設置的COM1,COM2,這樣我們便可觀察當前寄存器的讀取情況。
我們雙擊Modbus Poll(主設備端)地址中的0值,便可打開值設置窗口如下圖所示:

修改值為66,點擊Send打開Modbus Slave(從設備端)便可發現也做出了改變,如下圖所示:

我們還可以打開Modbus Poll,點擊Display,選擇Commuaction,查看發送的報文:

TX是我們主站發送的報文,RX是從站返回的報文,報文我們下面會展開說明,帶領大家一起看報文;
5.4 Modbus協議細節
5.4.1 Modbus協議概述
簡而言之,Modbus 協議是一種單主/多從的通信協議,其特點是在同一時間總線上只能有一個主設備,但可以有一個或者多個(最多 247 個)從設備。Modbus通信總是由主設備發起,當從設備沒有收到來自主設備的請求時,從設備不會主動發送數據。從設備之間不能相互通信,主設備只能同時啟動一個 Modbus 訪回事務處理。 主設備可以采用兩種方式向從設備發送 Modbus 請求報文,即主設備可以對指定的單個從設備或者線路上所有的從設備發送請求報文,而從設備只能在被被動接收請求報文后給出響應報文,即應答。這兩種模式分別如下圖所示:
Modbus請求應答周期

單播模式。主設備僅僅尋址單個從設備,從設備接收并處理請求后,向主設備返回一個響應報文,即應答。在這種模式下,一個 Modbus 事務處理包含兩個報文:一個是主設備的請求報文,另一個是從設備的響應報文。
每個從設備必須有唯一的地址(地址范圍為 1~247),這樣才能區別于其它從設備,從而可以獨立被尋址,同時主設備不占用地址。

廣播模式。此種模式下,主設備可以向所有從設備發送請求指令,而從設備在接收到廣播指令后僅進行相關指令的事務處理,而不要求返回應答。因此廣播模式下,請求指令必須是 Modbus 標準功能中的寫指令。
根據 Modbus 標準協議的要求,所有從設備必須接收廣播模式下的寫指令,且地址0被保留,用來識別廣播通信

- 請求:
主設備發送的請求報文主要包括從設備地址(或廣播地址0)、功能碼、傳輸的數據以及差錯檢測字段。
查詢消息中的功能碼告訴我們被選中的從設備地址要執行何種功能。數據段包含從設備要執行功能的所有附加信息。例如,功能代碼 03 要求從設備讀取保持寄存器并返回其內容。
數據段必須包含要告訴從設備的信息:從哪個寄存器開始讀取及要讀取的寄存器數量。差錯檢測域為從設備提供一種驗證消息內容是否正確的方法。
- 應答:
從設備的應答報文包括地址、功能碼、差錯檢測域等; 如果從設備產生了一個正常的回應,則回應消息中的功能碼是查詢消息中的功能碼的回應。數據段包括從設備收集的數據,如寄存器值或狀態。如果有錯誤發生,則功能碼將被修改以用于指出回應消息是錯誤的,同時數據段包含描述此錯誤信息的代碼。差錯檢測域允許主設備確認消息內容是否可用。
對于串行鏈路來說,又存在兩種傳輸模式:ASCII(American StandardCode for Information Interchange,美國標準信息交換碼)模式和 RTU(RemoteTerminal Unit,遠程終端單元)模式。但是,對于同一網絡或鏈路來說,所有設備必須保持統一,要么統一為 ASCII 模式,要么統一為 RTU 模式,不可共存。相對來說,RTU模式的傳輸效率更高,因此在當前普遍的生產環境中,RTU 模式獲得了廣泛應用,而 ASCII模式只能作為特殊情況下的可選項。
5.4.2 Modbus寄存器(存儲區)
Modbus 協議中的一個重要概念是寄存器,所有數據均存放于寄存器。最初,Modbus 協議借鑒了 PLC 中寄存器的含義,但是隨著 Modbus 協議的廣泛應用,寄存器的概念進一步泛化,它不再是指具體的物理寄存器,也可能是指一塊內存區域Modbus 寄存器根據存放的數據類型以及各自的讀寫特性將寄存器分為四部分,這四部分既可以連續,也可以不連續,由開發者決定。寄存器意義如下表所示:
Modbus寄存器(存儲區)
| 寄存器種類 | 說明 | 與PLC類比 | 舉例說明 |
|---|---|---|---|
| 線圈狀態(Coil Status) | 輸出端口。可設定端口輸出狀態,也可以讀取該位的輸出狀態。可分為兩種不同的執行狀態,列如保持型或邊沿觸發型 | DO(數字量輸出) | 電磁閥輸出、MOSFEF輸出、LED顯示等 |
| 離散輸入狀態(Input Status) | 輸入端口。通過外部設定改變輸入狀態,可讀但不可以寫 | DI(數字量輸入) | 撥碼開關、接近開關等 |
| 保持寄存器(Holding Register) | 輸出參數或保持參數,控制器運行時被設定的某些參數,可讀可寫 | AO(模擬量輸出) | 模擬量輸出設定值,PID運行參數,變量閥輸出大小,傳感器報警上限下限 |
| 輸入寄存器 | 輸入參數。控制器運行時從外部設備獲得的參數,但可讀不可寫 | AI(模擬量輸入) | 模擬量輸入 |
(PLC)數據的傳輸離不開存儲和讀寫操作,為了更好存儲不同的數據類型,我們可以理解Modbus 會將布爾和非布爾的數據分開存儲。
1. 存儲區類型
我們可以將存儲區類型分為布爾類型和非布爾類型,布爾類型我們用線圈(Coil)表示,非布爾用寄存器(Register)表示;
- 什么是布爾?
- 布爾類型只有兩個值,false 和 true。
- 通常用來判斷條件是否成立。
- C語言語法規定,如果變量值為 0 就是 false,否則為 true,布爾變量只有這兩個值。
因此,我們便有了線圈和寄存器的概念;
- 那線圈和寄存器又是什么?
- 線圈: 從電氣角度來看,在電氣控制回路中,一般都是靠接觸器或中間繼電器來實現控制,接觸器或中繼最終靠的是線圈的得電和失電來控制觸點閉合和斷開,因此用線圈表示布爾量;
- 寄存器: 用來暫時存放參與運算的數據和運算結果,具有接收數據、存放數據和輸出數據的功能。
- 而寄存器在計算機中,就是用來存儲數據的,因此非布爾的數據放在寄存器里。
回到存儲區分類,目的就是: 更好地存儲和區分不同的數據類型。
Modbus的線圈和寄存器應該也按照只讀、讀寫來進一步劃分,因此這就形成了Modbus的存儲區,如下表所示:
- Modbus存儲區類型及名稱
| 序號 | 讀寫 | 存儲類型 | 存儲區名稱 |
|---|---|---|---|
| 1 | 只讀 | 線圈 | 輸入線圈 |
| 2 | 讀寫 | 線圈 | 輸出線圈 |
| 3 | 只讀 | 寄存器 | 輸入寄存器 |
| 4 | 讀寫 | 寄存器 | 保持寄存器 |
- 存儲區代號:
為什么需要存儲區代號?上面表格里的存儲區名稱是一個全稱,開發和使用中使用全稱會比較麻煩,因此需要給他們取個別名。所以Modbus也要給這些存儲區取一個代號,干脆直接用數字吧,于是,就有了下面的規定:
| 存儲區名稱 | 存儲區代號 |
|---|---|
| 輸入線圈 | 1區 |
| 輸出線圈 | 0區 |
| 輸入寄存器 | 3區 |
| 保持寄存器 | 4區 |
存儲區代號其實可以簡單理解為我們的名字,例如我們的名字有全名,和小名,全名呢是正式場合,外人叫的,小名呢是我們的親近的人,日常場合叫的。這么一說是不是就理解為什么有存儲區代號啦。
- 存儲區范圍:
- 無論是什么存儲區,都會有一個范圍的限制;Modbus的每個存儲區也規定了一個范圍,不能無限制使用。
- Modbus規定每個存儲區的最大范圍是65536,也就是0~65535。
2. 協議地址模型
PLC地址是我們常見的,但它是怎么組成的的,它是由存儲區代號 + 地址組成,我們把這樣的地址稱為絕對地址,把后面的地址成為相對地址;而我們的Modbus地址跟PLC地址也是類似的;
Modbus地址公式:存儲區代號 + (地址 + 1)如下表所示:
- Modbus長地址模型
| 存儲區名稱 | 存儲區代號 | 絕對地址范圍 | 相對地址范圍 |
|---|---|---|---|
| 輸入線圈 | 1區 | 100001~165536 | 0~65535 |
| 輸出線圈 | 0區 | 000001~065536 | 0~65535 |
| 輸入寄存器 | 3區 | 300001~365536 | 0~65535 |
| 保持寄存器 | 4區 | 400001~465536 | 0~65535 |
Modbus地址分為長地址模型和短地址模型,上面Modbus長地址模型,但是在實際使用中,我們一般用不了這么多地址,一般情況下,10000以內就已經足夠使用了;因此,為了方便我們便有了另一種短的地址模型,如下圖所示:
- Modbus短地址模型
| 存儲區名稱 | 存儲區代號 | 絕對地址范圍 | 相對地址范圍 |
|---|---|---|---|
| 輸入線圈 | 1區 | 10001~19999 | 0~9998 |
| 輸出線圈 | 0區 | 00001~09999 | 0~9998 |
| 輸入寄存器 | 3區 | 30001~39999 | 0~9998 |
| 保持寄存器 | 4區 | 40001~49999 | 0~9998 |
5.4.3 Modbus常用功能碼
Modbus功能碼占用1字節,取值范圍是1127。之所以127以上不能用,是因為Modbus規定當出現異常時,功能碼+0x80(十進制128)代表異常狀態,因此129(1+128)255(127+128)的取值代表異常碼。
- Modbus常用功能碼
| 功能碼 | 描述 | 寄存器PLC地址 | 位/字操作 | 操作數量 |
|---|---|---|---|---|
| 01H | 讀線圈寄存器(讀輸出線圈) | 00001~09999 | 位操作 | 單個或多個 |
| 02H | 讀離散輸入寄存器(讀輸入線圈) | 10001~19999 | 位操作 | 單個或多個 |
| 03H | 讀保持寄存器 | 40001~49999 | 字操作 | 單個或多個 |
| 04H | 讀輸入寄存器 | 30001~39999 | 字操作 | 單個或多個 |
| 05H | 寫單個線圈寄存器 | 00001~09999 | 位操作 | 單個 |
| 06H | 寫單個保持寄存器 | 40001~49999 | 字操作 | 單個 |
| 0FH(15) | 寫多個線圈寄存器 | 00001~09999 | 位操作 | 多個 |
| 10H(16) | 寫多個保持寄存器 | 40001~49999 | 字操作 | 多個 |
功能碼可以分為位操作和字操作兩類。位操作的最小為1位(bit),字操作的最小單位為2字節。
① 位操作指令:讀取線圈狀態的功能碼 01 ,讀(離散)輸入狀態功能碼 02 ,寫單個線圈功能碼 05 和寫多個線圈功能碼 15。
② 字操作指令:讀保持寄存器功能碼 03 ,讀輸入寄存器功能碼 04 ,寫單個保持寄存器功能碼 06 ,寫多個保持寄存器功能碼 16 。
5.4.4 Modbus協議類型
Modbus可以在各種介質上傳輸,那么他的傳輸模式也分為三種。包括ASCII、RTU(遠程終端控制系統)、TCP三種報文類型
- 常用Modbus協議
| Modbus協議 | Modbus協議 |
|---|---|
| Modbus RTU | Modbus RTU Over TCP/IP |
| Modbus ASCII | Modbus ASCI Over TCP/IP |
| Modbus TCP/IP | Modbus RTU Over UDP/IP |
| Modbus UDP/IP | Modbus ASCII Over UDP/IP |
Modbus協議使用串口傳輸時可以選擇RTU或ASCII模式,并規定了消息、數據結構、命令和應答方式并需要對數據進行校驗。ASCII 模式采用LRC校驗,RTU模式采用16 位CRC校驗。通過以太網傳輸時使用TCP,這種模式不使用校驗,因為TCP協議是一個面向連接的可靠協議。
5.4.5 Modbus報文幀
一個報文就是一幀數據,一個數據幀就一個報文: 指的是一串完整的指令數據,本質就是一串數據。
Modbus報文是指主機發送給從機的一幀數據,其中包含著從機的地址,主機想執行的操作,校驗碼等內容
1. Modbus ASCII 模式
**當控制器設為在Modbus網絡上以ASCII模式通信時,在消息中每個8位(b)字節都將作為兩個 ASCII字符發送。這種方式的主要優點是字符發送的時間可隔可達到 1秒且不產生錯誤。 **
**在ASCII模式下,消息以冒號(:)字符(ASCII碼為 0x3A)開始,以回車換行奇結束(ASCII碼為 0x0D、0x0A)。消息的其他字段(域)可以使用的傳輸字符是十六進制的0···9、A···F。處于網絡上的 Modbus 設備不斷偵測“:”字符,當接收到一個冒號時,每個設備進入解碼階段,并解碼下一個字段(地址域)以判斷是否是發給自己的。消息幀中的字符間發送的時間間隔最長不能超過 1秒,否則接收設備將認為發生傳輸錯誤。 **
下表是一個典型的ASCII報文格式:
| 開始字符 | 地址 | 功能碼 | 數據 | LRC校驗 | 結束字符 |
|---|---|---|---|---|---|
| 1byte (:) | 2byte | 2byte | Nbyte | 2byte | CR、LF |
2. Modbus RTU 模式
Modbus協議RTU報文格式如下所示:
| 從機地址 | 功能碼 | 數據 | CRC校驗 |
|---|---|---|---|
| 1byte | 1byte | Nbyte | 2byte |
- **幀結構 = 從機地址 + 功能碼 + 數據 + 校驗 **
- 從機地址: 每個從機都有唯一地址,占用一個字節,范圍0-255,其中有效范圍是1-247,其中255是廣播地址(廣播就是對所有從機發送應答)
- 功能碼: 占用一個字節,功能碼的意義就是,知道這個指令是干啥的,比如你可以查詢從機的數據,也可以修改從機的數據,所以不同功能碼對應不同功能.
- 數據: 根據功能碼不同,有不同功能,比方說功能碼是查詢從機的數據,這里就是查詢數據的地址和查詢字節數等。
- 校驗: 在數據傳輸過程中可能數據會發生錯誤,CRC檢驗檢測接收的數據是否正確
3. 串行報文幀總結:
ModbusASCII有開始字符(和結束字符(CR LF),可以作為一幀數據開始和結束的標志,而ModbusRTU沒有這樣的標志,需要用時間間隔來判斷一幀報文的開始和結束,協議規定的時間為3.5個字符周期,就是說一幀報文開始前,必須有大于3.5個字符周期的空閑時間,一幀報文結束后,也必須要有3.5個字符周期的空閑時間否則就會出現粘包的情況。
**注意:針對3.5個字符周期,其實是一個具體時間,但是這個時間跟波特率相關。 在串口通信中,1個字符包括1位起始位、8位數據位(一般情況)、1位校驗位(或者沒有)、1位停止位(一般情況下),因此1個字符包括11個位,那么3.5個字符就是38.5個位,波特率表示的含義是每秒傳輸的二進制位的個位,因此如果是9600波特率,3.5個字符周期=38.5/9600=0.00401s*1000=4.01ms**
5.4.6 Modbus 差錯校驗
在Modbus串行通信中,根據傳輸模式(ASCII或RTU)的不同,差錯校驗域將采用不同的校驗方法。
- ASCII模式
在ASCII模式中,報文包含一個錯誤校驗字段,該字段由兩個字符組成,其基于對全部報文內容執行的縱向冗余校驗(Longitudinal Redundancy ChedLRC)計算的結果而來,計算對象不包括起始的冒號(:)和回車換行符號(CR LF)。 - **RTU模式 **
在RTU模式中,報文同樣包含一個錯誤校驗字段。與ASCII模式不同的是該字段由 16 個比特位共2字節組成,其值基于對全部報文內容執行的循環冗余校驗(Cyclical Redundancy Check,CRC)計算的結果而來,計算對象包括校驗域之的所有字節。
1. LRC校驗
在ASCII模式中,消息是由特定的字符作為幀頭和幀尾分隔的。
一條消息必須以“冒號”(:)字符(ASCII碼為0x3A)開始,以“回車換行(CRLF)(ASCII碼為 0x0D 和 0x0A)結束。LRC 校驗算法的計算范圍為“:” “CRLF”之間的字符。
從算法本質來說,LRC域自身為1字節,即包含一個 8位二進制數據,由發送設備通過LRC算法把計算值附到信息末尾。接收設備在接收信息時通過 LRC法重新計算值,并把計算值與 LRC 字段中接收的實際值進行比較。若兩者不同,則產生一個錯誤,返回一個異常響應幀,即對報文中的所有相鄰的兩個 8位字相加,丟棄任何進位,然后對結果進行二進制補碼,計算出 LRC值。
必須注意的是,計算 LRC 校驗碼的時機是在對報文中每個原始字節進行ASCII碼編碼之前,對每個原始字節進行LRC校驗的計算操作。
- LRC校驗流程:
- 將消息中的全部字節相加(不包括起始“:”和結束符(CRLF),并把結果送入8位數據區,舍棄進位。
- 由0xFF(即全 1)減去最終的數據值,產生1的補碼(即二進制反碼)
- 加1產生二進制補碼。
以上產生的LRC值占用1字節,但實際上在通過串行鏈路由ASCII模式傳遞消息頓時,LRC的結果(1字節)被編碼為2字節的ASCII字符,并將其放置在ASCII模式報文幀的CRLF字段之前。
Modbus 標準協議的英文版提供了 LRC算法,其中的參數意義如下unsigned char*auchMsg:含有生成LRC所使用的二進制數據的報文緩存區指針。 unsigned short usDataLen:報文緩存區中的字節數。
LCR的代碼如下:
/*函數返回unsigned char類型的 LRC值*/
?
static unsigned char LRC(unsigned char * auchMsg, unsigned short usDatalen)
?
{
?
unsigned char uchLRC=0; /*LRC字節初始化*/
?
?
while(usDataLen--) /*遍歷報文緩沖區*/
?
uchLRC+=*auchMsg++; /*緩沖區宇節相加,自動舍棄進位*/
?
?
return ((unsigned char)(-(( char)uchLRC))); /*返回二進制補碼*/
?
}
下面舉一個簡單的例子。假設從設備地址為 1,要求讀取輸人寄存器地址30001的值,則具體的查詢消息幀如下:
":" , "0" , "1" , "0" , "4" , "0" ,"0" ,"0" ,"0" ,"0" ,"0" ,"0" , "1" , "F" , "A" ,CR/LF
其中,“F”, “A”即為LRC值在 ASCII模式下的形式,即0xFA.
2. CRC校驗
在Modbus RTU傳輸模式下,通信報文(幀)包括一個基于循環冗余校驗方法的差錯校驗字段。
Modbus協議采用了CRC-16標準校驗方法。在RTU模式下,CRC自身由2字節組成,即CRC是一個16位的值。CRC字段校驗整個報文的內容,無論報文中的單個字節采用何種奇偶校驗方式,整個通信報文均可使用CRC-16校驗算法,CRC字段作為報文的最后字段添加在整個報文末尾。
需要注意的是,因為CRC-16是由2字節組成,所以涉及哪個字節放在前面,哪個字節放在后面傳輸的問題,即大小端模式的選擇問題。另外,由于Modbus協議規定寄存器為16位(即2字節)長度,因此大小端問題的存在給很多初學者造成了困擾,下一章我們會重點講一下大小端的問題。
- CRC校驗流程:
- 預置一個16位寄存器為0xFFFF(全1),稱之為CRC寄存器。
- 把數據幀中的第一個字節的8位與CRC寄存器中的低字節進行異或運算,結果存回CRC寄存器。
- 將CRC寄存器向右移一位,最高位填以0,最低位移出并檢測是0還是1。
- 如果最低位為0:重復第三步(再次右移一位);如果最低位為1:將CRC寄存器與一個預設的固定值(0xA001)進行異或運算。
- 重復第三步和第四步直到8次移位。這樣處理完了一個完整的八位。
- 重復第2步到第5步來處理下一個八位,直到所有的字節處理結束。
- 將該通信消息幀的所有字節按上述步驟計算完成后,再將得到的16位CRC寄存器的高、低位字節進行交換,即發送時首先添加低位字節,然后添加高位字節。
- 最終CRC寄存器的值就是CRC的校驗碼。
需要注意的是,在進行CRC計算時只有串行鏈路上的每個字符的8個數據位參與計算,從而起始位、停止位、奇偶校驗位等都不參與CRC計算。
常用的CRC-16算法有查表法、計算法。
查表法:
CRC查表法是將位移異或的計算結果做成了一個表,即將0~256 放入一個度為16位的寄存器的低8位,高8位填充0,然后將該寄存器與多項式0xA001照上述步驟3、4直到8 位全部移出,最后寄存器中的值就是表格中的數據,高8位、低 8 位分別單獨做成一個表。 實際上,Modbus 標準協議的英文版提供了CRC 查表算法 函數的輸入參數意義如下:
unsigned char * puchMsg; /*要進行CRC校驗的消息*/
?
unsigned short usDataLen; /*消息中的字節數*/
?
/*函數返回 unsigned short(即2個字節)類型的 CRC值*/
?
unsigned short CRC16(unsigned char *puchMsg,unsigned short usDataLen)
?
{
?
unsigned charuchCRCHi=0xFF; /*高 CRC字節初始化* /
?
unsigned char uchCRCLo=0xFF; /*低 CRC字節初始化*/
?
unsigned short uIndex; /*CRC 循環表中的索引*/
?
?
while (usDataLen--) /* 循環處理傳輸緩沖區消息 */
?
{
?
uIndex=uchCRCHi ^ * puchMsg++; /*計算 CRC* /
?
uchCRCHi=uchCRCLo ^ auchCRCHi[uIndex];
?
uchCRCLo=auchCRCLo[uIndex];
?
}
?
return (uchCRCHi < 81uchCRCLo);
?
}
其中,auchCRCHi和auchCRCLo的定義分別如下:
static unsigned char auchcRCHi[] =
?
{
?
0x00,0xC1,0x81,0x48,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,
?
0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,
?
0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,
?
0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x01,
?
0x00,0xc1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,
?
0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
?
0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,
?
0xc0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
?
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,
?
0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,
?
0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xCl,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,
?
0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
?
0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,
?
0x40,0x01,0xc0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01.0xC0,
?
0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,
?
0xc0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
?
0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xC1,0x81,
?
0x40
?
};
static char auchCRCLo[]=
?
{
?
0x00,0xc0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,0x05,0xC5,0xC4,
?
0x04,0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,
?
0x08,0xc8,0xDB,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,0x1E,0XDE,0XDE,0x1F,0xDD,
?
0x1D,0x1C,0xDC,0x14,0xD4,0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,
?
0x11,0xD1,0xD0,0x10,0xF0,0x30,0x31,0xF1,0x33,0xE3,0xE2,0x32,0x36,0xF6,0xF7,
?
0x37,0xF5,0x35,0x34,0xF4,0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,
?
0x3B,0xFB,0x39,0xF9,0xF8,0x38,0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,0xEE,
?
0x2E,0x2F,0xEF,0x2D,0xED,0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,
?
0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,0x61,0xA1,0x63,0xA3,0xA2,
?
0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,0xA4,0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,
?
0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xAB,0x68,0x78,0xB8,0xB9,0x79,0xBB,
?
0x7B,0x7A,0xBA,0xBE,0x7E,0x7E,0xBE,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,
?
0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0,0x50,0x90,0x91,
?
0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,0x9C,0x5C,
?
0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,0x99,0x59,0x58,0x98,0x88,
?
0x48,0x49,0x89,0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,0x8E,0x4F,0x8D,0x4D,0x4C,0x8C,
?
0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,
?
0x40
?
};
注意:實際編程時,auchcRCHi[]和auchCRCLo[]的定義應該放在函數CRC-16()之前。
查表法可以進一步化簡如下:
unsigned short CRC16(unsigned char * puchMsg,unsigned short usDataLen)
?
{
?
static const unsigned short usCRCTable[]=
?
{
?
0x0000,0xC0C1,0xC181,0x0140,0XC301,0X03C0,0X0280,0xc241,
?
0XC601,0X06C0,0x0780,0XC741,0X0500,0XC5C1,0XC481,0X0440,
?
0xCC01,0X0CC0,0X0D80,0XCD41,0X0F00,0XCEC1,0XCE81,0X0E40,
?
0X0A00,0XCAC1,0XCB81,0X0B40,0XC901,0X09C0,0X0880,0XC841,
?
0XD801,0x18c0,0X1980,0XD941,0X1B00,0XDBC1,0XDA81,0X1A40,
?
0X1E00,0XDEC1,0XDF81,0X1F40,0XDD01,0X1DC0,0X1C80,0XDC41,
?
0x1400,0XD4C1,0XD581,0X1540,0XD701,0X17C0,0X1680,0XD641,
?
0XD201,0X12c0,0X1380,0XD341,0X1100,0XD1C1,0XD081,0X1040,
?
0XF001,0X30C0,0X3180,0XE141,0X3300,0XE3C1,0XE281,0X3240,
?
0X3600,0XF6C1,0XE781,0X3740,0XE501,0X35C0,0X3480,0XE441,
?
0X3C00,0XFCC1,0XFD81,0X3D40,0XFF01,0X3FC0,0X3EB0,0XFE41,
?
0XFA01,0X3AC0,0X3B80,0XFB41,0X3900,0XE9C1,0XF881,0X3840,
?
0X2800,0XE8C1,0XE981,0X2940,0XEB01,0X2BC0,0X2A80,0XEA41,
?
0XEE01,0X2EC0,0X2F80,0XEF41,0X2D00,0XEDC1,0XEC81,0X2C40,
?
0XE401,0X24C0,0X2580,0XE541,0X2700,0XE7C1,0XE681,0X2640,
?
0x2200,0XE2C1,0XE381,0X2340,0XE101,0X21C0,0X2080,0XE041,
?
0XA001,0X60C0,0X6180,0XA141,0X6300,0XA3C1,0XA281,0X6240,
?
0X6600,0XA6c1,0XA781,0X6740,0XA501,0X65C0,0X6480,0XA441,
?
0X6C00,0XACC1,0XAD81,0X6D40,0XAF01,0X6EC0,0X6E80,0XAE41,
?
0XAA01,0X6AC0,0X6B80,0XAB41,0X6900,0XA9C1,0XA881,0X6840,
?
0X7800,0XB8C1,0XB981,0X7940,0XBB01,0X7BC0,0X7A80,0XBA41,
?
0XBE01,0X7EC0,0X7F80,0XBF41,0X7D00,0XBDC1,0XBC81,0X7C40,
?
0XB401,0X74C0,0X7580,0XB541,0X7700,0XB7C1,0XB681,0X7640,
?
0X7200,0XB2C1,0XB381,0X7340,0XB101,0X71C0,0X7080,0XB041,
?
0X5000,0X90c1,0X9181,0X5140,0X9301,0X53c0,0X5280,0X9241,
?
0X9601,0X56C0,0X5780,0X9741,0X5500,0X95c1,0X9481,0X5440,
?
0X9C01,0X5cc0,0X5D80,0X9D41,0X5E00,0X9FC1,0X9E81,0X5E40,
?
0X5A00,0X9AC1,0X9B81,0X5B40,0x9901,0x59c0,0x5880,0X9841,
?
0x8801,0X4BC0,0X4980,0X8941,0X4B00,0XBBC1,0X8AB1,0X4A40,
?
0X4E00,0X8EC1,0X8F81,0X4F40,0X8D01,0X4DC0,0X4C80,0X8C41,
?
0X4400,0X84c1,0x8581,0X4540,0X8701,0X47C0,0X4680,0X8641,
?
0x8201,0X42c0,0x4380,0X8341,0X4100,0XB1C1,0X8081,0X4040,
?
};
?
unsigned char nTemp;
?
unsigned short usRegCRC = 0xFFFF;
?
while (usDataLen--)
?
{
?
nTemp = * puchMsg ++ ^ usRegCRC;
?
usRegCRC > > = 8;
?
usRegCRC ^= usCRCTable[nTemp];
?
}
?
return usRegCRC;
?
}
查表法的特點是以字節為單位進行計算,速度快,語句少,但表格會占用一定的程序空間。
- 計算法:
計算法按位計算,適用于所有長度的數據校驗,最為靈活;但由于是按位計算,其效率并不是最優的,只適用于對速度不敏感的場合。計算法的基本算法如下; 輸入參數的意義:
unsigned char * puchMsg; /*要進行 CRC校驗的消息* /
?
unsigned short usDataLen; /*消息中的字節數*/
?
/*函數返回unsigned short(即 2個字節)類型的CRC值*/
?
unsigned short CRC16(unsigned char *puchMsg,unsigned short usDataLen)
?
{
?
int i,j; /*循環變量*/
?
unsigned shortusRegCRC =0xFFFF; /*用于保存CRC值*/
?
?
for(i=0;i < usDataLen;i++) /*循環處理傳輸緩沖區消息*/
?
{
?
usRegCRC ^= * puchMsg++; /*異或算法得到CRC值*/
?
for(j=0;j< 8;j++) /*循環處理每個 bit位*/
?
{
?
if (usRegCRC &0x0001)
?
usRegCRC =usRegCRC > >1^0xA001;
?
else
?
usRegCRC > >=1;
?
}
?
}
?
?
return usRegCRC;
?
}
下面舉一個簡單的例子。假設從設備地址為 1,要求讀取輸入寄存器地址30001的值,則RTU模式下的具體查詢消息幀如下:
0x01,0x04,0x00,0x00,0x00,0x01,0x31,0xCA
其中,0xCA31即為CRC值。因為Modbus規定發送時CRC必須低字節在前、高字節在后,因此實際的消息幀的發送順序為0x31,0xCA。
5.4.7 字節序和大小端
Modbus中傳輸的數據,按照“大字節序”來傳輸,比如:

寄存器數值是0x1234,先傳輸0x12,再傳輸0x34。
在 Modbus 寄存器中,對于一個由 2字節組成的16數,在內存中存儲這兩個字節有兩種方法:一種是將低序字節存儲在起始地址為小端(Little-Endian)字節序;另一種方法是將高序字節存儲在起始地稱為大端(Big-Endian)字節序。Modbus 通信協議中具體規定了字節高低位發送順序,這樣就自然引出了字節序和大小端的問題。
- 什么是大端:
所謂大端,是指數據的低位保存在內存的高地址中,數據的高位保存在內存的低地址中。 - 什么是小端:
所謂小端,是指數據的低位保存在內存的低地址中,數據的高位保存在內存的高地址中。 - 為什么會有大小端:
計算機系統是以字節為單位的,每個地址單元都對應著1個字節,一個字節為8bit。但在C語言中除了8bit的char類型,還有16bit的short類型和32bit的long類型,還有就是對于位數大于8位的處理器,如16位或32位的處理器,由于寄存器寬度大于一個字節,那么必然存在一個如何將多個字節安排的問題。因此就導致了大端存儲模式和小端存儲模式的出現。
低位字節和高位字節:好比如:123456 其中的1就是高位數字,6就是低位數字。
舉一個例子,在32位數字0x12345678在內存中的表示形式為:
1)大端模式 :
| 低地址 | —— | —— | 高地址 |
|---|---|---|---|
| 0x12 | 0x34 | 0x56 | 0x78 |
2)小端模式:
| 低地址 | —— | —— | 高地址 |
|---|---|---|---|
| 0x78 | 0x56 | 0x34 | 0x12 |
5.4.8 Modbus報文分析
在第二章中我們已經生成了一個報文,我們就拿此報文來逐步分析一下,報文如下:

我們可以看到上面報文都是循環發送的,這樣看起來不太容易分析,我摘抄下來其中一組來給大家分析
發送:
| 從機地址 | 功能碼 | 起始地址高位 | 起始地址低位 | 寄存器數量高位 | 寄存器數量低位 | CRC高位 | CRC低位 |
|---|---|---|---|---|---|---|---|
| 01 | 03 | 00 | 00 | 00 | 0A | C5 | CD |
響應:
| 從機地址 | 功能碼 | 返回字節數 | 數據位 | CRC高位 | CRC低位 |
|---|---|---|---|---|---|
| 01 | 03 | 14 | 00 42… | CF | 10 |
這里我們就以03功能碼為例來分析一下報文;
- 03發送報文格式:從機地址+功能碼+加起始地址+寄存器數量+CRC校驗
- 03接受報文格式:從機地址+功能碼+字節數+具體數據+CRC校驗
首先我們看一下發送報文:
從機地址是01,功能碼03,起始地址00,寄存器數量是十六進制0A也就是10,和門設置的是一樣的,我們來對對照一下我們設置的參數:

我們再來看一下接收報文:
從機地址是01,功能碼03,返回字節數是十六進制14也就是返回20給字節,我們發送是個返回20個字節也是對的上的,第一個數據位是00 42 也是和我們發送的可以對上,十六機制42,也就66,我們來看一下我們之前設置的參數:

5.4.9 Moubus TCP 消息幀格式
1.協議描述
在Modbus TCP/IP中,串行鏈路中的主/從設備分別演變為客戶端/服務器端設備,即客戶端相當于主站設備,服務器端設備相當于從設備。基于TCP/IP網絡的傳輸特性,串行鏈路上一主多從的機構也演變為多客戶端/多服務器端的構造模型。Modbus協議在TCP/IP上的實現是在TCP/IP層上的應用,它需要一個完整的TCP/IP棧作為支撐,Modbus TCP/IP服務器端通常使用端口502作為接收報文的端口。
下圖為Moubus TCP的通訊結構:

ModbusTCP與ModbusUDP的報文格式是一樣的,它們之間的區別其實就是TCP與UDP的區別,因此下面就針對ModbusTCP的協議進行分析,ModbusTCP與ModbusRtu(ModbusASCII)之間的區別如下圖:

從上圖可以看出,ModbusTCP在Modbus串行通信的基礎上,去除了校驗(由于TCP本身就帶有校驗和)和設備地址(ModbusTCP弱化了設備地址,用IP地址來取代),再加上MBAP報文頭(占7 bytes),下面針對MBAP進行分析說明:
| 字段名 | 長度 | 描述 | 主站(客戶端) | 從站(服務器端) |
|---|---|---|---|---|
| 事務處理標識符(Transaction Identifier) | 2字節 | Modbus 請求/應答傳輸過程的事務處理的識別碼,可以設置為0,也可以設置為每次通訊時自動+1 | 主站(客戶端)生成 | 應答時從主站(客戶端)復制該值 |
| 協議標識符(Protocol Identifier) | 2字節 | 00表示是 Modbus協議,固定值 | 主站(客戶端)生成 | 應答時從主站(客戶端)復制該值 |
| 長度(Lendgth) | 2字節 | 從單元標識符開始,整個PDU的數據長度,分為字節長度Hi和Lo | 請求時生成 | 應答時重新生成 |
| 單元標識符(Unit Identifier) | 1字節 | 串行鏈路或其他總線上連接的遠程從站的識別碼,可以設置為從機設備的地址 | 主站(客戶端)生成 | 應答時從主站(客戶端)復制該值 |
事務處理標識符:
事務處理標識用于在查詢報文與未來響應之間建立聯系。因此,對 TCP/IP 連接來說,在同一時刻這個標識符必須是唯一的。有以下幾種使用此標識符的方式。
例如,可以將傳輸標識作為一個帶有計數器的簡單“TCP 發送順序號”,在每個請求發送時自動+1;也可以用作智能索引或指針,用來識別事務處理的內容,以便記憶當前的遠端服務器和未處理的請求。
服務器端可接收的請求數量取決于其容量,即服務器資源量和 TCP 窗口尺同樣,客戶端同時啟動事務處理的數量也取決于客戶端的資源容量。
單元標識符:
在對Modbus或 Modbus+等串行鏈路子網中的設備進行尋址時,這個域用于路由的目的。在這種情況下,單元標識符(Unit Identifier) 攜帶一個遠端設備的Modbus 從站地址。
如果 Modbus 服務器連接到 Modbus+或 Modbus 串行鏈路子網,并通過一個網橋或網關配置這個服務器的IP 地址,則 Modbus 單元標識符對識別連接到網橋或網關后的子網的從站設備是必需的。TCP 連接中的目的 IP 地址識別了網橋本身的地址,而網橋則使用 Modbus 單元標識符將請求轉交給正確的從站設備。 分配給串行鏈路上的 Modbus 從站設備地址為 1~247(十進制),地址0作為廣播地址。
對單純的 Modbus TCP/IP 設備來說用IP 地址即可尋址 Modbus 服務器端設備,此時 Modbus 單元標識符是無用的,必須使用值0xFF 填充。當對直接連接到 TCP/IP 網絡上的 Modbus 服務器尋址時,建議不要在“單元標識符”域使用有效的Modbus從站地址。
以上是MBAP報文頭個字段含義的詳細說明。
實際上,在 Modbus TCP/IP傳輸過程中,服務端(從機)返回的響應報文中同樣包含 MBAP報頭,除了 Length 字段外,其他字段均與客戶端一致。Modbus消息由 TCP/IP 層提供,不需要像串行鏈路那樣自己判斷一幀是否結束,所有數據傳輸均由 TCP/IP層處理。因為底層的 TCP/IP 確保了端到端的連接,而且 TCP/IP鏈路層已確保傳輸數據的準確性,所以 Modbus TCP/IP 中已不再需要 LRC或CRC 等校驗功能。
2. 查詢與響應報文示例
對于Modbus TCP消息幀格式,下面舉例說明各部分的含義。
- 查詢報文:00 00 00 00 00 06 09 03 00 04 00 01。
- 0x06: 后續還有6字節。
- 0x09: 單元標識符為9。
- 0x03: 功能碼 3,即讀保持寄存器的值。
- 0x00 0x04: Modbus起始地址4(即40005)。
- 0x00 0x01: 讀取寄存器個數為1。
- 響應報文:00 00 00 00 00 05 09 03 02 00 05。
- 0x05:表示后續還有5字節。
- 0x09:同查詢報文,單元標識符。
- 0x03:功能碼,同查詢報文。
- 0x02:返回數據字節數。
- 0x00 0x05:寄存器的值。
可見,在Modbus TCP模式下,差錯校驗字段已不復存在。但在某些特殊場合,例如串行Modbus協議轉Modbus TCP的情況下,串行協議數據可以完整地裝載到Modbus TCP的數據字段,這時CRC或LRC差錯校驗字段仍然存在。例如,Modbus RTU Over TCP/IP 或 Modbus ASCII Over TCP/IP等。
5.5 Moubus功能碼詳解
本節大部分內容參考《Modbus軟件開發實戰指南》。
5.5.1 功能碼概要
Modbus標準在協議中規定了以下3類Modbus功能碼。
- 公共功能碼:
- 被明確定義的功能碼;
- 保證唯一性;
- 由Modbus協會確認,并提供公開的文檔;
- 可進行一致性測試;
- 包括協議定義的功能碼和保留將來使用的功能碼。
- 用戶自定義功能碼:
- 有兩個用戶自定義功能碼區域,分別是65
72和100110; - 用戶自定義,無法保證唯一性。
- 有兩個用戶自定義功能碼區域,分別是65
- 保留功能碼:
保留功能碼因為歷史遺留原因,某些公司的傳統產品現行使用的功能碼不作為公共使用。
5.5.2 01(0x01)讀取線圈
1.功能說明
01功能碼用于讀取從設備的線圈或離散量輸出的狀態,即各Do(Discrete Output,離散輸出)的ON/OFF狀態。消息幀中指定了需要讀取的線圈起始地址和線圈數目。需要注意的是,在Modbus協議規定的PDU中,所有線圈或寄存器地址都必須從0開始計算。
2. 查詢報文
如下表所示,查詢幀的消息中定義了從設備地址為3,并讀取從設備的Modbus地址0001900055(線圈地址0002000056)共計37個狀態值。起始線圈地址為0x13(即十進制00019),因為線圈地址是從0開始計數的。
功能碼01查詢報文示例
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x01 | “0”,“1” | 0x01 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x25 | “2”,“5” | 0x25 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
Modbus協議規定,起始地址由2字節構成,取值范圍為0x0000 ~ 0xFFFF: 線圈數量由2字節構成,取值范圍為0x0001~ 0x07D0 (即+進制1~2000)另外,注意觀察ASCII模式和RTU模式的區別,ASCII模式直接按每4位拆分成對應的字符表示。
3. 響應報文
**在響應報文的數據字段中,每個線圈占用1位 (bit),狀態被表示為1=ON和O=OFF兩種類型。第1個數據字節的LSB(最低有效位)標識查詢報文中的起始地址線圈的狀態值,其他線圈以此類推,一直到這個字節的MSB(最高有效位)為止,并在后續字節中按照同樣的方式(由低到高)排列。**
例如,下表中線圈20~27的狀態值分別是ON - ON - OFF OFF - ON - OFF - ON - OFF表示為二進制則為01010011 (0x53),注意觀察對應的順序。1字節可以表示8個線圈的狀態如果最后的數據字節中不能填滿8個線圈的狀態,則用0填充。對應于查詢報文中需要讀取37個線圈的狀態,共需要5字節保存狀態值。
功能碼01響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x01 | “0”,“1” | 0x01 |
| 數據域字節數 | 0x05 | “0”,“5” | 0x05 |
| 數據1 | 0x53 | “5”,“3” | 0x53 |
| 數據2 | 0x6B | “6”,“B” | 0x6B |
| 數據3 | 0x01 | “0”,“1” | 0x01 |
| 數據4 | 0xF4 | “F”,“B” | 0xF4 |
| 數據5 | 0x1B | “1”,“B” | 0x1B |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 21 | 10 |
5.5.3 02 (0x02) 讀取離散量輸入值
1. 功能說明
02功能碼用于讀取從設備的離散輸入,即DI (Discrete Input) 的ON/OFF狀態。消息頓中指定了需要讀取的離散輸入寄存器的起始地址和數目,可以讀取1 ~ 2000個連續的離散量輸入狀態如果從設備接受主設備的請求則回復功能碼02,并返回離散量且輸入各變量的當前狀態。如果返回的離散輸入數量的個數不是8的整數倍,將用0填充最后的數據字節的剩余位。
2. 查詢報文
如下表所示,查詢頓的消息中定義了從設備的地址為3,并讀取從設備的離散輸入寄存器中地址1010110120 (Modbus地址表示為十進制100119) 共計20個離散輸入狀態值。從下表中可以發現,起始地址為0x64 (即十進制100),因為消息PDU中的Modbus地址從0開始計數。
功能碼02查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x02 | “0”,“2” | 0x02 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x64 | “6”,“4” | 0x64 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x14 | “1”,“4” | 0x14 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
與5.4.2節中的功能碼 (01 (0x01) 讀取線圈/離散量輸出狀態 (Read Coil status/DOs))一樣,本功能碼的起始地址由2字節構成,取值范圍為0x0000 ~ 0xFFFF;離散量數量由2字節構成,取值范圍為0x0001 0x07D0 (即十進制1 2000),最多一次性可讀取2000人離散輸入狀態值。
3. 響應報文
響應報文的各項構成和意義與5.4.2章節的功能碼(01(0x01)讀取線圈/離散量輸出狀態(Re-ad Coil Status/DOs))一樣,如下表所示:
功能碼02響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x02 | “0”,“2” | 0x02 |
| 數據域字節數 | 0x03 | “0”,“3” | 0x03 |
| 數據1 | 0x53 | “5”,“3” | 0x53 |
| 數據2 | 0x6B | “6”,“B” | 0x6B |
| 數據3 | 0x01 | “0”,“1” | 0x01 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 21 | 10 |
5.5.4 03(0x03)讀取保持寄存器值
1. 功能說明
03功能碼用于讀取從設備保持寄存器的內容,不支持廣播模式。消息頓中指定了需要讀取的保持寄存器的起始地址和數目。而保持寄存器中各地址的具體內容和意義則由設備開發者自行規定。
2. 查詢報文
在查詢報文中,必須指定保持寄存器的開始地址和需要讀取的寄存器數量,例如,如下表所示,從設備地址為7 (0x07),需要讀取保持寄存器地址40201 ~ 40203共計3個寄存器的內容即讀取Modbus協議地址200~ 202的內容,在報文中表示如下。
起始地址: 0x00C8 (十進制200)。
讀取數量: 0x0003 (十進制3)。
功能碼03查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x03 | “0”,“3” | 0x03 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0xC8 | “C”,“8” | 0xC8 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x03 | “0”,“3” | 0x03 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
本功能碼的起始地址由2字節構成,取值范圍為0x00000xFFFF;寄存器數量由2字節構成取值范圍為0x00010x007D (即十進制1~125) ,即最多可以連續讀取125個寄存器。
需要特別注意的是,Modbus的保持寄存器和輸入寄存器是以字 (Word) 為基本單位的(1Word=2Byte),所以如果讀取保持寄存器地址為40001開始的一個16位 (bit) 的無符號數,那么返回2字節,并可以從40002開始讀取下一個16位的無符號數。如果需要讀取寄存器地址為40001開始的一個32位浮點數,則需要返回4字節,即必須連續讀取40001和40002的內容,而且下一個32位的浮點數必須從40003開始讀取。對于浮點數(或者32位的整數)而言,連續讀取的兩個寄存器之間存在字節序和大小端的問題,這一點在開發時必須引起注意。
3. 響應報文
響應報文的各項構成和意義如下表所示。因為Modbus的保持寄存器和輸入寄存器是以字為基本單位的,在上面的例子中,查詢報文連續讀取3個寄存器的內容,將返回6字節,參考表下表中數據1~3的高位和低位。
功能碼03響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x03 | “0”,“3” | 0x03 |
| 數據域字節數 | 0x06 | “0”,“6” | 0x06 |
| 數據1(高位) | 0x03 | “0”,“3” | 0x03 |
| 數據1(低位) | 0x53 | “5”,“3” | 0x53 |
| 數據2(高位) | 0x01 | “0”,“1” | 0x01 |
| 數據2(低位) | 0xF3 | “F”,“3” | 0xF3 |
| 數據3(高位) | 0x01 | “0”,“1” | 0x01 |
| 數據3(低位) | 0x05 | “0”,“5” | 0x05 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 23 | 11 |
5.5.5 04 (0x04)讀取輸入寄存器值
1.功能說明
與功能碼03類似,04功能碼用于讀取從設備輸入寄存器的內容,不支持廣播模式。消息頓中指定了需要讀取的輸入寄存器的起始地址和數目,而輸入寄存器中各地址的具體內容和意義則由設備開發者自行規定。
2.查詢報文
在查詢報文中必須指定輸入寄存器的起始地址和需要讀取的寄存器數量。例如,如表4-9所示,從設備地址為7 (0x07),需要讀取輸入寄存器地址30301 ~ 30303共計3個寄存器的內容即讀取Modbus協議地址300 ~302的內容,在報文中表示如下。
起始地址: 0x012C (十進制300) 。
讀取數量: 0x0003 (十進制3)。
功能碼04查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x04 | “0”,“4” | 0x04 |
| 起始地址(高位) | 0x01 | “0”,“1” | 0x01 |
| 起始地址(低位) | 0x2C | “2”,“C” | 0x2C |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x03 | “0”,“3” | 0x03 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
本功能碼中,起始地址由2字節構成,取值范圍為0x0000 ~ 0xFFFF;寄存器數量由2字節構成,取值范圍為0x0001 0x007D (即十進制1125) ,即最多可以連續讀取125個寄存器同樣有一點需要注意,Modbus的保持寄存器和輸入寄存器是以字為基本單位的。所以對于浮點數(或者32位的整數)而言,連續讀取的兩個寄存器之間存在字節序和大小端的問題,這一點在開發時必須引起注意。
3.響應報文
響應報文的各項構成和意義如下表所示。因為Modbus的保持寄存器和輸入寄存器是以字為基本單位的,上面的例子中,查詢報文連續讀取3個寄存器的內容,那么將返回6字節,參考下表中數據1~3的高位和低位。
功能碼04響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x04 | “0”,“4” | 0x04 |
| 數據域字節數 | 0x06 | “0”,“6” | 0x06 |
| 數據1(高位) | 0x03 | “0”,“3” | 0x03 |
| 數據1(低位) | 0x53 | “5”,“3” | 0x53 |
| 數據2(高位) | 0x01 | “0”,“1” | 0x01 |
| 數據2(低位) | 0xF3 | “F”,“3” | 0xF3 |
| 數據3(高位) | 0x01 | “0”,“1” | 0x01 |
| 數據3(低位) | 0x05 | “0”,“5” | 0x05 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 23 | 11 |
5.5.6 05 (0x05)寫單個線圈
1. 功能說明
05功能碼用于將單個線圈寄存器 (或離散輸入)設置為ON或OFF,該功能碼支持廣播模式在廣播模式下,所有從站設備的同一地址的值將被統一修改。查詢報文中的ON/OFF狀態由報文數據字段的常數指定,0xFF00表示ON狀態,0x0000表示OFF狀態。其他值均是非法的,并且對寄存器不起作用,將會返回異常響應。
2. 查詢報文
查詢報文中需要指定從設備地址以及需要變更的線圈地址和設定的狀態值。需要注意的是,在查詢報文中,線圈地址從地址0開始計數。例如,如下表所示,從設備地址為3,設置線圈地址00150為ON狀態,則查詢報文中的線圈地址設置為0x95 (149)。
功能碼05查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x05 | “0”,“5” | 0x05 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
| 變更數據(高位) | 0xFF | “F”,“F” | 0xFF |
| 變更數據(低位) | 0x00 | “0”,“0” | 0x00 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
本功能碼中,起始地址由2字節構成,取值范圍為0x0000~0xFFFF:變更目標數據由2字節構成,取值只能為0xFF00或0x0000。
3.響應報文
響應報文的各項構成和意義如下表所示。對于從設備,在線圈或離散輸出寄存器正常變更的情況下會返回與查詢報文相同的響應報文。如果修改失敗,則會返回一個異常響應,對于異常響應,后續章節會進一步詳細介紹。
功能碼05響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x05 | “0”,“5” | 0x05 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
| 變更數據(高位) | 0xFF | “F”,“F” | 0xFF |
| 變更數據(低位) | 0x00 | “0”,“0” | 0x00 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.7 06 (0x06) 寫單個保持寄存器
1.功能說明
06功能碼用于更新從設備的單個保持寄存器的值,該功能碼支持廣播模式,在廣播模式下所有從設備的同一地址的值將被統一修改。
2. 查詢報文
查詢報文中需要指定從設備地址以及需要變更的保持寄存器地址和設定的值。需要注意的是查詢報文中,寄存器地址從地址0開始計數。例如,如下表所示,從設備地址為3,設置寄存器地址40150為1200 (即0x04B0) ,則查詢報文中的地址字段設置為0x95 (149)。
功能碼06查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x06 | “0”,“6” | 0x06 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
| 變更數據(高位) | 0x04 | “0”,“4” | 0x04 |
| 變更數據(低位) | 0xB0 | “B”,“0” | 0xB0 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
本功能碼中,起始地址由2字節構成,取值范圍為0x00000xFFFF; 變更目標數據由2字節構成,取值范圍為0x00000xFFFF。
3.響應報文
響應報文的各項構成和意義,如下表所示。對于從設備,在保持寄存器正常變更的情況下會返回與查詢報文相同的響應報文。如果修改失敗,則返回個異常響應。
功能碼06響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x03 | “0”,“3” | 0x03 |
| 功能碼 | 0x06 | “0”,“6” | 0x06 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
| 變更數據(高位) | 0x04 | “0”,“4” | 0x04 |
| 變更數據(低位) | 0xB0 | “B”,“0” | 0xB0 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.8 08 (0x08) 診斷功能
1.功能說明
08功能碼僅用于串行鏈路,主要用于檢測主設備和從設備之間的通信故障,或檢測從設備的各種內部故障,該功能碼不支持廣播。為了區別各診斷類型,查詢報文中提供了2字節的子功能碼字段。
通常在正常的響應報文中,從設備將原樣回復功能碼和子功能碼.
2. 查詢報文
查詢報文中需要指定從設備地址、功能碼以及子功能碼。
例如,下表中標識了子功能碼“原樣返回查詢數據”的診斷功能,其中子功能碼為0(0x0000)。在子功能碼為0x0000的情況下,數據字段可以為任意值。各子功能碼的詳細意義可參考下表。
功能碼08查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x08 | “0”,“8” | 0x08 |
| 子功能碼(高位) | 0x00 | “0”,“0” | 0x00 |
| 子功能碼(低位) | 0x00 | “0”,“0” | 0x00 |
| 數據(高位) | 0x04 | “0”,“4” | 0x04 |
| 數據(低位) | 0xB0 | “B”,“0” | 0xB0 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
本功能碼中,子功能碼由2字節構成,取值則根據意義而不同;數據字段由2字節構成,其取值由子功能碼確定。
3.響應報文
響應報文的各項構成和意義如下表所示。對于從設備,在保持寄存器正常變更的情況下會返回與查詢報文相同的響應報文。如果修改失敗,則返回一個異常響應。
功能碼08響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x08 | “0”,“8” | 0x08 |
| 子功能碼(高位) | 0x00 | “0”,“0” | 0x00 |
| 子功能碼(低位) | 0x00 | “0”,“0” | 0x00 |
| 數據(高位) | 0x04 | “0”,“4” | 0x04 |
| 數據(低位) | 0xB0 | “B”,“0” | 0xB0 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
4. 診斷子功能碼
各常用的診斷子功能碼的定義如下。
Return Query Data(00)
| 診斷內容 | 原樣返回查詢報文 |
|---|---|
| 子功能碼 | 0x00,0x00 |
| 查詢報文數據字段 | 任意16位數據 |
| 響應報文數據字段 | 同查詢報文 |
Restart Communication Option(01)
| 診斷內容 | 重啟通信選項; 用于初始化并重新啟動從站設備,清楚所有通信事件計數器; 如果端口處于 Listen Only Mode,則不返回響應;否則在重啟之前返回響應 |
|---|---|
| 子功能碼 | 0x00,0x01 |
| 查詢報文數據字段 | 0x00, 0x00 保持事件記錄; 0xFF,0x00 清除事件記錄 |
| 響應報文數據字段 | 同查詢報文 |
Return Diagnostics Register(02)
| 診斷內容 | 返回診斷寄存器 |
|---|---|
| 子功能碼 | 0x00,0x04 |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 診斷寄存器的內容 |
Force Listen Only Mode
| 診斷內容 | 強制只聽模式; 強制被尋址的從站設備進入只聽模式,使得此設備與網絡中的其他設備斷開,不返回響應 |
|---|---|
| 子功能碼 | 0x00,0x04 |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 不返回響應 |
Clear Counters and Diagnostic Register
| 診斷內容 | 清除計數器和診斷寄存器 |
|---|---|
| 子功能碼 | 0x00,0x0A |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 同查詢報文 |
Return Bus Message Count(11,0x0B)
| 診斷內容 | 返回總線報文計數 |
|---|---|
| 子功能碼 | 0x00,0x0B |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回報文的技數值 |
Return Bus Communication Error Count(12,0x0C)
| 診斷內容 | 返回總線通信CRC差錯計數 |
|---|---|
| 子功能碼 | 0x00,0x0C |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回報文的CRC出錯總數 |
Return Bus Exception Error Count(13,0x0D)
| 診斷內容 | 返回總線異常差錯計數 |
|---|---|
| 子功能碼 | 0x00,0x0D |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回異常響應的總數 |
Return Slave Message Count(14,0x0E)
| 診斷內容 | 返回從站設備報文總數 |
|---|---|
| 子功能碼 | 0x00,0x0E |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回從站設備接收報文總數 |
Return Slave No Response Counrt(15,0x0F)
| 診斷內容 | 返回從站設備無響應計數 |
|---|---|
| 子功能碼 | 0x00,0x0F |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回加電后沒有返回響應的報文的保文數量 |
Return Slave Busy Count(17,0x11)
| 診斷內容 | 返回從站設備忙計數 |
|---|---|
| 子功能碼 | 0x00,0x11 |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回加電后異常響應忙的報文數量 |
Return Bus Character Overrun Count(18,0x12)
| 診斷內容 | 返回總線字符超限計數 |
|---|---|
| 子功能碼 | 0x00,0x12 |
| 查詢報文數據字段 | 0x00,0x00 |
| 響應報文數據字段 | 返回超限的報文數量 |
5.5.9 11 (0x0B) 獲取通信事件計數器
1. 功能說明
11功能碼主要用于獲取從設備通信計數器中的狀態字和事件計數的值,本功能碼不支持廣播模式。通過在通信報文之前和之后讀取通信事件計數值,可以確定從設備是否正常處理報文。
對于正常完成報文處理和傳輸的場合,事件計數器增加1;而對于異常響應、輪詢命令或讀事件計數器(即Ox0B功能碼)的場合,則計數器不變。通過【0x08診斷功能】 中的子功能碼【Restart Communication Option ( 0x0001 )】 和 【Clear Counters and Diagnostic Register (0x000A) 】 可以復位事件寄存器。
2. 查詢報文
下表中的示例表示獲取通信事件計數器的查詢報文內容,其中從站設備地址為5.
功能碼11查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0B | “0”,“B” | 0x0B |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 9 | 4 |
3. 響應報文
對于從設備,在正常情況下,響應報文返回2字節的狀態字和2字節的事件計數。其中,如果從站設備處于忙狀態,那么狀態字為0xFFFF,否則狀態字為0x0000。在表4-18的示例中,狀態字為0x0000,表示從站設備外于空閑狀態。事件計數的值為0x03E8,表示記錄了1000 (0x03E8)個事件。
功能碼11響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0B | “0”,“B” | 0x0B |
| 子功能碼(高位) | 0x00 | “0”,“0” | 0x00 |
| 子功能碼(低位) | 0x00 | “0”,“0” | 0x00 |
| 數據(高位) | 0x03 | “0”,“3” | 0x03 |
| 數據(低位) | 0xE8 | “E”,“8” | 0xB8 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.10 12 (0x0C) 獲取通信事件記錄
1. 功能說明
12功能碼主要用于從從設備獲取狀態字、事件計數、報文計數以及事件字節字段。其中,狀態字和事件計數與功能碼11 (0x0B) 獲取的值一致。報文計數包含加電重啟、清除計數器之后的報文數量,報文計數與通過診斷功能碼08 (0x08)、子功能碼11 (0x0B) 獲取的值一致。事件字節字段包含0~64字節,用來定義各種事件。
2. 查詢報文
下表中的示例表示獲取通信事件記錄的查詢報文內容,其中從站地址位5。
功能碼12查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0C | “0”,“C” | 0x0C |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 9 | 4 |
3. 響應報文
對于從站設備,在正常情況下,響應報文包括一個2字節的狀態字字段、一個2字節的事件計數字段、一個2字節的消息計數字段以及0~ 64字節的事件字段。因為事件字段是變長的,所以增加了一個1字節的數據長度字段,以方便讀取響應數據,如下表所示;
功能碼12響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0C | “0”,“C” | 0x0C |
| 字節數 | 0x08 | “0”,“8” | 0x08 |
| 狀態字(高位) | 0x00 | “0”,“0” | 0x00 |
| 狀態字(低位) | 0x00 | “0”,“0” | 0x00 |
| 事件計數(高位) | 0x03 | “0”,“3” | 0x03 |
| 事件計數(低位) | 0xE8 | “E”,“8” | 0xE8 |
| 消息計數(高位) | 0x01 | “0”,“1” | 0x01 |
| 消息計數(低位) | 0xF6 | ”F“,”6“ | 0xF6 |
| 事件0 | 0x20 | ”2“,”0“ | 0x20 |
| 事件1 | 0x00 | ”0“,”0“ | 0x00 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.11 15(0x0F)寫多個線圈
1.功能說明
15功能碼用于將連續的多個線圈或離散輸出設置為ON/OFF狀態,支持廣播模式,在廣播模式下,所有從站設備的同一地址的值將被統一修改。15功能碼中,起始地址字段由2字節構成,取值范圍為0x00000xFFFF:而寄存器數量字段由2字節構成,取值范用為0x00010x07B0.
2. 查詢報文
查詢報文中包含請求數據字段,用于定義ON或OFF狀態。數據字段中為邏輯1的位對應ON;邏輯0的位對應OFF。其中,ON/OFF與數據字段的對應關系可參考前面的童節“01 (0x01) 讀取線圈,離散量輸出狀態 (Read Coil status/DOs)” 中的內容舉例說明,假設從站設備地址為5,需要設置線圈地址20 ~30的狀態如下表所示。
線圈狀態:
| 值 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 線圈 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | — | — | — | — | — | 30 | 29 | 28 |
那么,寫入的數據字段被劃分為2字節,值分別為0xD1,對應于27 ~ 20的線圈,值0x05對應于30~28的線圈,注意細體會其中的高低位的對應關系。需要注意的是,在查詢報文中,Modbus協議的起始地址為19 (0x13) ,即比線圈起始地址20少1。如下表所示,其中字節數字段表示需要變更數據的字節總數。
功能碼15查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0F | “0”,“F” | 0x0F |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x0B | “0”,“B” | 0x08 |
| 字節數 | 0x02 | “0”,“2” | 0x02 |
| 變更數據(高位) | 0xD1 | ”D“,”1“ | 0xD1 |
| 變更數據(低位) | 0x05 | ”0“,”5“ | 0x05 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 23 | 11 |
3. 響應報文
對于從設備,在正常情況下,響應報文包括功能碼、起始地址以及寫入的線圈數量,如下表所示。
功能碼15響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x0F | “0”,“F” | 0x0F |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x0B | “0”,“B” | 0x08 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.12 16 (0x10) 寫多個保持寄存器
1. 功能說明
16功能碼用于設置或寫入從設備保持寄存器的多個連續的地址塊 (1 ~ 123個寄存器),支持廣播模式,在廣播模式下,所有從站設備的同一地址的值將被統一修改。本功能碼中,起始地址字段由2字節構成,取值范圍為0x0000 ~ 0xFFFF;而寄存器數量字段由2字節構成,取值范圍為0x0001~0x007B。
2. 查詢報文
查詢報文包含請求數據字段。數據字段保存需要寫入的數值,各數據按每個寄存器2字節存放。舉例說明,從站設備地址為5,需要將保持寄存器地址40020 ~ 40022設置為如下表所示的數值。
寄存器的設置:
| 寄存器地址 | 設定值 | 寄存器地址 | 設定值 |
|---|---|---|---|
| 40020 | 0x0155 | 40022 | 0x0157 |
| 40021 | 0x0156 |
對應于40020~40022的寄存器,注意仔細體會其中的高低位的對應關系。需要注意的是,在查詢報文中,Modbus協議的起始地址為19 (0x13),即比寄存器起始地址20少1。如下表所示,其中字節數字段表示需要變更數據的字節總數。
功能碼16查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x10 | “0”,“F” | 0x0F |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x03 | “0”,“B” | 0x08 |
| 字節數 | 0x06 | ”0“,”6“ | 0x06 |
| 變更數據1(高位) | 0x01 | ”0“,”1“ | 0x01 |
| 變更數據1(低位) | 0x55 | ”5“,”5“ | 0x56 |
| 變更數據2(高位) | 0x01 | ”0“,”1“ | 0x01 |
| 變更數據2(低位) | 0x56 | ”5“,”6“ | 0x56 |
| 變更數據3(高位) | 0x01 | ”0“,”1“ | 0x01 |
| 變更數據3(低位) | 0x57 | ”5“,”7“ | 0x57 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 31 | 15 |
3. 響應報文
對于從設備,在正常情況下,響應報文包括功能碼、起始地址及寫入的寄存器數量,如下表所示。
功能碼16響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x10 | “1”,“0” | 0x10 |
| 起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
| 起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x03 | “0”,“3” | 0x03 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
在實際開發過程中,功能碼“16 (0x10) 寫多個寄存器 (Preset Multiple Registers) ”通常用于方便用戶寫入多字節類型的數據。
例如,假設從站設備地址為5,需要向保持寄存器寫入一個32位 (4字節) 的浮點數,那么此浮點數將占用2個寄存器地址。假設浮點數將存放在40001和40002寄存器中,設定值為1.235(即0x3F9E 147A)實際的查詢和響應報文如下(其中標記部分為設定的浮點數值,假設字節序為AB-CD,參考第5.3.7章字節序和大小端的內容)。
查詢報文: 05 10 00 00 00 02 04 3F 9E 14 7A 05 86
響應報文: 05 10 00 00 00 02 40 4C
對于64位(8字節)的雙精度浮點數,同理將占用4個寄存器地址共8字節的空間。特別需要注意的是字節序及大小端的問題,前面討論過多字節存在大小端問題,因此主站設備和從站設備必須保持一致的規則處理,約定Modbus傳輸中的數據字段的字節序,否則會因為大小端不一致而產生數據處理錯誤。
5.5.13 17 (0x11) 報告從站ID (僅用于串行鏈路)
1. 功能說明
17功能碼用于讀取從站設備的ID、類型描述、當前狀態以及其他信息,不支持廣播模式。響應消息的構成依賴于設備而不盡相同。
2. 查詢報文
查詢報文中不包含請求數據字段。舉例說明,從站設備地址為5,獲取相關信息,如下表所示。
功能碼17查詢報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x11 | “1”,“1” | 0x11 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 9 | 4 |
3. 響應報文
**對于從設備,在正常情況下,響應報文包括從站ID、運行狀態以及其他附加信息,如下表所示。運行狀態字段占用1字節,且0x00=OFF,0xFF=ON,而響應報文的組成則由開發者決定。**
功能碼17響應報文示例:
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x05 | “0”,“5” | 0x05 |
| 功能碼 | 0x11 | “1”,“1” | 0x11 |
| 字節數 | 設備相關 | 設備相關 | 設備相關 |
| 從設備ID | 設備相關 | 設備相關 | 設備相關 |
| 運行狀態 | 0xFF | “F”,“F” | 0xFF |
| 附加情報1 | 設備相關 | 設備相關 | 設備相關 |
| …… | 設備相關 | 設備相關 | 設備相關 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
5.5.14 Modbus異常響應
以上介紹了一些常見的公共功能碼的報文 (消息頓)構成,廣播模式以外的查詢報文都希望能夠獲取一個正常的響應報文。在通常情況下,從站設備將返回一個正常響應報文,但是在某些特殊情況下將返回異常響應報文。
對于查詢報文,存在以下4種處理反饋:
- 正常接收,正常處理,返回正常響應報文;
- 因為通信錯誤等原因造成從站設備沒有接收到查詢報文,主站設備將按超時處理:從站設備接收到的查詢報文存在通信錯誤 (如LRC、CRC錯誤等),此時從站設備將丟棄報文不響應,主站設備將按超時處理;
- 從站設備接收到正確的報文,但是超過處理范圍(如不存在的功能碼或者寄存器等),此時從站設備將返回包含異常碼 (Exception Code) 的響應報文。
- 異常響應報文由從站地址、功能碼以及異常碼構成。其中,功能碼與正常響應報文不同,在異常響應報文中,功能碼最高位 (即MSB) 被設置為1。因為Modbus協議中的功能碼占用1字節故用表達式描述為:
異常功能碼=正常功能碼+0x80
舉例說明,如下表所示,查詢報文的起始地址為0x012C (十進制300) ,即需要讀取寄存器地址為30301開始的值。若從站設備中不存在輸入寄存器30301,則從站設備將返回一個異常響應報文,參見下表的功能碼和異常碼。
異常響應示例(功能碼04查詢報文 ):
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x04 | “0”,“4” | 0x04 |
| 起始地址(高位) | 0x01 | “0”,“1” | 0x01 |
| 起始地址(低位) | 0x2C | “2”,“C” | 0x2C |
| 寄存器數(高位) | 0x00 | “0”,“0” | 0x00 |
| 寄存器數(低位) | 0x03 | “0”,“3” | 0x03 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 17 | 8 |
異常響應示例(功能碼04響應報文 ) :
| 字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
|---|---|---|---|
| 幀頭 | “:” | ||
| 從設備地址 | 0x07 | “0”,“7” | 0x07 |
| 功能碼 | 0x84 | “8”,“4” | 0x84 |
| 異常碼 | 0x02 | “0”,“2” | 0x02 |
| 差錯校驗 | LRC(2字符) | CRC(2字節) | |
| 幀尾 | CR/LF | ||
| 合計字節數 | 11 | 5 |
常見的異常碼如下表所示:
常見異常碼說明:
| 異常碼 | 名稱 | 說明 |
|---|---|---|
| 01 | 非法功能碼 | 從站設備不支持此功能碼 |
| 02 | 非法數據地址 | 指定的數據地址在從站設備中不存在 |
| 03 | 非法數據值 | 指定的數據超過范圍或者不允許使用 |
| 04 | 從站設備故障 | 從站設備處理響應的過程中出現未知錯誤等 |
-
寄存器
+關注
關注
31文章
5608瀏覽量
129957 -
MODBUS
+關注
關注
28文章
2457瀏覽量
83183 -
通訊協議
+關注
關注
10文章
298瀏覽量
21484
發布評論請先 登錄
第5章_Modbus通訊協議
評論