網路基礎 Modbus協議學習總結

授客發表於2024-07-21

協議簡介

Modbus協議,首先從字面理解它包括Mod和Bus兩部分,首先它是一種bus,即匯流排協議,匯流排就意味著有主機,有從機,這些裝置在同一條匯流排上。

Modbus支援單主機,多個從機,最多支援247個從機裝置。關於Mod,因為這種協議最早被用在PLC控制器中,準確的說是Modicon公司的PLC控制器,這也是Modbus名稱的由來。後來Modicon被施耐德電器收購,Modbus協議廣泛應用在工業控制器、HMI和感測器上,逐漸被其他廠商所接受,成為工業領域通訊協議的業界標準,並且現在是工業電子裝置之間常用的連線方式。

  • 每種裝置(PLC、HMI、控制皮膚、驅動程式、動作控制、輸入/輸出裝置)都能使用 Modbus協議來啟動遠端操作。

  • 在基於序列鏈路和以太 TCP/IP 網路的 Modbus 上可以進行相互通訊。

  • 一些閘道器允許在幾種使用 Modbus 協議的匯流排或網路之間進行通訊

術語說明

HDLC 高階資料鏈路控制
HMI 人機介面
IETF 因特網工程工作組
I/O 輸入/輸出裝置
IP 互連網協議
MAC 介質訪問控制
MB Modbus 協議
MBAP Modbus 協議

關於匯流排補充說明

匯流排(Bus)是計算機各種功能部件之間傳送資訊的公共通訊幹線,它是由導線組成的傳輸線束, 按照計算機所傳輸的資訊種類,計算機的匯流排可以劃分為資料匯流排地址匯流排控制匯流排,分別用來傳輸資料、資料地址和控制訊號。匯流排是一種內部結構,它是cpu、記憶體、輸入、輸出裝置傳遞資訊的公用通道,主機的各個部件透過匯流排相連線,外部裝置透過相應的介面電路再與匯流排相連線,從而形成了計算機硬體系統。在計算機系統中,各個部件之間傳送資訊的公共通路叫匯流排,微型計算機是以匯流排結構來連線各個功能部件的。

Modbus被廣泛使用主要原因

  1. 標準、開放,使用者可以免費、放心地使用Modbus協議,不需要交納許可證費,也不會侵犯智慧財產權
  2. Modbus可以支援多種電氣介面,如RS-232、RS-485等,還可以在各種介質上傳送,如雙絞線、光纖、無線等。  
  3. Modbus的幀格式簡單、緊湊,通俗易懂。使用者使用容易,廠商開發簡單。

協議版本

Modbus 協議目前分別定義了基於串列埠()和乙太網傳輸資料的協議,其中串列埠(RS232,RS485,RS422,光纖,無線等)資料傳輸協議分為 Modbus RTU 和 Modbus ASCII序列鏈路協議,乙太網資料傳輸協議一種 Modbus TCP/IP協議。

其中,Modbus RTU是一種緊湊的,採用二進位制表示資料的方式,Modbus ASCII是一種人類可讀的,冗長的表示方式。

工作模式

Modbus 協議採用主從(Master/Salve)通訊方式,TCP/IP傳輸模式下,主節點也叫客戶機,從節點也叫伺服器。連線到匯流排上的所有主機節點中有且只有一個 Master 節點(主節點),其餘為 Slave 節點(從節點,可選地址為1~247,每個Slave節點的地址必須唯一)。

主從通訊以 請求/應答 為主,每次通訊都是主節點先傳送請求(採用廣播模式、單播模式),從節點響應指令,並按要求應答,或者報告異常。當主節不傳送請求時,從節不會自己發出資料,從節點之間不能相互通訊(也就是說從節點之間不能相互傳送請求)。

無論主節點傳送的是廣播指令還是單播指令,實際上所有從節點都會完整接收指令。但傳送單播指令時,只有地址和指令中中指定地址相同的從節點才會執行及回應指令,其它從節點將忽略收到的指令,而廣播請求所有收到指令的裝置都會執行指令,但不會給主機回應指令。

半雙工通訊

Modbus 由於請求/應答機制所以不能同步通訊(同步通訊需要收發雙方以相同的節奏傳送和接收資料),匯流排上每次只有一幀資料進行傳輸,屬於半雙工通訊。

Modbus 沒有支援繁忙機制處理,例如主機給從機傳送命令, 如果從機正在處理其他任務,此時從機將無法響應主機,所以需要透過軟體的方式來判斷是否正常接收。

Modbus訊息幀

Modbus 協議定義了一個與基礎通訊層無關的簡單協議資料單元(PDU -> 功能碼 + 資料 部分)。特定匯流排或網路上
的 MODBUS 協議對映能夠在應用資料單元(ADU -> 地址域 + 功能碼 + 資料 + 差錯校驗)上引入一些附加域

通用資料幀

地址域 功能碼 資料 差錯校驗碼
1位元組 1位元組 N位元組 CRC: 16位元組 LRC: 1位元組

說明:每個劃分欄位都用16進製表示

  • 地址域

    從機裝置地址,通常1-247為有效地址,0為廣播地址(用於接收主機的廣播資料),每個從機在匯流排上地址必須唯一,只有與主機傳送的地址碼相符的從機才能響應返回資料

    主節點透過將要聯絡的從節點的地址放入訊息中的地址域來選取需要通訊的從裝置。當從節點傳送回應訊息時,需要把自己的地址放入回應的地址域中,以便主節點知道是哪一個裝置作出的回應。

  • 功能碼

    表明主節點請求資料的型別。

    當主節點向從裝置傳送訊息時,功能碼將告訴從裝置需要執行哪些行為。例如去讀取輸入的開關狀態,讀一組暫存器的資料內容等。

  • 資料

    包含暫存器地址暫存器資料

  • 差錯校驗

    對資料進行冗餘校驗的結果,CRC、LRC

其中事務處理正常時,客戶機向伺服器傳送請求,在功能碼中填充功能碼代號,說明伺服器需要執行的動作,在資料碼區填充具體的要求,比如讀暫存器的地址和數量,通訊正常時伺服器會在返回的通訊幀的功能碼區中填充一個操作碼,該操作碼=功能碼,在通訊幀的資料區填充返回的取樣資料。

當出現事務處理異常時,伺服器會在返回的通訊幀的功能碼中填充一個差錯碼,該差錯碼 = 功能碼 + 0x80,即將功能碼的最高位置1代表出現錯誤。並在後面的資料段中填充錯誤碼,用來指示本次通訊的錯誤具體內容。

Modbus ASCII訊息幀

起始位(😃 + ADU + 結束符

起始位 地址域 功能碼 資料 LRC 結束符
: 1位元組 1位元組 N位元組 1位元組 2個字元

說明:訊息以 : 冒號字元(ASCII 碼16進製表示 3A)開始,以回車換行符(CR LF, ASCII 碼16進製表示 0D0A)結束。

一個典型 ASCII 訊息幀如下

起始位 地址域 功能碼 資料 LRC 結束符
: 2個字元 2個字元 0 到 2x252 字元 2個字元 2個字元

Modbus RTU 訊息幀

地址域 功能碼 資料 CRC低位元組 CRC高位元組
1位元組 1位元組 0 到 252 位元組 1位元組 1位元組

說明:RTU 通訊模式下,其傳送的位元組資料即為原始位元組資料,接收端接收後無需再次轉換。

注意:

  1. RTU 模式,資料幀之間必須至少間隔 3.5 個字元時間,透過時間區間來區分報文,如下:

  1. RTU 模式下,整個報文幀必須以連續的字元流傳送,如果兩個字元之間的空閒間隔大於 1.5 個字元時間,則報文幀被認為不完整應該被接收節點丟棄

  1. 字元時間
    所謂字元時間指的是傳輸一個 ASCII 字元需要花費的時間,一個 ASCII 字元包含 1 個位元組(8 bits),所以傳輸一個字元需要花費傳輸 8 個資料位的時間(所以這裡字元傳輸時間指代傳輸1位元組資料消耗的時間)。

    然而實際上傳輸 1 個位元組資料需要花費的時間並不只 8 個位時間,因為除了傳輸固有的 1 位元組資料,還需要傳輸一些輔助功能位。例如傳送 1 個位元組需要固定起始位 1 位,資料位 8 位,校驗位 1 位(可選的),停止位 1 位,其中 8 位資料位才是真正的有效資料,所以有如下公式來計算字元時間。

    字元時間=1s / 波特率 × 字元的位元組總位數。

    例如:固定起始位 1 位,資料位 8 位,奇/偶校驗位 1 位,停止位 1 位,波特率為9600 bps,計算單個字元傳輸時間為:

    字元時間 = 1000 ms / 9600 × ( 1 + 8 + 1 + 1 ) = 1.145833 ms

Modbus TCP/IP 訊息幀

MBAP 報文頭 + PDU(此處PDU來自資料鏈路層的PDU)

事務元識別符號 協議識別符號 長度 單元識別符號 功能碼 資料
2位元組 2位元組 2位元組 1位元組 1位元組 N位元組

MBAP 報文頭包括下列域:

  • 事務元識別符號

    Modbus 請求/響應事務處理的識別,可以理解為報文的序列號,一般每次通訊之後就要加 1 以區別不同的通訊資料包文

  • 協議識別符號

    00 00 表示Modbus 協議 客戶機啟動

  • 長度

    表示接下來的資料長度,包括單元識別符號和資料域

  • 單元識別符號

    序列鏈路或其它匯流排上連線的遠端從節點的識別碼,可以理解為從節點地址

資料編碼

MODBUS 使用一個Big-Endian (低地址位存放最高有效位元組) 表示地址和資料項。這意味著當發射多個位元組時,首先傳送最高有效位。

例如:
暫存器大小 值

16 0x1234 傳送的第一位元組為 0x12, 然後 0x34

參考閱讀:Big Endian和Little Endiand的區別

資料模型

為了抽象 PLC 中可訪問的資料,Modbus 協議定義了 資料模型 概念,資料模型定義了四種可訪問的資料型別:

型別 大小 訪問許可權 元素地址字首編碼 元素地址範圍(0~65535) 元素地址範圍(1~9999)
輸出線圈(Coils) 1 Bit 可讀可寫 0 000000~065535 00000~09999
輸入離散量(Discrete Input) 1 Bit 只讀 1 100000~165535 10000~19999
輸入暫存器(Input Registers) 16 Bit 只讀 3 300000~365535 30000~39999
保持暫存器(Holding Registers) 16 Bit 可讀可寫 4 400000~465535 40000~49999

實際上以上的資料型別都屬於可程式設計邏輯控制器(PLC)中的術語,可以簡單理解為用來存放資料的容器,線圈通常用於表示開關狀態(如繼電器的通或斷),而暫存器通常用於儲存線性或非線性的數值資料。

資料模型中的每一種資料型別都最多允許有 65536 個元素,元素的地址編號從 0 開始,因此地址的範圍為:0-65535。

需要說明的是:65536 是 Modbus 協議允許的最大元素範圍,實際應用中一般不需要這麼大的儲存區,因此 PLC 廠家普遍採用的是 10000 以內的地址範圍。

引入元素地址字首編碼,是為了簡化資料模型與裝置儲存區的對應關係。

參考連結:https://blog.csdn.net/jf_52001760/article/details/130192127

資料模型是一種抽象,在實際使用時必須將其對映到真實的物理儲存區才能被訪問。

Modbus 協議允許裝置將四種資料型別分別對映到不同的儲存區塊中,各個區塊之間相互獨立,使用不同的功能碼可讀取到不同的數值,如下圖所示

帶有多個獨立塊的裝置

僅有1個塊的裝置

功能碼

功能碼整體可以分成三類:

  • 公共功能碼
  • 使用者自定義功能碼([65, 72], [100, 110])
  • 保留功能碼

常用功能碼

功能碼 名稱 操作型別 功能描述
01 讀線圈狀態 位操作 讀位(讀 N 個 bit)讀從機線圈暫存器
02 讀輸入離散量 位操作 讀位(讀 N 個 bit)讀離散輸入暫存器
03 讀保持暫存器 位元組操作 讀整型,字元型,狀態字,浮點型(讀 N 個 word)讀保持暫存器
04 讀輸入暫存器 位元組操作 讀整型,狀態字,浮點型(讀 N 個word)讀輸入暫存器
05 寫單個線圈 位操作 寫位(寫 1 個 bit)寫線圈暫存器
06 寫單個保持暫存器 位元組操作 寫整型,字元型,狀態字,浮點型(寫一個 word)寫保持暫存器,
0F 寫多個線圈 位操作 寫位(寫 N 個 bit)強置一串連續邏輯線圈的通斷
10 寫多個保持暫存器 位操作 寫整形,字元型,狀態字,浮點型(寫 N 個 word)把具體的二進位制值裝入一串連續的保持暫存器

請求和應答報文示例

Modbus RTU通訊

示例1:寫單個暫存器。向01地址裝置0x0105保持暫存器寫入1個資料:0x0190

主機傳送: 01 06 01 05 01 90 99 CB
從機回覆: 01 06 01 05 01 90 99 CB

說明:01表示從機地址,06功能碼錶示寫單個保持暫存器,01 05表示暫存器地址,01 90 表示寫入暫存器的數值,99 CB為CRC校驗值。

可以看出,當寫1個暫存器資料時,從機響應的資料幀和主機傳送的資料幀完成一致。

附:CRC(迴圈冗餘校驗)線上計算地址:http://www.ip33.com/crc.html

CRC-16程式碼實現

# -*- coding:utf-8 -*-

'''生成  CRC  的過程為:
1.將一個 16 位暫存器裝入十六進位制 FFFF,將之稱作 CRC 暫存器.
2.將報文的第一個8位位元組與上述 CRC 暫存器的低位元組異或,結果置於 CRC 暫存器.
3.將 CRC 暫存器右移 1 位  (向 LSB(Least Significant Bit,最低有效位) 方向),  MSB(Most Significant Bit,最高有效位) 充零。提取並檢測 LSB。
4.如果 LSB 為 0,則重複步驟 3 (另一次移位).
  如果 LSB 為 1: 對 CRC 暫存器異或多項式值 0xA001 (對應16位二進位制:1010 0000 0000 0001)
5.重複步驟 3 和 4,直到完成 8 次移位。當做完此操作後,將完成對 8 位位元組的完整操作。
6. 對報文中的下一個位元組重複步驟 2 到 5,繼續此操作直至所有報文被處理完畢。
7. CRC  暫存器中的最終內容為 CRC  值.
8. 當放置 CRC 值於報文時,需要交換CRC高低位元組。
'''

def hex_char_to_int(hex_char):
    '''
    :param hex_char: 16進製表示的字元
    :return: 位元組
    '''

    return "0123456789ABCDEF".find(hex_char)

def hex_string_to_bytes(hex_string):
    '''
    16進位制字串轉為位元組陣列
    :param hex_string:  16進製表示的字串
    :return: 位元組陣列
    '''

    hex_string = hex_string.strip()
    hex_string_len = len(hex_string)

    if not hex_string_len:
        return

    byte_array = [] # 存放最終結果
    hex_string = hex_string.upper()

    for i in range(int(hex_string_len/2)):
        high_digit = hex_char_to_int(hex_string[2*i]) # 獲取高4位的10進製表示
        low_digit = hex_char_to_int(hex_string[2*i+1])  # 獲取低4位的10進製表示

        # high_digit 左移4位,形成位元組高4位
        byte_array.append(high_digit << 4 | low_digit) # 透過異或運算,得到位元組

    if hex_string_len % 2 == 1: # 不能整除時,最後一個1個字元的處理
        byte_array.append(0x00 | hex_char_to_int(hex_string[hex_string_len-1]))

    return byte_array



def bytes_to_hex_string(byte_list):
    '''
    位元組轉為16進位制字串
    :param byte_list: 位元組陣列
    :return: 位元組陣列對應的大寫16進位制字串表示
    '''

    hex_string = ""
    for i in range(len(byte_list)):
        # [2:] 去掉 '0x'
        # zfill(2) 1位元組需要2位16進位制字元來表示,不夠時填充0
        hex_string += hex(byte_list[i] & 0xFF)[2:].zfill(2)
    return hex_string.upper()


def calculate_crc(data):
    reg_crc = 0xFFFF
    for byte in data:
        reg_crc ^= byte
        for _ in range(8):
            if reg_crc & 0x0001: # 這裡直接透過與運算,就可以判斷最低有效位是否為0,和步驟3操作等價的
                reg_crc = (reg_crc >> 1) ^ 0xA001
            else:
                reg_crc >>= 1
    return reg_crc.to_bytes(2, 'big') # 按大端模式返回代表整數的2位元組資料

# 測試
string_data = '01 06 01 05 01 90'
string_data = string_data.replace(' ', '')
byte_list = bytes(string_data, encoding='utf-8')  # 注意,這裡不能用程式碼:實現,這樣是錯誤的,獲取不到正確結果
byte_list = hex_string_to_bytes(string_data)
crc_value  = calculate_crc(byte_list)
print(crc_value, bytes_to_hex_string(crc_value)) # 輸出:b'\xcb\x99' CB99

final_crc = ''
for byte in crc_value:
    final_crc = str(hex(byte)).lstrip('0x') +  ' ' + final_crc
print(final_crc.upper()) # 輸出:99 CB

示例2:寫多個暫存器。向01地址裝置0x0105、0x0106、0x0107地址保持暫存器,寫入3個暫存器資料:0x1102, 0x0304, 0x0566

主機傳送:01 10 01 05 00 03 06 11 02 03 04 05 66 4A 12
從機回覆:01 10 01 05 00 03 91 F5

說明:01從機地址,10功能碼錶示寫多個保持暫存器,01 05表示起始地址,00 03表示寫3個暫存器,06表示資料量為6個位元組,11 02/03 04/05 66分別表示寫入3個暫存器的數值,4A 12 表示CRC校驗數值。

示例3:讀單個暫存器。讀01地址裝置0x0105保持暫存器資料。

主機傳送:01 03 01 05 00 01 95 F7
從機回覆:01 03 02 56 78 87 C6

說明:

03表示讀多個暫存器,0105表示起始地址,00 01表示讀1個暫存器

02表示2個位元組,56 78表示暫存器的資料。

示例4:讀多個暫存器。讀01地址裝置0x0105、0x0106、0x0107地址保持暫存器,共3個暫存器資料。

主機傳送:01 03 01 05 00 03 14 36
從機回覆:01 03 06 11 22 33 44 55 66 2A 18

說明:

03表示讀多個暫存器,01 05表示起始地址,00 03 表示讀3個暫存器

06表示6個位元組,11 22 33 44 55 66表示暫存器的資料。

Modbus ASCII

例子:向地址為0x01的從裝置的0x0405地址,寫入數值0x1234,報文如下:

主機傳送請求: :01 06 04 05 12 34 AA <CR><LF>

說明:01表示裝置地址,06表示寫單個保持暫存器。04 05 表示暫存器地址,12 34 表示資料,AA 表示LRC校驗值。實際進行校驗的資料不包含起始符(:)和結束符(<CR><LF>)。

附:LRC校驗(縱向冗餘校驗)線上計算地址:http://www.ip33.com/lrc.html

LRC程式碼實現

string  = '01 06 04 05 12 34'
total = 0
for item in string.split(' '):
    total += int(item, 16)

result = total % 256

hex_lrc_vale = hex(256 - result)

錯誤碼

常用錯誤碼如下表所示。

異常碼 名稱 描述
01 (01H) 非法功能 在請求中接收的功能程式碼不是從裝置的一個授權操作。從裝置可能處於錯誤狀態,無法處理特定請求。
02 (02H) 非法資料地址 從裝置接收的資料地址不是從裝置的一個授權地址
03 (03H) 非法資料值 指定的資料超過範圍或者不允許使用。
04 (04H) 從站裝置故障 從裝置未能執行一個請求的操作,因為出現了一個無法修復的錯誤
05 (05H) 確認 確認 從站裝置已經接受請求,並且正在處理這個請求,但是需要長持續時間進行這些操作,返回這個響應防止在客戶機(或主站)中發生超時錯誤,客戶機(或主機)可以繼續傳送輪詢程式完成報文來確認是否完成處理
06 (06H) 從站裝置忙 從裝置忙於處理另一個命令。主裝置必須在從裝置空閒後傳送請求
07 (07H) 否定確認 從站裝置無法執行主站裝置傳送的請求
08 (08H) 儲存奇偶性差錯 從裝置在嘗試讀取擴充套件儲存器的時候從儲存器中檢測到一個奇偶校驗錯誤
10 (0AH) 不可用的閘道器路徑 與閘道器一起使用,指示閘道器不能為處理請求分配輸入埠值輸出埠的內部通訊路徑。通常意味著閘道器是錯誤配置的或過載的
11 (0BH) 閘道器目標裝置響應失敗 與閘道器一起使用,指示沒有從目標裝置中獲得響應,通常意味著裝置不在網路中

相關文章