modbus RTU



from http://www.cht.nahua.com.tw/epaper/2017/307/




著工控領域的發展,通訊是不可或缺的一環,而 Modbus 通訊又是其中的佼佼者,很多控制器、錶頭、設備都是走 Modbus 通訊,那要如何用人機與 Modbus 設備溝通?在此間單做個介紹:


簡介:
在此以 Modbus RTU 為例;通常要讀取資料或寫入資料都要以用送碼方式來達成
詳細收送碼方式,請參照 177 期電子報

以讀取碼為例:
送碼 01,03,00,00,00,01,84,0A
01 表示 MODBUS 站號
03 表示讀取 MODBUS 資料
00,00 表示 MODBUS 位址
00,01 表示 MODBUS 長度(WORD數)
84,0A 表示 MODBUS CRC檢查碼   tool https://www.scadacore.com/tools/programming-calculators/online-checksum-calculator/
以上都是 16 進制呈現

Modbus 觀念(依不同fun code會有不同Modbus位址)
位置 0000 為起始位址
依不同 fun code 的起始位置也會跟著不同



以表格呈現 WORD 與 BIT 的讀取與寫入

其中,紅色部分是字串呈現與人機MODBUS位址呈現
以下呈現都是16進制
1. 當不同的 FUNTION CODE 會使人機MODBUS地址不同
2. WORD的FUNCTION CODE 有 03(讀取)/06 (單筆寫入)/10(多筆寫入);人機起始位址40001
3. WORD的FUNCTION CODE 有 04(唯讀);人機起始位址30001
4. BIT 的 FUNCTION CODE 有 01(線圈讀取)/05(線圈單筆寫入)/0F(線圈多筆寫入);人機起始位址 1
5. BIT 的 FUNCTION CODE 有 02(讀取BIT輸入狀態);人機起始位址 10001

舉例:
若 MODBUS RTU 的位址是 1000(16進制),當 FUN CODE 不同時,人機對應的位址也會不同
當 FUN CODE 為 03/06時,MODBUS RTU的人機位址為1000(16進制)先轉成 10 進制,即 4096;
再加上起始位址 40001,所以 FUN CODE(03/06/10)的人機 MODBUS RTU 位值為 44097;
其中 40001 為該 fun code(03/06/10)的起始位址


知道人機位址後,就可以簡單地設值到人機內






from https://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf

2、3 Modbus ファンクション・フォーマット
(1)データ・アドレス
  Modbus 通信で、データの参照や変更を行う場合、データ・アドレスを用いて行います。データの種
 類には、コイル、入力ステータス、入力レジスタ、保持レジスタの 4 種類があります。
(2)コイル(Coil)
  コイルは、フィールドへの ON / OFF 出力である DO(Discrete Output)やスレーブデバイスの状
 態やモードを変更する為にスイッチとして用いられます。参照・変更が可能な2値のデータで、可能ア
 ドレス範囲は 1 から 9999 です。
(3)入力ステータス(Input Status)
  入力ステータスは、フィールドからの ON / OFF 入力である DI(Discrete Input)やスレーブデバイ
 スの状態入力として用いられます。参照のみで変更はできない 2 値のデータで、可能アドレス範囲は
 10001 から 19999 です。
(4)入力レジスタ(Input Register)
  入力レジスタは、フィールドからのAI(Analog Input)やスレーブデバイス内の情報として用いられ
 ます。16ビット長のデータで、参照するのみで変更はできません。可能アドレス範囲は30001から39999
 です。複数の連続したアドレスを割当てることにより、単精度実数、倍精度実数などのデータを扱う
 こともできます。
(5)保持レジスタ(Holding Register)
  保持レジスタは、フィールドからの AO(Analog Output)やスレーブデバイス内の設定情報として
 用いられます。16 ビット長のデータで、参照・変更ができます。可能アドレス範囲は 40001 から 49999
 です。複数の連続したアドレスを割当てることによって、単精度実数、倍精度実数などのデータを扱うこ
 ともできます。


1、1 ファンクション・コード
(表 31)に R1M シリーズでサポートするファンクション・コードを示します。
●ファンクションコード一覧表(表 31)

 ファンクション名 機  能 (仕様書 PI-MBUS-300 より)
01(0x01) Read Coil Status コイル、DO の読出し
02(0x02) Read Input Status 入力ステータス、DI の読出し
03(0x03) Read Holding Register 保持レジスタの読出し
04(0x04) Read Input Register 入力レジスタの読出し
05(0x05) Force Single Coil コイル、DO への1点書込み
06(0x06) Preset Single Register 保持レジスタへの書込み
15(0x0F) Force Multiple Coils 複数コイル、DO への一括書込み
16(0x10) Force Multiple Registers 複数保持レジスタへの一括書込み
1、7 保持レジスタ・データ詳細
(1)アナログ出力 % 値(40001 ~ 40016)
  各出力チャンネル(1 ~ 16)のアナログ出力 % 値です。将来の予備です。
(2)アナログ出力工業単位値(40017 ~ 40048)
  各出力チャンネル(1 ~ 16)のアナログ出力値です。値は工業単位量でその単位は出力のタイプで
 決定されます。データ形式は 32 ビット長の実数で表現されます。従って、保持レジスタは各チャンネ
 ルに対して連続した 2 個を使用します。将来の予備です。
(3)入出力タイプ番号(40145 ~ 40160)
  各入力チャンネル(1 ~ 16)の入出力タイプ番号です。入出力タイプ番号を 16 ビット整数で示し
 ます。




3、ファンクション・コード(Modbus ファンクション)
3、1 Read Coil Status(01)
●機能
 スレーブの DO(Discrete Output)の ON / OFF 状態を読出します。ブロードキャストはありません。
 アドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、コイルの開始アドレスとコイルの数を指定します。(表 5)に、スレーブデ
バイス 3に対してコイル20 番地から 56番地までの、37 個のコイルのステータスを読む例を示します。開
始アドレスの値が、1 少ない 19(0x13)になっていることに注意して下さい。
・Read Coil Status のクェーリメッセージ例(表 5)
●レスポンス
 レスポンスメッセージにおけるコイルのステータスはデータフィールドのデータに、1 コイル当り 1
ビットのパックされます。ビットの意味は 1 = ON、0 = OFF で、最初のデータの LSB が先頭アドレスの
コイルのステータスになります。コイル20から27のステータスがON-ON-OFF-OFF-ON-OFF
- ON - OFF とすると 2 進数で 01010011(0x53)となります。1 バイトで 8 コイル分のステータスを表
します。最後のデータでコイル数が 8 個に満たない場合には、残りには 0 を詰めます。(表 5)のクェー
リに対するレスポンス例を(表 6)に示します。
・スレーブからの応答メッセージ例(表 6)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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”,”4” 0xF4
データ 5 0x1B “1”,”B” 0x1B
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 21 10
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-10-
3、2 Read Input Status(02)
●機能
 スレーブの DI(Discrete Input)の ON / OFF 状態を読出します。ブロードキャストはありません。
 アドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、入力ステータスの開始アドレスと入力ステータスの数を指定します。スレー
ブデバイス 3 に対して入力ステータス 10101 番地から 10120 番地までの 20 個の入力ステータスのステー
タスを読む例を示します。開始アドレスの値が、10001 少ない 100(0x64)になっていることに注意して
下さい。
・Read Coil Status のクェーリメッセージ例(表 7)
●レスポンス
 レスポンスメッセージにおけるデータの構成、意味はRead Coil Status(01)と同じです。(表7)のクェー
リに対するレスポンス例を(表 8)に示します。
・スレーブからの応答メッセージ例(表 8)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-11-
3、3 Read Holding Register(03)
●機能
 スレーブの保持レジスタの内容を読出します。ブロードキャストはありません。
 アドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、保持レジスタの開始アドレスと保持レジスタの数を指定します。(表9)に、
スレーブデバイス 7に対して保持レジスタ 40201番地から40203番地までの3個の保持レジスタの内容を
読む例を示します。開始アドレスの値が、40001少ない200(0xC8)になっていることに注意して下さい。
・Read Holding Register のクェーリメッセージ例(表 9)
●レスポンス
 レスポンスメッセージにおけるデータは、1保持アドレス当り16ビットのデータです。(表9)のクェー
リに対するレスポンス例を(表 10)に示します。
・スレーブからの応答メッセージ例 (表 10)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x07 “0”,”7” 0x07
ファンクションコード 0x03 “0”,”3” 0x03
データバイト数 0x06 “0”,”6” 0x06
データ 1(上位) 0x03 “0”,”3” 0x03
データ 1(下位) 0xE8 “E”,”8” 0xE8
データ 2(上位) 0x01 “0”,”1” 0x01
データ 2(下位) 0xF4 “F”,”4” 0xF4
データ 3(上位) 0x00 “0”,”0” 0x00
データ 3(下位) 0x0A “0”,”A” 0x0A
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 23 11
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-12-
3、4 Read Input Register(04)
●機能
 スレーブの入力レジスタの内容を読出します。ブロードキャストはありません。
 アドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、入力レジスタの開始アドレスと入力レジスタの数を指定します。(表11)に
スレーブデバイス 7に対して入力レジスタ 30301番地から30303番地までの3個の入力レジスタの内容を
読む例を示します。開始アドレスの値が、30001 少ない 300(0x12C)になっていることに注意して下さ
い。
・Read Input Register のクェーリメッセージ例(表 11)
●レスポンス
 レスポンスメッセージにおけるデータは、1入力アドレス当り16ビットのデータです。(表11)のクェー
リに対するレスポンス例を(表 12)に示します。
・スレーブからの応答メッセージ例(表 12)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x07 “0”,”7” 0x07
ファンクションコード 0x04 “0”,”4” 0x04
データバイト数 0x06 “0”,”6” 0x06
データ 1(上位) 0x03 “0”,”3” 0x03
データ 1(下位) 0xE8 “E”,”8” 0xE8
データ 2(上位) 0x01 “0”,”1” 0x01
データ 2(下位) 0xF4 “F”,”4” 0xF4
データ 3(上位) 0x00 “0”,”0” 0x00
データ 3(下位) 0x0A “0”,”A” 0x0A
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 23 11
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-13-
3、5 Force Single Coil(05)
●機能
 スレーブの DO(Discrete Output)の状態を ON / OFF のいずれかに変更(書込み)します。ブロー
ドキャストの場合には、全スレーブの同じアドレスのコイルを書換えます。
 アドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、コイルのアドレスと変更したいステータスを指定します。変更したいステー
タス(ON/OFF)は、クェーリのデータエリアで指定します。0xFF、0x00はONのリクエストで、0x00、
0x00 は OFF のリクエストです。それ以外のデータは不正データで、変更動作は行われません。(表 13)
に、スレーブデバイス 3のコイル 150番地をON にする例を示します。開始アドレスの値が、1 少ない 149
(0x95)になっていることに注意して下さい。
・Force Single Coil のクェーリメッセージ例(表 13)
●レスポンス
 正常に変更された場合の正常メッセージは、クェーリと同じになります。(表 14)に、(表 13)に対す
る正常応答メッセージを示します。
・スレーブからの正常応答メッセージ例(表 14)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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(16 ビット)
トレーラー CR / L


3、6 Preset Single Register(06)
●機能
 スレーブの保持レジスタの内容を変更(書込み)します。ブロードキャストの場合には、全スレーブ
の同じアドレスの保持レジスタの内容が変更されます。
 保持レジスタのアドレスとその内容については、各スレーブデバイスの仕様書をご参照下さい。
●クェーリ
 クェーリメッセージでは、保持レジスタのアドレスと変更したいデータを指定します。変更したいデー
タは、クェーリのデータエリアで 16 ビットのデータとして指定します。(表 15)に、スレーブデバイス
3 の保持レジスタ 40150 番地を 1000 にする例を示します。アドレスの値が、1 少ない 149(0x95)になっ
ていることに注意して下さい。
・Preset Single Register のクェーリメッセージ例(表 15)
●レスポンス
 正常に変更された場合の正常メッセージは、クェーリと同じになります。(表 16)に、(表 15)に対す
る正常応答メッセージを示します。
・スレーブからの正常応答メッセージ例(表 16)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x03 “0”,”3” 0x03
ファンクションコード 0x06 “0”,”6” 0x06
アドレス(上位) 0x00 “0”,”0” 0x00
アドレス(下位) 0x95 “9”,”5” 0x95
変更データ(上位) 0x03 “0”,”3” 0x03
変更データ(下位) 0xE8 “E”,”8” 0xE8
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x03 “0”,”3” 0x03
ファンクションコード 0x06 “0”,”6” 0x06
アドレス(上位) 0x00 “0”,”0” 0x00
アドレス(下位) 0x95 “9”,”5” 0x95
変更データ(上位) 0x03 “0”,”3” 0x03
変更データ(下位) 0xE8 “E”,”8” 0xE8
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-15-
3、7 Diagnostics(08)
●機能
 マスターとスレーブ間の通信の診断やスレーブの機器の診断ファンクションです。ブロードキャスト
は、ありません。診断のタイプを定義する為に、クェーリの中に2バイトの診断サブコードフィールドが
あります。スレーブからの正常なレスポンスには、ファンクションコードと共にこのサブコードもエコー
バックします。また、クェーリにはスレーブのコントロールや診断の為のデータを渡す為に、2バイトの
データがあります。
 診断サブファンクションとその内容については、「4、診断(0x08)」の項をご参照下さい。
●クェーリ
(表 17)は、スレーブデバイス 5 とのクェーリのリードバック診断の例です。リードバック診断のサブ
コードは、0(0x0000)です。
・Diagnostics のクェーリメッセージ例(表 17)
●レスポンス
(表 17)のリードバック診断の正常レスポンスは、クェーリと同じになります。
・スレーブからの正常応答メッセージ例(表 18)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x05 “0”,”5” 0x05
ファンクションコード 0x08 “0”,”8” 0x08
診断サブコード(上位) 0x00 “0”,”0” 0x00
診断サブコード(下位) 0x00 “0”,”0” 0x00
データ(上位) 0x03 “0”,”3” 0x03
データ(下位) 0xE8 “E”,”8” 0xE8
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 8- ビット(Hex)
ヘッダー “:” なし
スレーブアドレス 0x05 “0”,”5” 0x05
ファンクションコード 0x08 “0”,”8” 0x08
診断サブコード(上位) 0x00 “0”,”0” 0x00
診断サブコード(下位) 0x00 “0”,”0” 0x00
データ(上位) 0x03 “0”,”3” 0x03
データ(下位) 0xE8 “E”,”8” 0xE8
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8
Modbus
-16-
3、8 Fetch Communication Event Counter(11,0x0B)
●機能
 スレーブの通信イベントカウンタからステータスワードとイベントカウンタを読出します。メッセー
ジ通信の前後でこのカウンタを読むことで、マスタはスレーブが正しくメッセージを処理しているかが
分かります。ブロードキャストはありません。
 コントローラはメッセージを正しく処理した場合、イベントをカウントアップします。例外レスポン
ス、ポールコマンドおよび本コマンドの場合にはカウントアップしません。イベントカウンタは診断ファ
ンクションのサブコード Restart Communication Option(コード 0x0001)と、Clear Counters and Diagnostic Register(0x000A)でリセットされます。
●クェーリ
(表 19)は、スレーブデバイス 5 への Fetch Communication Event Counter の例です。
・Fetch Communication Event Counter のクェーリ(表 19)
●レスポンス
 Fetch Communication Event Counter の正常レスポンスは、2 バイトのステータスワードと 2 バイトの
イベントカウンタが含まれます。(表 20)に、(表 19)に対する応答メッセージの例を示します。
・スレーブからの正常応答メッセージ例(表 20)
フィールド名 例 ASCII モード RTU モード
(Hex) 文字列 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” 0xE8
エラーチェック LRC(2 文字) CRC(16 ビット)
トレーラー CR / LF なし
合計バイト数 17 8




補 足 2 Modbus / TCP プロトコル
 以下に、Modbus / TCP プロトコル仕様の概要を掲載します。Modbus / TCP の仕様は、Web 上で
「OPEN Modbus / TCP SPECIFICATION」として公表されています。詳細は原文を参照下さい。
1、はじめに
Modbus / TCP は、業界標準となっている Modbus プロトコルを TCP / IP に拡張したプロトコルです。TCP / IP を使用していますのでインターネット環境でもメッセージのやり取りが可能になります。
Modbus / TCP では、クライアント・サーバモデルで通信を行なうことになります。クライアントは従来の Modbus プロトコルでのマスターに相当し、サーバはスレーブに対応します。TCP / IP プロトコルを基本にしていますので、複数クライアント、複数サーバのサポートが可能になります。即ちマルチマスター、マルチスレーブのシステム構成が可能になります。
TCP 経由で Modbus / TCP 通信を行なう場合には、登録されたポート番号 502 を用います。
2、プロトコルのレイアウト以下に Modbus リクエストまたはレスポンスが Modbus / TCP ネットワーク上で通信されるときのカプセル化の一般的な形式を記載します。Modbus / TCP 通信データ上のファンクションコードからデータ部分の終わりまでのリクエストとレスポンス本体の構造が、以下の Modbus 体系で、完全に同一レイ
アウトと同一の意味を持ちます。
Modbus serial port - ASCII encoding
Modbus serial port - RTU(binary)encoding
アドレスの解釈については相違がありますので注意が必要です。
Modbus のスレーブアドレス・フィールドは、1 バイト長のユニット識別子に置換えられます。ユニット識別子はブリッジやゲートウェイ(例えば、72EM)のようなデバイスと通信する場合に用いられ、同一IP アドレス上の複数の独立した終端ユニットを指定するのに使います。単一ユニットの場合には、IP アドレスがユニットを指定しますのでスレーブアドレス・フィールドは意味を持ちません。
Modbus / TCP プロトコルのリクエストとレスポンスには、次のような 6 バイト情報を前に付けます。
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 以降 : 必要なデータ列(Modbus プロトコル参照)
トランザクション識別子は、サーバ側はコピーを返すのみで、クライアント側でのメッセージのトラン
ザクション管理に使います。0 固定でも構いません。
Modbus/TCPとModbusとのメッセージのデータ構造の関係を図式化すると以下のようになります。


3、動作の例
ユニット識別子9のデバイスからオフセット4のレジスタを読むと、値5が返ってくるトランザクショ
ンの例は以下のようになります。
リクエスト:00 00 00 00 00 06 09 03 00 04 00 01
応   答:00 00 00 00 00 05 09 03 02 00 05
4、注意事項
Modbus / TCP では LRC または CRC-16 チェック・フィールドが不要な点に注意しなければなりませ
ん。パケットの正確な伝送のチェックには、TCP / IP やリンク層(例えば Ethernet)のチェックメカニ
ズムが使われます。









    private void btnOpenPort_Click(object sender, EventArgs e)
    {
      // If the port is open, close it.
        try
        {
            if (comport.IsOpen) comport.Close();
            else
            {
                // Set the port's settings
                comport.BaudRate = int.Parse(cmbBaudRate.Text);
                comport.DataBits = int.Parse(cmbDataBits.Text);
                comport.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cmbStopBits.Text);
                comport.Parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.Text);
                comport.PortName = cmbPortName.Text;

                // Open the port
                comport.Open();
            }

            // Change the state of the form's controls
            EnableControls();
            // If the port is open, send focus to the send data box
            if (comport.IsOpen) txtSendData.Focus();
        }
        catch (Exception ex)
        {
            Log(LogMsgType.Error, String.Format(ex.Message.ToString() + "at {0}\n", DateTime.Now), rtfTerminal);
        }
    }


            comport.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string data = comport.ReadExisting();
            byte[] LogoDataBy = ASCIIEncoding.ASCII.GetBytes(data);
            Log(LogMsgType.Incoming, BitConverter.ToString(LogoDataBy), rtfTerminal);

        }

    private void buttonRead_Click(object sender, EventArgs e)
    {


            //    GetData("0103200000018FCA", ComName);
            //     byte[] cmd = new byte[] { 0x02, 0x03, 0x20, 0x00, 0x00, 0x01 };
            byte[] cmd = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02 };
            byte[] crc16 = BitConverter.GetBytes(CRC16(cmd));
            byte[] cmdCRC = Combine(cmd, crc16);
            comport.Write(cmdCRC, 0, cmdCRC.Length);

            txtData.Text = ""; 
            Log(LogMsgType.Outgoing, BitConverter.ToString(cmdCRC) + "\n", rtfTerminal);
        }


        static ushort CRC16(byte[] pDataBytes)
        {
            ushort crc = 0xffff;
            ushort polynom = 0xA001;

            for (int i = 0; i < pDataBytes.Length; i++)
            {
                crc ^= pDataBytes[i];
                for (int j = 0; j < 8; j++)
                {
                    if ((crc & 0x01) == 0x01)
                    {
                        crc >>= 1;
                        crc ^= polynom;
                    }
                    else
                    {
                        crc >>= 1;
                    }
                }
            }

            return crc;
        }


        public static byte[] Combine(byte[] first, byte[] second)
        {
            byte[] ret = new byte[first.Length + second.Length];
            Buffer.BlockCopy(first, 0, ret, 0, first.Length);
            Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
            return ret;
        }

        private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {


            try
            {
                int bytesToRead = _serialPort.BytesToRead;
                byte[] array = new byte[bytesToRead];
                if (bytesToRead > 0)
                {
                    _serialPort.Read(array, 0, bytesToRead);
                    //Log(LogMsgType.Normal, String.Format("_serialPort_DataReceived array=" + ByteArrayToHexString(array) + "at {0}\n", DateTime.Now), rtfError);
                    PaserRcvData(array);
                }
                TimerComTimeout.Stop();
            }
            catch (TimeoutException ex)
            {
                Log(LogMsgType.Error, string.Format(ex.Message.ToString() + "at {0}\n", DateTime.Now), rtfError);
                //ErrorLog.Info(MethodBase.GetCurrentMethod().ToString() + "," + ex.Message.ToString());
                OpenPort(sOpen: false, _serialPort.PortName);
            }
            catch (Exception ex2)
            {
                Log(LogMsgType.Error, string.Format(ex2.Message.ToString() + "at {0}\n", DateTime.Now), rtfError);
                //ErrorLog.Info(MethodBase.GetCurrentMethod().ToString() + "," + ex2.Message.ToString());
                OpenPort(sOpen: false, _serialPort.PortName);
            }


    }
        private void PaserRcvData(byte[] data)
        {
            try
            {
                StringBuilder stringBuilder = new StringBuilder(data.Length * 3);
                foreach (byte b in data)
                {
                    stringBuilder.Append(Convert.ToString(b, 16).PadLeft(2, '0'));
                }
                string text = stringBuilder.ToString();
                string text2 = stringBuilder.ToString().ToUpper();
                text2 = text2.Substring(6, 4);
                int num = Convert.ToInt16(text2, 16);
                string sensorValue= ((decimal)num / 10m).ToString("0.0"); 

            }
            catch (Exception ex)
            {
                Log(LogMsgType.Error, string.Format(ex.Message.ToString() + "at {0}\n", DateTime.Now), rtfError);
            }
        }
















from http://www.xuan.idv.tw/wordpress/?p=1705



先前留下了一些關於modbus的文章卻好像沒有留下一點關於modbus基礎的介紹
modbus是一種工業控制常用的通訊協定,他定義了一個標準的通訊封包格式,
而非一種通訊技術,最早modbus是使用於PLC上,漸漸的許多工控設備也開始
採用modbus作為一種標準的通訊格式。
而modbus也產生出了許多不同的形態,如modbus RTU為最原始的以二進制方式表示
也有使用ASCII的modbus ASCII,以及modbus TCP/IP等等型態,
其不同的差異只有在於部分的格式不同(ASCII採用字元編碼方式傳送)
在低階的硬體控制中,最常使用的莫過於modbus RTU這種以二進制方式傳送的通訊
是最簡單不過的,在RS485(TTL485)中經常可以看到modbus的通訊協定
其原因為modbus本身也具有master與slave的架構,在一個並聯(RS485)的通訊環境
中,有一節點(設備)為master,由該master向其他slave通訊,進行通訊、控制等。
modbus提供許多操作功能碼,其詳細的定義了這些功能的作用以及格式
這邊所提到,定義,指的只是一個規則,我們在寫程式或在使用時就必須遵照
這些規則進行,就等於是在實行標準的modbus協議。
以下是我簡單介紹的modbus RTU的使用方式
1.讀/寫位址表(Mapping table):
在使用modbus協定中,要讀取或寫入設備,通常必須先知道欲控制或讀取設備之
記憶體暫存器位置表,不同的產品、設備,都會有自己的位址表,基本上是不會
相同的,標準中每個地址的長度以1word表示,每個位址所代表的資料量為1word
等於 1 address = 1 word data
但在實際用途上,經常性的資料範圍會有大於1word的時候,所以有些人則會連續
定義兩個2 address做為1個資料的內容存放空間,所以,在使用modbus RTU前,
必須先了解該設備的位址表,才有辦法讀取或控制自己要的內容
2.Slave address/Slave ID 設備端ID
當要讀寫slave時,必須先知道該設備的address/ID,至於這個ID要從何得知?
通常就要問負責該設備的人,或者設計者,或者原廠公司,或者熟悉他的人
3.常用的modbus功能碼(function code) - 0x03
0x03功能碼(function code)所定義的功能為讀取多個暫存器,用來讀取一連續
位址的資料。
master讀取格式:
設備ID(slave address/ID) + 0x03 + 讀取起始位置(word) + 讀取的數量(word) + CRC16
slave回復格式:
設備ID(slave address/ID + 0x03 + 回復資料的byte數 + 資料1(word) + ... + 資料n(word) + CRC16
例如:(以下所出現之命令數字均已16進制表示)
master送出: 05 03 0100 0003 + CRC16
slave回應: 05 03 06 00A1 00B2 00C3 + CRC16
這樣就或許到暫存器位址中0100,0101,0102的資料分別為
0100 = 00A1
0101 = 00B2
0102 = 00C3
4.常用的modbus功能碼(function code) - 0x06
0x06的作用,在於寫入單一個暫存器資料,因一次只能寫入一筆資料,
依照modbus標準的定義來看,一次只能寫入1word的資料量。
master寫入格式:
設備ID(slave address/ID) + 0x06 + 寫入暫存器位置(word) + 寫入資料(word) + CRC16
slave回復格式:
設備ID(slave address/ID) + 0x06 + 寫入暫存器位置(word) + 寫入資料(word) + CRC16
0x06寫入與回應是相同的時候,代表寫入成功,如寫入失敗時,則會有另外一種格式,於後面介紹
寫入範例:
master 送出: 05 06 0205 9999 + CRC16
slave 回應: 05 06 0205 9999 + CRC16
經過上面的命令後,slave端暫存器位置0205資料寫入變更為0x9999
5.常用的modbus功能碼(function code) - 0x10
0x10是在執行連續寫入多個暫存器用,其發送命令類似0x03功能的回覆命令
master寫入格式:
設備ID(slave address/ID) + 0x10 + 寫入暫存器起始位置(word) + 寫入的數量(word) + 資料數量(byte)
+ 第1筆資料(word) + ... +第n筆資料(word) + CRC16
slave回復格式:
設備ID(slave address/ID) + 0x10 + 寫入暫存器起始位置(word) + 寫入的數量(word) + CRC16
這個命令通常剛接觸的人都會霧煞煞,簡單說就是指定一個寫入的起始位置,然後預計寫入多少個暫存器
然後再加上一個資料數量,這個資料數量就是後面所帶的資料量有幾個byte,所以等於寫入的數量乘以2
因為一次需要寫入一個word的資料量,以至於就必須告訴slave端說後面帶了多少資料,很多人會說
已經知道寫入幾個了,為什麼還要脫褲子放屁的加上一個資料byte數呢?其原因應該是增加資料的可靠度
當slave端收到命令時,可以有助於驗證資料的正確性,如同0x03功能碼回覆時,也必須帶一個資料長度的大小
也是一樣的作用。
寫入範例:
master 送出: 05 10 0601 0003 06 000A 000B 000C + CRC16
slave 回應: 05 10 0601 0003 + CRC16
這行命令代表了我要寫入多個暫存器,由0601開始寫,連續寫入三個,所以後面的資料長度是6byte
經過命令之後
0601 = 000A
0602 = 000B
0603 = 000C
6.錯誤例外處理
標準modbus中也提供了錯誤例外的處理,例如當收到不合乎規則的命令時,
該如何回應master端的要求,其格式相當簡單
slave回應的錯誤例外格式:
設備ID(slave address/ID) + (功能碼 | 0x80) + 錯誤識別碼 + CRC
由上面格式中可以看到,當發生錯誤時,功能碼最高位元會 OR 0x80
也就是最高位元為1時代表有錯誤產生,而錯誤識別碼在標準modbus中也有定義
01 - Illegal function
錯誤的功能碼,要求指令中的Function是不正確的。
02 - Illegal data address
錯誤的地址,要求指令中出現了不被辨識的Address。
03 - Illegal data value
錯誤的資料參數,要求指令中出現了錯誤的Data。
04 - Slave device failure
Slave device發生錯誤。
05 - Acknowledge
Slave device正再處理上一個命令。
06 - Slave device busy
Slave device忙碌。
我認為這些錯誤的例外處理代碼,是可以自行定義的,只需在文件中說明清楚即可
7.modbus RTU的Timeout超時
規範中說明第一個命令與上一個命令至少距離3.5字節的時間
熟悉通訊的人可能對於timeout/超時這個時間不是很陌生,但是不熟悉的人可能就會
霧煞煞。設備通訊就跟人與人之間的對談是一樣的,想想在電話中與遠處的人交談時
你如何決定何時該回話?通常都是等待著對方結束上一句話的n個時間內(幾毫秒內),
而這幾毫秒內就相當於modbus中的timeout超時時間,代表這個命令傳送已經結束了
slave端可以進行資料的解析做處理,在modbus ASCII傳送時,因為是ASCII(字元碼)的關係
可以使用特定的字元做為結束的標記
,slave端一直接收資料,直到收到特定字元時,
則代表該次傳送命令已結束,而在modbus RTU中,因為沒有特定的字元可以做標示
因為在二進制中,並沒有辦法使用哪個編碼做為特定字元(有存在衝突的可能),

所以取而代之使用時間做為結束的表示,上面所說的3.5字節的時間,
是modbsu標準的定義,以一個baud rate 9600bps的通訊環境來說,1個字節相當於1ms
換句話說,3.5 * 1 = 3.5ms也就是相當於需要4ms時,就可以判斷該命令已結束
但是在實際應用中,因為通訊環境的因素可能不是著麼理想(存在許多干擾的因素)
通常timeout時間我都抓在5~10字節時間,比較不會發生master尚未傳送完命令
但slave已經關閉接收的問題。
以下是我寫的modbus slave接收處理程式,
其程式作用在於操作modbusRAM[]陣列變數,
以modbusRAM[]陣列變數做為modbus address的呼應
所以當master傳送操作、讀取命令時,均是在操作讀取modbusRAM[]陣列內容。

  1. #include "remote.h"
  2. //The remote function is standard modbus : 1 addr = 1 word data
  3.  
  4. uint16 modbusRAM[128];
  5. uint8 commTimeCount;
  6. static uint8 slaveID;
  7. static uint8 txCount; //transfer buffer counter
  8. static uint8 cnt; //receive buffer counter
  9. uint8 commRxBuffer[200]; //receive remote command buffer
  10. uint8 commTxBuffer[200];
  11.  
  12. //----------------------------------------------------------------------------
  13. //Communication Uart Receive Interrupt
  14. //這是UART的中斷程式,採用1byte接收中斷一次
  15. //----------------------------------------------------------------------------
  16. void commUartINT (void)
  17. {
  18. commRxBuffer[cnt] = commUart_bReadRxData();
  19.  
  20. if (commRxBuffer[0] == slaveID)
  21. {
  22. cnt++;
  23. }
  24. else
  25. {
  26. cnt = 0;
  27. commRxBuffer[0] = 0;
  28. }
  29.  
  30. commTimeCount = 0;
  31. return;
  32. }
  33. //----------------------------------------------------------------------------
  34. //Remote communication function in here
  35. //Modbus命令接收Timeout檢查
  36. //----------------------------------------------------------------------------
  37. void remoteFun (void)
  38. {
  39. uint16 crcTmp;
  40.  
  41. if ( cnt != 0 &amp;&amp; commTimeCount &gt; RXTIMEOUT)
  42. {
  43. //communication response status check
  44. switch( commRxBuffer[1] )
  45. {
  46. case 0x03:
  47. modbus03();
  48. break;
  49. case 0x06:
  50. modbus06();
  51. break;
  52. case 0x10:
  53. modbus10();
  54. break;
  55. default:
  56. funError(ErrorFuncCode);
  57. }
  58. cnt = 0;
  59. commTimeCount = 0;
  60. return;
  61. }
  62. if ( cnt == 0 )
  63. {
  64. commTimeCount = 0;
  65. }
  66. }
  67. //----------------------------------------------------------------------------
  68. //Modbus function code 0x03 - read many register
  69. //Modbus 0x03功能-讀取多個暫存器
  70. //----------------------------------------------------------------------------
  71. void modbus03 (void)
  72. {
  73. uint8 dptr,count;
  74. uint16 crcTmp;
  75. // 0 1 2 3 4 5 6 7
  76. //01 03 00 01 00 00 AA BB
  77. if (....)
  78. {
  79. //modbus 03錯誤檢查放置於此
  80. funError(ErrorData);
  81. return;
  82. }
  83.  
  84. dptr = 0;
  85. count = commRxBuffer[5];
  86. while (count)
  87. {
  88. commTxBuffer[3+dptr+dptr] = modbusRAM[commRxBuffer[3] + dptr] &gt;&gt; 8;
  89. commTxBuffer[4+dptr+dptr] = modbusRAM[commRxBuffer[3] + dptr++];
  90. count--;
  91. }
  92. commTxBuffer[0] = commRxBuffer[0];
  93. commTxBuffer[1] = commRxBuffer[1];
  94. commTxBuffer[2] = dptr &lt;&lt; 1;
  95. crcTmp = crcCHK( commTxBuffer , ( 3 + dptr + dptr ));
  96. commTxBuffer[ 3 + dptr + dptr ] = crcTmp;
  97. commTxBuffer[ 4 + dptr + dptr ] = crcTmp &gt;&gt; 8;
  98.  
  99. commTxFunction( 5 + dptr + dptr );
  100. }
  101. //----------------------------------------------------------------------------
  102. //Modbus function code 0x06 - write one register
  103. //Modbus 0x06功能-寫入單一個暫存器
  104. //----------------------------------------------------------------------------
  105. void modbus06 (void)
  106. {
  107. // 0 1 2 3 4 5 6 7
  108. //01 06 00 01 00 0F AA BB
  109. uint8 x;
  110. uint16 data;
  111. if (......)
  112. {
  113. //modbus 06錯誤檢查放置於此
  114. funError(ErrorAddress);
  115. return;
  116. }
  117.  
  118. data = commRxBuffer[4];
  119. data &lt;&lt;= 8;
  120. data |= commRxBuffer[5];
  121.  
  122. modbusRAM[commRxBuffer[3]] = data;
  123.  
  124. if ( commRxBuffer[0] == 0xFF )
  125. {
  126. return;
  127. }
  128. for ( x = 0 ; x &lt; cnt ; x++ )
  129. {
  130. commTxBuffer[x] = commRxBuffer[x];
  131. }
  132.  
  133. commTxFunction(cnt);
  134.  
  135. }
  136. //----------------------------------------------------------------------------
  137. //Modbus function code 0x10 - write many register
  138. //Modbus 0x10功能-寫入多個暫存器
  139. //----------------------------------------------------------------------------
  140. void modbus10 (void)
  141. {
  142. // 0 1 2 3 4 5 6 7 8 9 10 11 12
  143. //01 10 00 01 00 02 04 00 0F 00 0F AA BB
  144. uint8 count,dptr;
  145. uint16 crcTmp;
  146.  
  147. if (......)
  148. {
  149. //modbus 10錯誤檢查放置於此
  150. funError(ErrorAddress);
  151. return;
  152. }
  153.  
  154. count = commRxBuffer[5];
  155. dptr = 0;
  156.  
  157. while (count)
  158. {
  159. modbusRAM[commRxBuffer[3] + dptr] = (commRxBuffer[7+dptr+dptr])&lt;&lt;8;
  160. modbusRAM[commRxBuffer[3] + dptr] |= commRxBuffer[8+dptr+dptr++];
  161. count--;
  162. }
  163.  
  164. if ( commRxBuffer[0] == 0xFF )
  165. {
  166. return;
  167. }
  168.  
  169. for ( count=0; count&lt;6; count++ )
  170. {
  171. commTxBuffer[count] = commRxBuffer[count];
  172. }
  173.  
  174. crcTmp = crcCHK( commTxBuffer,6);
  175.  
  176. commTxBuffer[6] = crcTmp;
  177. commTxBuffer[7] = crcTmp&gt;&gt;8;
  178. commTxFunction(8);
  179. }
  180. //----------------------------------------------------------------------------
  181. //Modbus Error Function response
  182. //----------------------------------------------------------------------------
  183. void funError ( uint8 exception )
  184. {
  185.  
  186. uint16 crcTmp;
  187.  
  188. if ( commRxBuffer[0] == 0xFF )
  189. {
  190. return;
  191. }
  192.  
  193. commTxBuffer[0] = slaveID;
  194. commTxBuffer[1] = commRxBuffer[1] | 0x80;
  195. commTxBuffer[2] = exception;
  196.  
  197. crcTmp = crcCHK( commTxBuffer , 3 );
  198.  
  199. commTxBuffer[3] = crcTmp;
  200. commTxBuffer[4] = crcTmp &gt;&gt; 8;
  201.  
  202. commTxFunction(5);
  203. }
  204. //----------------------------------------------------------------------------
  205. //Modbus CRC16 function check
  206. //----------------------------------------------------------------------------
  207. uint16 crcCHK ( uint8* data,uint8 length)
  208. {
  209. int cnt;
  210. unsigned int tmpCRC=0xFFFF;
  211.  
  212. while (length--)
  213. {
  214. tmpCRC ^= *data++;
  215.  
  216. for (cnt=0; cnt&lt;8; cnt++)
  217. {
  218. if (tmpCRC &amp; 0x01)
  219.  
  220. tmpCRC = (tmpCRC &gt;&gt; 1) ^ 0xA001;
  221. else
  222. tmpCRC = tmpCRC &gt;&gt; 1;
  223. }
  224.  
  225. }
  226.  
  227. return tmpCRC;
  228. }








留言

熱門文章