ModbusマルチレジスタデータParseの究極ガイド:エンディアンの完全なソリューション

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

この問題はあなたが思うより一般的です。

Modbus Pollを使用して電力メーターの順方向アクティブ総電力量を読み取り、レジスタ40001 〜 40002を入力し、`42 C 8 00 00`を返す。自信を持ってIEEE 75 4 float32を押す-100.0 kWh、合理的です。同じプログラムは、別の国内の電力メーターに変更し、同じ2つのレジスタを読み取り、'00 0 42 C 8'に戻り、0.0を綴ると、PLCのタッチスクリーンは直接直線を描画します。レジスタを間違えたり、CRCを間違えたりしていません。純粋にバイトオーダーです。

これがModbusマルチレジスタデータParseの核心的な問題点です。Modbusプロトコル仕様V1.1 bのセクション4.2“Data Encoding”は、“Modbus uses a 'big-endian' representation for addresses and data items.”と率直に述べています。This means that when a numerical quantity larger than a single byte is transmitted the most significant byte is sent first.“** 人の言葉への翻訳:Modbusは単一レジスタ内の16ビットデータを送信する際にビッグエンディアンを使用します。しかし、仕様では、32ビットや64ビットが複数のレジスタでどのように配置されるかは規定されていない。2つの連続したレジスタの順序、および各レジスタ内の2バイトの順序は、デバイスメーカーに委ねられています。

これは鍋のように聞こえますが、1979 yearのModiconのエンジニアは32ビット浮動小数点を心配する必要はありませんでした。PLCは16ビット整数でさえ贅沢でした。40年後には自分たちでやらなければならない。

4つの文字列、4つの文字列。

ホールドレジスタをアドレス0(Modbusアドレス40001)から読み込み、2つのレジスタを要求するとします。局から返されるメッセージのデータ領域は:

41 DB 85 1F

これはIEEE 75 4単精度浮動小数点数の生のバイト列です。真の値は27.440271...。(27.44)4つの文字列の綴りを見てみましょう:

ABCD(ビッグエンディアン順序、Modbus標準配列)

レジスタ40001の値は`0x41`であり,レジスタ40002の値は` 0x851F `である. 32ビットの順序で:

レジスタ40001(上位16ビット)    レジスタ40002(下位16ビット)
+--------+--------+      +--------+--------+
|  0 × 41| 0 xdb|      |  0 x 85| 0 x 1 F|
+--------+--------+      +--------+--------+
  Aは        Bさんは               Cは        Dは

メモリビュー(低アドレスから高アドレス):`41DB 85 1F`

→ float32 = **27.44**

これはSchneider ModiconシリーズPLC(M 340、M 580、M 241など)のデフォルトの動作です。Modbusプロトコルの父であるシュナイダーは、常にこの標準的な配置に従っていました。小さなレジスタアドレスは16ビット、大きなレジスタアドレスは16ビットです。READ_VARで2つの単語を読み返すと、直接スペルが正しいです。

DCBA(リトルエンディアン、完全反転)

レジスタ40001(ただし内容が反転している)レジスタ40002
+--------+--------+      +--------+--------+
|  0 x 1 F| 0 x 85|      |  0 xdb| 0 × 41|
+--------+--------+      +--------+--------+

メモリ:`1F8541`

float32 = **1.5468 × 10(²)**-ゼロに近い極端な値で、画面は基本的に0.0またはオーバーフローエラーになります。

一部の国産Modbusモジュール(特に51マイクロコントローラや初期のARMベア走行に基づく機器)はこの方法を使用している。内部 CPUはリトルエンディアンであるため、直接 memcpyは本質的に“変換が面倒”です。北京のスマートパワーメーターのブランドは典型的な例で、バイト反転を手動で行う必要があります。

CDAB(ワードスワップ、レジスタ前後スワップ)

レジスタ40002の内容は、前のレジスタ40001の内容を後ろに置く
+--------+--------+      +--------+--------+
|  0 x 85| 0 x 1 F|      |  0 × 41| 0 xdb|
+--------+--------+      +--------+--------+

メモリ:`85 1F 41DB`

→ float32 = **-1.096 × 10⁻³³**

Siemens S 7 -1200/S 7 -1500がModbus TCPサーバを実行したときの実際の動作です。Siemens PLCの内部 REAL 型変数は4バイトを占め、DBブロックに格納された標準IEEE 75 4フォーマットですが、MB_SERVERCommandブロックはModbusクライアントにレジスタを公開するとき、最初のローアドレスレジスタのローバイト、次にハイバイト、次にハイアドレスレジスタのローバイト、ハイアドレスレジスタのハイバイト ***。これはシュナイダー ABCDの2つのワードを前後に交換することに相当する。Modbus Pollを使用してSiemens PLCの浮動小数点数を読み取ります。ディスプレイ設定でFloatを“CDAB”モードに切り替えないと、天文学的な数字になります。

libmodbusは、これら4つの順序に特化した関数セットを提供します。`modbus_get_float_abcd`、`modbus_get_float_dcba`、`modbus_get_float_badc`、`modbus_get_float_ctab`です。

BADC(バイトスワップ、ワードごとに内部反転)

レジスタ40001(内部バイトスワップ)レジスタ40002(内部バイトスワップ)
+--------+--------+      +--------+--------+
|  0 xdb| 0 × 41|      |  0 x 1 F| 0 x 85|
+--------+--------+      +--------+--------+

メモリ:`DB 41 1F 85`

→ float32 = **-1.457 × 10¹⁷**

デルタDVPシリーズ、三菱 FX 3 U/FX5U、H3 uシリーズ(H 5 u、H 5 u)は、Modbus RTU経由で32ビットデータを送信する際に実際にBADC配列を実行します。なんで?これらの日系/台湾系 PLCの内部データストア自体が“ワード内リトルエンド”であるため、単一の16ビットWORD 内部ローバイトが先行しています。Modbusトランスポート層では、WORDの上位バイト(Modbus要件)が先行し、WORD 内部で逆になります。2つのワードの間にはModbusを維持したビッグエンディアン順序がある.結果はBADCであり、ワード間のABCDの順序を維持するワードごとに内部バイトが反転します。

それほど厳密ではない例を挙げると、シュナイダーはパズルの箱全体を順番にあなたに(ABCD)、シーメンスは左右半分を最初に置き換えてからあなたに(CDAB)、デルタ/三菱/恵川は各半分の内側を最初にひっくり返してから順番にあなたに(BADC)する。DCBAに関しては、箱の内容が全く逆でした。

データ型はエンディアンよりもはるかに隠されている

同じ4バイトシーケンス`41DB 85 1F`、異なるデータ型でParse:

Parseの方法結果は
float32(IEEE 754)27.44
uint321,104,626,975
int321,104,626,975
Uint16の2つの組み合わせ[16859, 34079]

float32の27.44とuint32の11億のdifferenceを見てください。SCADAで110462 69 7 5.0 ° Cの温度測定ポイントが表示された場合は、センサを変更しないでください。まず、float32がuint32でParseされているかどうかを確認してください。

これは実際のエンジニアリングではバイトオーダーの問題よりも隠されていますなぜなら正しい数字が得られたが解釈が間違っているからですエンディアンが間違っています少なくとも数字は乱雑です一目でわかります型が間違っていると時にはそれがうまくいきます

現場の例を挙げる。ある太陽光発電所のインバータはModbus TCP経由でその日の発電量を送信しており、レジスタマップファイルには“40001-40002(日の発電量、32ビット)”と書かれています。エンジニアはint32を使用してParseし、正の値を読み取り、値は非常に合理的に見え、エネルギー消費プラットフォームで1 ヶ月実行しました。財務調整が30%のdifferenceを発見するまで、最初のチェックは知りません-メーカーが実際に持っているのは **uint32** であり、int32によると、高い値はシンボルビットとして処理され、発電量が2,14 7,48 3,64 7 Wh(2.15 GWh)を超える限り、解析値は負になります。

したがって、マルチレジスタデータポイントに直面して、変数は1つではなく、3つであることを確認する必要があります。

1.データ型(float32 / int32 / uint32 / uint64 / uint64 / double64 / string)とは何ですか? 2.(4つのうちの1つ)とは? 3.ズームファクターはありますか?例えば、メーターは元の値× 10または× 100を格納します。

第三はしばしば無視される。レジスタを節約し、浮動小数点転送を避けるために、多くのModbusメーターは浮動小数点値に10または100を掛けてint32に変換する。例えば、int32 = 2744と読み返すと、実際のtemperatureは27.44℃(÷100)、圧力は274.4kPa(÷10)である。このスケーリング係数はデバイスマニュアルにのみ記述されており、プロトコルメッセージにはまったく表示されません。

各主流メーカーの実態

このセクションは、この記事の最も実用的な部分です。以下のデータは、実際の試運転記録とコミュニティ検証からのものであり、製品マニュアルからのものではありません。

シュナイダーモディコンM340/M580/M241

Modbusプロトコルの発明者であり、最も“標準的”な振る舞いをしている。QUANTUM、M 340、M 580、M 241フルライン製品のModbus TCP/RTU Communicationは、32ビットデータ(REAL、DINT)はデフォルトで ***ABCD** 配列です。READ_VARで読み返された`%MW`レジスタは,下位アドレスに上位16ビット,上位アドレスに下位16ビットを格納する.

特記事項:シュナイダーのUnity Pro / EcoStruxure Control Expertでは、`%MD`(ダブルワード)と`%MF`(浮動小数点ダブルワード)の内部記憶はModbusレジスタマッピングと1層変換されている。`% MD0 `は` % MW0 `と` % MW1 `に対応し,このうち` % MW0 `は高位,` % MW1 `は低位― ― ―ちょうどABCDである。

結論:シュナイダー PLCがModbusスレーブを行う場合、上流はABCDで直接解析できます。

シーメンスS7-1200/S7-1500 MB_

これは最大の穴の一つですSiemensはTIA Portalで`MB_SERVER`Commandブロックを呼び出してModbus TCPサーバを作り,REAL 型変数(4バイト)の2つの保持レジスタにおける配列は **CDAB** である.

Siemens S 7 -1200/1500のDBブロックはデフォルトで“最適化されたブロックアクセス”であり、内部バイト配置は不透明です。最適化を解除しても(標準アクセスに設定して)、DB内のREAL 型変数のバイトオーダーはリトルエンディアン(Intel x 86プロセッサによって決定されます)になります。`MB_SERVER`がこのREALの4バイトをModbus 保持レジスタにマップすると,アドレスが小さいレジスタは0と1(REALの下位部分),アドレスが大きいレジスタは2と3(上位部分)というバイト順にパディングされる.

効果はCDABです。Modbus Pollのディスプレイ設定では、正しい浮動小数点数を表示するには“CDAB”をカットする必要があります。

Siemens PLCをModbusクライアント(`MB_CLIENT`)として使用してサードパーティ製のModbusスレーブを読み取る場合は、スレーブのエンディアンに依存し、Siemens内部のREALストレージは異なることに注意する必要があります。この場合、V 16以上で提供されている`READ_BIG` / `READ_LITTLE` / `WRITE_BIG` / `WRITE_LITTLE`コマンドを使用してエンディアン変換を行うことは、手動のSWAP加算シフトよりもはるかに信頼性が高いです。

デルタDVPシリーズ

デルタDVP-ES2/EX2/SV2シリーズなどのModbus RTUスレーブを行う場合、32ビット浮動小数点(Fレジスタ)をModbus 保持レジスタにマッピングする配列は **BADC** となります。

これはデルタの内部浮動小数点の格納方法に関係しています。デルタPLCの32ビットデータは“ローアドレス”(リトルエンド)ルールに従いますが、Modbus RTUフレームは各ワードをビッグエンドで送信する必要があります。したがって、ワード内部のバイトは一度反転しますが、2つのワード間のPLC 内部順序は維持され、最終的にBADCになります。

WPLSoftでD0 = F 100.0(浮動小数点数)と表示されている場合、Modbus Pollで40001~40002を読み取り、表示が“BADC”を選択すると、100.0が表示されます。他の3つの順序はすべてランダムです。

三菱 FX 3 U/FX5U(Modbus Communicationモジュール経由)

三菱 FX 3 UにFX 3 U-485 ADP-MB Communicationモジュールを搭載したModbus RTUマスターまたはスレーブ用の場合、32ビットデータの配列もBADC** になります。デルタのロジックと同じように、日本のPLCの内部アーキテクチャはリトルエンドです。

FX5U 本体にはModbus RTU機能(RS-485 端子経由)が搭載されており、ADPRWコマンドでCommunicationする場合、読み出した32ビットデータはMOVコマンドで手動でコンパイルする必要があります。MODBUSから2つのレジスタD 100、D 101を読み返すと、BADC配列の生データが格納され、各レジスタのバイトをSWAPCommandでスワップし、次にハイとローのスプライシングCommandで最終的な32ビット値に結合します。

三菱 PLCがシュナイダー周波数コンバータの周波数を読み取り、SWAPを行わずに逆に読み取り、2日間デバッグしてみると、エンディアンポットが見つかりました。

オムロンCP 1 H/CP1L

オムロンCP1シリーズは、CP1W-CIF11(RS-485オプションボード)を介してModbus RTUスレーブを行う場合、DMゾーンマッピングを使用してレジスタを保持します。2つのDMワード内の32ビットデータの配置は、プログラム方法によって異なります。

CP1シリーズ自体はワードアドレッシングアーキテクチャであり、ネイティブの2ワードデータ型はない。浮動小数点数は2つのDM単語で綴られ、順序はラダーグラフによって完全に決まります。しかし、ほとんどの標準実装は、CP1シリーズが初期のSYSMACアーキテクチャから派生したABCD** 配列に従っています。実際のデバッグでは、既知の値を使用することをお勧めします(例えば、浮動小数点1.0 = `3 F 80 0000`を2つのDMワードに書く)。

PLC(

Huithuan H 3 u/H 5 uシリーズはModbus RTUスレーブステーションを行い、32ビットデータのデフォルト ***BADC*** 配列、デルタと一致しています。しかし、(CODESYSプラットフォームに基づいて)Edituan AM600シリーズは異なります。CODESYS 内部実数ストレージは標準的なIEEE 75 4ビッグエンド構造であり、Modbus TCP Serverが公開するレジスタ配列はデフォルトで **ABCD** です。

Fuchuan Easyシリーズは、小型デバイス制御用のH 5 uの簡略版に属し、Modbus RTUスレーブの浮動小数点配列もBADC*** です。

歩科(Kinco)には一部の型番が **CDAB**、例えばK2シリーズがあります。これはSiemensに似ており、Boko K 2/K 5シリーズはソフトウェアレベルでSiemensのタッチスクリーンプログラミングロジックを借用している。

クイックチェックの概要:

メーカー/設備Modbus 32ビット配列備考:コメント
シュナイダー M340/M580/M241ABCDModbus工場出荷時の標準
シーメンスS 7 -1200/1500CDABMB_SERVERディレクティブ·ブロックの動作{{MB_SERVERこめんとぶろっくのどうさ}}
デルタDVPシリーズBADCワード内バイト交換
三菱 FX3U/FX 5UBADCSWAP後の接続
オムロンCP 1 H/CP1LABCD(ほとんどの実装)ラダーに依存する
H 3 U/H 5 UBADCデルタとの連携
川AM600ABCDCODESYSプラットフォーム
K 2シリーズ。CDAB一部モデル。
典型的な国産電力計DCBAまたはABCD個別テストが必要

注:この表は測定基準であり、公式なコミットメントではない。同じメーカーの異なるモデル、異なるファームウェアバージョンでは、エンディアン動作が異なる場合があります。本番環境に移行する前にテストしてください。

Modbus Pollのエンディアン切り替え

Modbus Pollは、ディスプレイ設定でデータフォーマット切り替え機能を提供します。データ表示領域を右クリック→フォーマットに選択すると、以下が表示されます。

シグニテッド       - 符号付き16ビット整数
Unsigned     - 符号なし16ビット整数
ヘックスは          - 16進法
Binaryより       - Binaryシングル
32-ビット·シグネチャ    - 32ビット符号付き整数
32-bit unsigned-32ビット符号なし整数
32-ビット·フロート     - 32ビット浮動小数点数
64-ビット·フロート     - 64ビット倍精度小数点数

32ビットFloatを選択すると、右側にエンディアンオプションがポップアップします。

ABCD(Float Big-endian)         - ビッグエンド
Float Little-endian DCBA      - 小さな端。
BADC(Float Big-Endianバイトスワップ)   - ビッグエンディアン·バイト交換
Float Little-endian byte swap CDAB-リトルエンディアン·バイトスワップ

どのメーカーが使用するかを知る必要はありません。デバッグ時に4つの順序をカットし、どの値が妥当かを確認します。測定範囲を決定する既知の測定点(周囲温度が20~30 ° Cの間など)を読み取ると、4つの順序のうち1つだけが妥当な範囲に入ります。

64 bitデータの処理

電力累積値、総実行時間、大容量トラフィック蓄積-これらはしばしば4つのレジスタ(8バイト)で表現する必要があり、エンディアンの問題はより複雑です。2つのレイヤーを処理する必要があります。

レイヤー 1:各32ビットDWORD 内部のエンディアン(前の32ビットシナリオと同様に、4つの可能性があります) レイヤ2:2つの32ビットDWORD間の順序(レジスタ1-2は上位32ビットか下位32ビットか)

4つのレジスタを読み込み、元のバイトシーケンスを返すとします。

41 DB 85 1F 42 C8 00 00

“上位32ビット、下位32ビット、各32ビットをABCD”とすると、これが最も一般的な順序です。

上位32ビットABCD 41 DB 85 1F → float32 = 27.44
下位32ビットABCD 42C8 00 00 → float32 = 100.0

注:これは典型的なトタライザの構築方法です。上位32ビットは大部分、下位32ビットは小数部分、または上位32ビットは整数部分、下位32ビットは小数部分を保持します。組み合わせ方は機材マニュアルを参照してください。

最も一般的なシナリオはuint64累積値です。4つのレジスタはABCDで配置され、レジスタアドレスの低い順に次のとおりです。

レジスタ1 MSW-High A B →バイト0-1
レジスタ2 MSW-Low C D →バイト2-3   高さは32。
レジスタ3 LSW-High E F →バイト4-5
レジスタ4 LSW-Low G H →バイト6-7   低32ビットを構成する

uint64 `uint64_t reg1<< 48| REG 2<< 3 2| REG 3<< 16| reg4 `ただし,この式はCPU自体がビッグエンディアンの場合にのみ成り立つ. x 86(リトルエンド)でCコードを書く場合は、逆にする必要があります。

測定経験:64ビットデータを処理するときは、まずエンディアンをもつれず、最初に4つのレジスタの元の値を16進数で読み出し、アキュムレータが成長しているかどうかを確認し、成長方向を観察します。アキュムレータが毎回増加する場合、変化がローアドレスレジスタから始まるか、ハイアドレスレジスタから始まるかを確認し、ハイアドレスレジスタから始まるかを確認し、ハイとローのビットの配列がわかります。

libmodbusは64ビット変換関数を直接提供しないので、自分で書く必要があります。

Uint16_t regs[4];
mod_read_registers ctx 4 regs;
//ABCD配列を仮定する
uint64_t value = uint64_t regs[0]<< 48|
                 ((uint64_t)regs[1]<< 32)|
                 ((uint64_t)regs[2]<< 16)|
                 ((uint64_t)regs[3]);

しかし、これは4つの順序のうちの1つに過ぎませんデバイスがBADC配列の場合は、まず各レジスタのバイトをスワップし、CDABの場合は、レジスタペアの位置をスワップします。

文字列の保存

デバイスのシリアル番号、ファームウェアバージョン番号これらのASCII文字列をModbusレジスタに格納するには、2つの主要なモードがあります。

** モード1:レジスタあたり2文字(標準)**

各16ビットレジスタは2バイトを格納し、各バイトに1文字のASCII文字を格納する。例えば、シリアル番号“SN20240001”(10文字)の場合、5つのレジスタが必要です。

レジスタ1 0x53 0x 4E → S N'
レジスタ2 0x32 0x30 → 2 0'
レジスタ3 0x32 0x34 → 2 4'
レジスタ4 0x30 0x30 → 0 0'
レジスタ5 0x30 0x31 → 0 1'

読み取りは直接バイトでASCII値を変換するだけで、エンディアンを気にする必要はありません--ASCII文字`A`は`0 x 41`であり、大きな端に関係なく。

** モード2:レジスタあたりの下位バイトのみ有効 **

一部の機器メーカーは怠惰で、各レジスタは下位8ビットのみを1文字、上位8ビットは0 x 00です。同じ“SN20240001”には、10 个のレジスタが必要です。

レジスタ1 0x00 0x53 → 空S'
レジスタ2 0x00 0x4E → 空N'
...

これはレジスタの半分のスペースを無駄にしますが、Parseは簡単です。各レジスタの下位バイトを取るだけです。

ヒント:ASCIIはシングルバイトエンコーディングであるため、文字列のバイトオーダーの問題はほとんどexistenceしません。本当に問題になるのは、UTF-16やその他のマルチバイトエンコーディングを使用する場合です。しかし、それはもはやModbusプロトコルの領域ではなく、純粋にアプリケーション層のエンコーディングオプションです。

デバッグ方法論

推測しないでデータを使ってください以下は標準的なバイトオーダーエラー処理手順です。

** 最初のステップ:オリジナルの16進数を読む **

Modbus Pollを使用して表示フォーマットをHexにカットし、元のレジスタ値を書き留めます。まず、裸のデータを見てください。

40001-40002の読み取りは、PowerPath 41 DB 85 1Fを返します。

** ステップ2:既知の値を逆推定する **

本当の価値を知っているポイントを見つけなさい。現在の気温は27.44 ℃。デバイスの内蔵ディスプレイから値を読み取った場合は、メモしてください。4つの順序のどれが27.44であれば正解です。

より良い方法-既知の値を積極的に書く。関数コード16(複数のレジスタを書き込む)を使用して、`0 x 3 F 80 0000`(float32 = 1.0の標準IEEE 75 4表現)をレジスタペアに書き込み、読み戻します。オリジナルHexxを見る:

- 読み返すと`3F 80 00 00` → ABCD - 読み返すと`00 00 3F 80` → BA - 読み返すと`80 3F 00 00` → BADC - 読み返すと`00 00 80 3F` → CDAB

** ステップ3:Wiresharkでパケットをキャプチャする **

Modbus TCPのポート数は502です。Wiresharkでは`tcp.port == 502`をフィルタリングし,右→ Decode As → Modbus/TCP.レスポンスでは、データ領域は生のバイト列です。このシーケンスを解析結果と比較してください:

Wiresharkのメッセージ
    Modbus/TCP
        Transaction Identifier 1
        Protocol Identifier 0
        Length: 7
        Unit Identifier 1
        Function Code 3 Read Holding Registers
        Byte 4
        Register 0 40001 0x41db
        Register 1 40002 0x851f

Wiresharkが表示するレジスタ値は、ビッグエンディアンでParseした結果です。PLC 内部にBADCがある場合、Wiresharkが示した'0 x 41db'と'0 x 851f'はもはや生のバイト列ではありません。生のバイト列は'DB 41 1 F 85'です。

そのため、エンディアンの問題をデバッグするときは、レジスタ値をParseするのに役立つよりもWiresharkの16進ダンプ ** を見る方が良いでしょう。メッセージの詳細で“Data”フィールドを展開し、元のバイトを確認します。

** ステップ4:オンラインバイトエンディアン計算機 **

コンセプトは簡単です。4つの16進数バイトを入力すると、float32、int32、uint32の4つの順序を計算し、どちらが正しいかを一目で確認できます。

利用できない場合は、自分で書くのは難しくありません。

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void print_float(uint8_t *bytes) {
    float f;
    memcpy(&f, bytes, 4);
    printf("float32: %fn", f);
}

int main() {
    uint8_t data[4] = {0x41, 0xDB, 0x85, 0x1F};

    uint8_t abcd[4] = {data[0], data[1], data[2], data[3]};
    uint8_t dcba[4] = {data[3], data[2], data[1], data[0]};
    uint8_t badc[4] = {data[1], data[0], data[3], data[2]};
    uint8_t cdab[4] = {data[2], data[3], data[0], data[1]};

    printf("ABCD: "); print_float(abcd);
    printf("DCBA: "); print_float(dcba);
    printf("BADC: "); print_float(badc);
    printf("CDAB: "); print_float(cdab);

    return 0;
}

付録:Modbus CRC-16ルックアップテーブルのC実装

バイトオーダーの問題は、しばしばCRCチェックのバイトオーダーと混同されます。アルゴリズムに問題があるわけではありませんが、CRC計算結果の2バイトがメッセージ内で最初に誰が後に来るのか、文書によって異なります。

Modbusの仕様は明確です:CRC-16の最も低いバイトの先頭です。つまり、CRC計算結果が`0 x 1234`である場合、メッセージの最後の2バイトは`34 12`であり、`12 34`ではない。

以下のルックアップテーブル実装は、Modbus仕様に準拠した結果を直接返します。下位バイトはメッセージの最後に直接追加できます。

#include <stdint.Hは>

/* CRC-16 Modbusルックアップテーブル *
static const uint 16_t crc16_table[256] = {
    0x0000 0xC0C1 0xC181 0x0140 0xC301 0x03C0 0x0280 0xC241
    0xC601 0x06C0 0x0780 0xC741 0x0500 0xC5C1 0xC481 0x0440
    0xCC01 0x0CC0 0x0D80 0xCD41 0x0F00 0xCFC1 0xCE81 0x0E40
    0 x 0 A 00、0 x CAC 1、0 x CB 81、0 x 0 B 40、0 x C 901、0 x 0 9 C 0、0 x 0 880、0 x C 841、
    0 x D 801、0 x 18 C 0、0 x 1980、0 x D 941、0 x 1 B 00、0 x DBC 1、0 x DA 81、0 x 1 A 40、
    0x1E00 0xDEC1 0xDF81 0x1F40 0xD01 0x1DC0 0x1C80 0xDC41
    0x1400 0xD4C1 0xD581 0x1540 0xD701 0x17C0 0x1680 0xD641
    0xD201 0x12C0 0x1380 0xD341 0x1100 0xD1C1 0xD081 0x1040
    0xF001 0x30C0 0x3180 0xF141 0x3300 0xF3C1 0xF281 0x3240
    0x3600 0xF6C1 0xF781 0x3740 0xF501 0x35C0 0x3480 0xF441
    0x3C00 0xFCC1 0x81 0x3D40 0xFF01 0x30 0x3E80 0xFE41
    0 x FA 01、0 x 3 AC 0、0 x 3 B 80、0 x FB 41、0 x 3900、0 x F 9 C 1、0 x F 881、0 x 38 40、
    0 x 2800、0 x E 8 C 1、0 x E 981、0 x 2940、0 x EB 01、0 x 2 BC 0、0 x 2 A 80、0 x EA 41、
    0xEE01 0x2EC0 0x2F80 0x41 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 0x6FC0 0x6E80 0xAE41
    0 x AA 01、0 x 6 AC 0、0 x 6 B 80、0 x AB 41、0 x 6900、0 x A 9 C 1、0 x A 881、0 x 6840、
    0 x 7800、0 x B 8 C 1、0 x B 981、0 x 7940、0 x BB 01、0 x 7 BC 0、0 x 7 A 80、0 x BA 41、
    0xBE01 0x7EC0 0x7F80 0xBF41 0x7D00 0xB1 0x81 0x7C40
    0xB401 0x74C0 0x7580 0xB541 0x7700 0xB7C1 0xB681 0x7640
    0x7200 0xB2C1 0xB381 0x7340 0xB101 0x71C0 0x7080 0xB041
    0 x 5000、0 x 90 C 1、0 x 91 8 1、0 x 51 4 0、0 x 9301、0 x 53 C 0、0 x 528 0、0 x 9241、
    0 x 96 01、0 x 56 C 0、0 x 57 80、0 x 97 41、0 x 5500、0 x 95 C 1、0 x 9481、0 x 54 40、
    0 x 9 C 01,0 x 5 CC 0,0 x 5 D 80,0 x 9 D 41,0 x 5 F 00,0 x 9 FC 1,0 x 9 E 81,0 x 5 E 40,
    0 x 5 A 00、0 x 9 AC 1、0 x 9 B 81、0 x 5 B 40、0 x 9901、0 x 59 C 0、0 x 588 0、0 x 9841、
    0 x 8801、0 x 48 C 0、0 x 4980、0 x 8941、0 x 4 B 00、0 x 8 BC 1、0 x 8 A 81、0 x 4 A 40、
    0x4E00 0x8EC1 0x8F81 0x4F40 0x8D01 0x4DC0 0x4C80 0x8C41
    0x4400 0x84C1 0x8581 0x4540 0x8701 0x47C0 0x4680 0x8641
    0x8201 0x42C0 0x4380 0x8341 0x4100 0x81C1 0x8081 0x4040
};

/*
 * Modbus CRC-16を計算し、Modbusフレーム形式に準拠した結果を返す
 * 下位バイトが先頭(下位アドレス)で、メッセージの最後に直接追加できます。
 */
uint16_t crc16_modbus const uint8_t *data uint16_t len
{
    uint8_t crc_hi = 0 xFF;
    uint8_t crc_lo = 0xFF;
    Uint8_T IDX;

    while len--{
        idx = crc_hi ^ *data++;
        crc_hi = crc_lo ^uint8_t crc16_table[idx] 8;
        crc_lo = uint8_t crc16_table[idx] 0xFF;
    }

    /*
     * 16ビットのCRC値を返す
     * Modbus RTUフレームの最後に追加する:
     *   frame[len]   = crc 0 xFF;        //Low Byte Beforeシングル
     *   frame[len+1] = crc 8 0xFF; //上位バイトは後に
     */
    return uint16_t crc_<<lo8| CR_Hi;
}

他のCRC-16の実装を見ると、いくつかの結果はハイバイトの前とローバイトの前になります。相違点は`return`文--`<<crc_lo8| crc_hi`と` crc_hi<< 8| crc_lo`は逆です。Modbusの仕様では、CRCフィールドは“ローバイト前”であるため、上記の実装ではuint 16_tを返しますが、下位8ビットがCRCのローバイトで、上位8ビットがCRCのハイバイトであることがわかります。メッセージに追加する际には,`crc 0xFF`を先に书き,`crc 8 0xFF`を次に书く.

`return crc_hi8を使用した<<場合|crc_lo `では、追加順序が逆になります。これは、多くのデバッグで“CRCチェックが常に間違っている”ことがわかる理由の1つです。

再び話す問題がある。次にバイト順序が間違っている場合は、メーカーのマニュアルを読むよりも速く、ABCD/DCBA/BADC/CDABの4バイトを直接実行します。

Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.