mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)

無料無料技术资料

这篇内容可直接阅读,适合用于基础学习和検索引流。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)

Modbusは、オープンな産業用通信プロトコルとして、さまざまな産業機器で広く使用されています。私もModbus通信を長年使ってきました。既製のものやアプリケーション固有のものを使っています。毎回書くのではなく、その後のプロジェクトで再利用できる、より汎用的なプロトコルスタックを開発したいと思っています。プロジェクト開発の機会を利用して、独自のModbusプロトコルスタックを開発してください。

Modbusには国際規格と国内規格があり、内容はまったく同じです。標準時計は2つの物理リンクをサポートします。RS485 RS232用シリアルリンク2つ目はEthernet用TCP/IPリンク。実際、アプリケーション層プロトコルとしてのModbusプロトコルは、物理キーに特別な要件はなく、光ファイバー、ワイヤレスなどを実装することができます。

今回は主に開発Modbus RTUそして、Modbus TCP一般的な機能コードをサポートする2つの標準プロトコル方式:

関数コードの特徴名前は実現すること説明する。
0x01リードコイルを読むはい。読み書き可能型の状態量の読み取りを行う
0x02離散入力の読み取りはい。読み取り専用タイプの状態量の読み取り
0x03リードホールドレジスタはい。読み書き可能型のレジスタ量を読み出しする
0x04読み取り入力レジスタはい。リードオンリー型のレジスタ量の読み出し
0x05シングルコイルを書く。はい。単一の読み書き型の状態量を書き込む
0x06単一レジスタの書き込みはい。単一の読み書き型のレジスタ量に対して書き込みを行う
0x0F複数のコイルを書く。はい。複数の読み書き型の状態量を書き込む
0x10複数のレジスタを書く。はい。複数の読み書き型のレジスタ量に対して書き込みを行う

Modbusプロトコルはマスター·マスター(またはクライアント/サーバ)モードプロトコル。マスター(クライアント)取引依頼の開始、スレーブ(サーバ)トランザクション要求への対応一般的に、私たちがアクセスしたい(またはアクセスされる)デバイスはスレーブ(サーバー)であり、私たちが通常他の人のデバイスにアクセスするために行くデバイスはマスター(クライアント)です。RTUの場合はマスタとスレーブと呼ばれることが多く、TCP方式の場合はサーバとクライアントと呼ばれる。名前は違うだけですが、本質的には私を区別しません。この2つの名前を同時に使用します。今回の開発では、マスターとスレーブの両方の機能を実現する予定です。

1標準的な流れ

MODBUSトランザクションを開始したクライアントは、MODBUSアプリケーションデータユニットを作成します。クライアントからサーバデバイスにメッセージが送信されると、ファンクションコードは実行されるアクションの種類をサーバに示します。

クライアントからサーバデバイスに送信されるメッセージデータフィールドには、関数コードで定義された操作を実行するためにサーバーが使用する追加情報が含まれます。正しく受信されたMODBUS ADUに要求されたMODBUS機能に関連するエラーがない場合、サーバーからクライアントへの応答データフィールドには要求データが含まれます。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图

通常の取引フロー

MODBUS関数の要求に関連してエラーが発生した場合、フィールドには例外コードが含まれ、サーバーアプリケーションが次に実行するアクションを決定するために使用できます。サーバがクライアントに応答すると、関数コードフィールドを使用して、通常の(エラーのない)応答または何らかのエラー(例外応答と呼ばれる)が発生したことを示します。通常のレスポンスでは、サーバは元の関数コードにのみ応答します。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图1

例外取引フロー

2·オペレーションデザイン

Modbusの標準トランザクションフローを理解した上で、マスターとスレーブの特定の操作プロセスを設計します。

主要な操作手順:

(1)プライマリ局、ステーション識別子、タイマーの初期化、

(2)アクセス命令の生成

ADUは、ネットワーク層とリンク層の2つの層で実装されている。

ネットワーク層は、特定のリンクに依存しないPDU(関数コードとデータを含む。ここで、データは送信されるデータであり、変数の値ではなく、実際には数とアドレスも含む)を実装する。対応するファイルはmbpdu.c/.hで、コマンドとレスポンスを含むPudユニットのデータ処理を実装しています。

リンク層はADUのカプセル化を実装します。対応するファイルはmbrtu.c/. hおよびmbtcp.c/.hおよびmbascii.c/.hで、ADUユニットのカプセル化を実装します。また、スレーブとして使用する場合の書き込み操作の応答もここにカプセル化されます。

(3)アクセスコマンドをタイミングで送信し、リードコマンドはポーリングでなければならず、ライトコマンドは優先度が高い。書き込みコマンドがある場合は、読み取りコマンドを一時停止し、

(4)受信したデータを解析し,対応する処理を行う

ステーションからの操作手順:

(1)プロトコルスタックの初期化:スレーブアドレス設定、データストアドメインの作成、アクセス制御

(2)送信局からのメッセージ受信と解析

メッセージ解析後のデータ操作(対応するオブジェクトの値の読み書き)

応答の生成

(3)応答データの生成と応答

3コマンド形式。

異なる関数コードは異なるメッセージ形式を持っていますが、上記の8つの関数コードの情報形式を実装するだけです。

(1)機能コード0 x 0 1:読み取りコイル

リモートデバイスでは、この機能コードを使用してコイルの1 ~ 2000個の連続状態を読み取ります。要求PDUには、開始アドレス、すなわち、指定された最初のコイルアドレスとコイル番号が詳細に記載されています。コイルをゼロからアドレス指定します。したがってアドレス指定コイル1-16は0-15である。

応答メッセージ内のコイルは、データフィールドの各ビットに応じてコイルに分割されます。1= ONと0= OFFを指定します。最初のデータバイトのLSB(最下位ビット)には、問い合わせでアドレス指定された出力が含まれます。他のコイルは、このバイトの上位の端まで続き、後続のバイトの下位から上位へと続きます。

返される出力の数が8の倍数でない場合、最後のデータバイトの残りのビット(バイトの上位端まで)はゼロで埋められます。バイト数フィールドには、データの完全なバイト数が示されます。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图2

(2)関数コード0 x 0 2:離散入力を読み取る

リモートデバイスでは、機能コードを使用して、離散量入力の1 ~ 2000連続状態を読み取ります。要求PDUには、開始アドレス、すなわち指定された最初の入力アドレスと入力番号が詳細に記載されています。入力をゼロからアドレス指定します。したがって、アドレス指定入力1-16は0-15である。

応答メッセージ内の離散入力は、データフィールドの各ビットに基づいて1つの入力に分割されます。1=ONと0=OFFを指定します。最初のデータバイトのLSB(最下位ビット)は、問い合わせでアドレス指定される入力を含んでいます。他の入力は、このバイトの上位の端まで続き、後続のバイトの下位から上位へと続きます。

返される入力数が8の倍数でない場合、最後のデータバイトの残りのビット(バイトの上位端まで)はゼロで埋められます。バイト数フィールドには、データの完全なバイト数が示されます。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图3

実際、関数コード0 x 0 1と0 x 0 2のデータフォーマットはまったく同じであり、操作方法もまったく同じですが、操作対象が異なるだけです。

(3)関数コード0 x 0 3:リードホールドレジスタ

1つのリモートデバイスでは、この機能コードを使用して保持レジスタの連続ブロックの内容を読み取る。要求PDUには、開始レジスタ·アドレスとレジスタ数が記載されています。レジスタをゼロからアドレス指定します。したがって、アドレスレジスタ1-16は0-15である。

応答メッセージ内のレジスタデータを各レジスタに2バイトに分割し、バイナリ内容を各バイトで直接調整します。

各レジスタについて、最初のバイトは上位ビットを含み、2番目のバイトは下位ビットを含む。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图4

応答情報の長さは、具体的に読み取られるデータ量に関係します。

(4)関数コード0 x 0 4:入力レジスタの読み取り

リモートデバイスでは、関数コードを使用して、1 ~約125の連続入力レジスタを読み取る。リクエストPDUには、開始アドレスとレジスタ数が記載されています。レジスタをゼロからアドレス指定します。したがって、アドレス指定入力レジスタ1 〜 16は0 〜 15である。

応答メッセージ内のレジスタデータを各レジスタに2バイトに分割し、バイナリ内容を各バイトで直接調整します。

各レジスタについて、最初のバイトは上位ビットを含み、2番目のバイトは下位ビットを含む。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图5

実際、関数コード0 x 0 3と0 x 0 4のデータフォーマットはまったく同じであり、操作方法もまったく同じですが、操作対象が異なるだけです。応答情報の長さは、具体的に読み取られるデータ量に関係します。

関数コード0 x 0 5:単一コイルを書き込む

リモートデバイスでは、関数コードを使用して単一の出力をONまたはOFFに書き込みます。

要求データフィールドの定数は、要求のON/OFF状態を指定します。16進値0 x FF 00要求出力はONである。16進値0 x 0000要求出力はOFFです。その他の値はすべて不正であり、出力には影響しません。

要求PDUには、必須のコイルアドレスが記載されています。コイルをゼロからアドレス指定します。したがって、アドレス指定コイル1は0である。コイルの範囲の定数は、要求のON/OFF状態を指定します。16進値0 XFF 00は、コイルをONにすることを要求します。16進値0 X 0000はコイルをオフにするよう要求します。その他の値はすべて不正であり、コイルには効果がありません。

ノーマルレスポンスはリクエストに対するレスポンスであり、コイル状態に書き込まれた後にこのノーマルレスポンスを返します。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图6

関数コード0 x 0 6:単一のレジスタに書き込む

リモートデバイスでは、この関数コードを使用して単一のホールドレジスタを書き込みます。

要求PDUは、レジスタに書き込まれるアドレスを示す。レジスタをゼロからアドレス指定します。したがって、アドレスレジスタ1は0である。

ノーマルレスポンスはリクエストに対するレスポンスであり、レジスタ内容を書き込んだ後にこのノーマルレスポンスを返す。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图7

(7)関数コード0 x 0F:複数のコイルを書き込む

リモートデバイスでは、機能コードを使用して、コイルシーケンス内の各コイルを強制的にオンまたはオフにします。要求PDUには、必須のコイル参照が記載されています。コイルをゼロからアドレス指定します。したがって、アドレス指定コイル1は0である。

要求データフィールドの内容は、要求されたON/OFF状態を記述します。フィールドビット位置における論理“1”は、該当する出力をONにすることを要求する。フィールドビット位置における論理“0”は、該当する出力をOFFにすることを要求する。

通常の応答は、フィーチャーコード、開始アドレス、および強制コイル数を返します。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图8

関数コード0 x 10:複数のレジスタを書き込む

リモートデバイスでは、関数コードを使用して連続レジスタブロック(1 〜約120レジスタ)を書き込む。

要求データフィールドには、要求された書き込みの値が記載されています。各レジスタはデータを2バイトに分割します。

通常の応答は、関数コード、開始アドレス、および書き込まれたレジスタの数を返します。具体的な形式の例は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图9

これらの説明を念頭に置いて、Modbusプロトコルの開発の旅を始めることができます。

前にModbusの基本的なトランザクションを説明し、それに応じて実装するマスター/スレーブステーションの動作フローを設計しました。Modbusに直接関係するのは、Modbusメッセージフレームの生成である。ModbusメッセージフレームはModbus通信プロトコルの基本でもある。

1、Modbusメッセージフレーム解析

MODBUSプロトコルは、異なる物理リンク上のメッセージフレームにいくつかの違いがありますが、分析すると、これらの異なるメッセージフレーム内に同じ部分があることがわかります。これは、統一されたデータ操作を実現するために非常に重要です。

(1、Simple Protocol Data Unit

MODBUSプロトコルは、基礎となる通信層とは無関係な、単純なプロトコルデータユニットPDUを定義します。Simple Protocol Data Unitの構造は次のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图10

PDUは、機能コードとデータを含む特定の伝送ネットワークに依存しない部分です。特定のバス又はネットワーク上のMODBUSプロトコルは、PDUに加えてアプリケーションデータユニットADU上にいくつかの追加ドメインを導入するに過ぎない。

データユニット部分の開発は、最も基本的な部分であり、主に2つの側面のクラスサイズです。1つは、クライアント(マスター)がサーバー(スレーブ)にアクセスするコマンド部分を生成し、2つ目は、サーバー(スレーブ)応答クライアント(マスター)応答部分を生成することです。

(2)、RTUアプリケーションデータユニット

シリアルリンク上で動作するModbusプロトコルの場合、そのアプリケーションデータユニットADUはPDUに加えて、前にアドレスフィールドを、後にデータチェックを加える。フォーマットは次の図のようになります。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图11

アドレスフィールドは、訪問されたスレーブステーションのアドレスであり、0から255の値を持つ8ビットの符号なし番号であるが、0と255は固定の意味を持つことはできない。CRCチェックはCRC16チェック方式を使用します。

(3)、TCPアプリケーションデータユニット

イーサネットリンク上で動作するModbusプロトコルは、そのアプリケーションデータユニット(ADU)はPDUにMBAPヘッダを追加して形成され、具体的なフォーマットは以下のとおりである。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图12

MBAPヘッダの場合、以下のフィールドが含まれます。

ドメイン名:Domain長さは説明する。クライアント·クライアントサーバー·サーバー
トランザクションメタ識別子2 bytesMODBUSリクエスト/レスポンス·トランザクションの識別子クライアントの起動サーバは受信した要求から再複製する
プロトコル識別子2 bytes0=MODBUSプロトコルクライアントの起動サーバは受信した要求から再複製する
長さは2 bytes次のバイト数クライアント(要求)サーバ応答
ユニット識別子1 byteよりシリアルリンクまたは他のバスに接続された远隔スレーブ局のコードクライアントの起動サーバは受信した要求から再複製する

上の表から、ヘッダは7バイト長であることがわかります。

取引子取引ペアで使用します。応答では、MODBUSサーバは要求のトランザクション識別子を複製します。
プロトコル識別子:システム内の多重化に使用されます。MODBUSプロトコルは、値0で識別されます。

長さ:長さフィールドは、セル識別子とデータフィールドを含む、次のフィールドのバイト数です。

セル識別子:システム内ルーティングのためにこのドメインを使用する.イーサネットTCP-IPネットワークとMODBUSシリアルリンク間のゲートウェイを介したMODBUSまたはMODBUS+シリアルリンクスレーブ間の通信に特化しています。単純なポイントは、シリアルリンク内のアドレスフィールドです。MODBUSクライアントはリクエストでこのフィールドを設定し、サーバはレスポンスで同じ値を返しなければなりません。

2データフレームの具体的な構成分析

単純プロトコルの基本データ要素、RTUアプリケーションデータユニット、TCPアプリケーションデータユニットのメッセージフォーマットの上記の分析から、基本データユニットの部分が一致していることがわかり、プロトコルの操作部分を階層的にカプセル化することを検討することができます。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图13

まず、Modbus基本データユニットを実装し、データ共有部分は特定のアプリケーションとは関係なく、一度だけパッケージ化する必要があり、この部分の開発はModbusの標準プロトコルに従ってのみ開発する必要があります。

関数コードの特徴名前は実現すること説明する。
0x01リードコイルを読むはい。読み書き可能型の状態量の読み取りを行う
0x02離散入力の読み取りはい。読み取り専用タイプの状態量の読み取り
0x03リードホールドレジスタはい。読み書き可能型のレジスタ量を読み出しする
0x04読み取り入力レジスタはい。リードオンリー型のレジスタ量の読み出し
0x05シングルコイルを書く。はい。単一の読み書き型の状態量を書き込む
0x06単一レジスタの書き込みはい。単一の読み書き型のレジスタ量に対して書き込みを行う
0x0F複数のコイルを書く。はい。複数の読み書き型の状態量を書き込む
0x10複数のレジスタを書く。はい。複数の読み書き型のレジスタ量に対して書き込みを行う

これら8つはModbusプロトコルで定義されている最も重要な機能であり、これらの機能コードのメッセージフォーマットは以下のように説明されている。

(1(リードコイル)0x01

ModbusプロトコルはPLCアプリケーションから派生しており、コイルはPLCのDO出力の名前であり、一般的にマスターステーションからスレーブステーションへの操作コマンドに適用されるため、読み取りコイルは書き込み可能なスイッチ量の一種です。この読み書き機能を持つ状態量を読み取るデータフォーマットは次のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图14

コマンドの形式は、ドメイン名+機能コード+開始アドレス+番号です。

(2離散入力の読み取り0x02

読み取りステータス入力は、PLCのデジタル入力量に対応する読み取り専用のスイッチング量信号を読み取ります。この読み取り専用スイッチ入力の読み取り形式は次のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图15

コマンドの形式は、ドメイン名+機能コード+開始アドレス+番号です。

(3)リードホールドレジスタ0x03

ホールドレジスタは、8ビット整数、16ビット整数、32ビット整数、64ビット整数、単倍精度浮動小数点数など、さまざまなデータを表すために単一または複数のホールドレジスタを使用することができます。読み出し保持レジスタのメッセージ形式は以下の通りである。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图16

コマンドの形式は、ドメイン名+機能コード+開始アドレス+番号です。

(4(読み取り入力レジスタ)0x04

入力レジスタは、読み取り専用の16ビットデータです。単一または複数の入力レジスタを介して、8ビット整数、16を整数、32ビット整数、64ビット整数、単倍精度浮動小数点数などを表現することができます。入力レジスタを読み取るメッセージのフォーマットは以下のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图17

コマンドの形式は、ドメイン名+機能コード+開始アドレス+番号です。

(5シングルコイルを書く。0x05

単一のコイルを書き込むことは、単一の読み書き可能なスイッチを操作することですが、直接“0”や“1”を書き込むのではなく、“1”を書き込む必要があるときに0 x FF 00を送信し、“0”を書き込む必要があるときに0 x 0000を送信します。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图18

コマンドの形式は、ドメイン名+機能コード+出力アドレス+出力値です。コマンドの具体的な内容は読み取り操作とは異なりますが、フォーマットはまったく同じであり、プログラミング中に実際の読み取りと書き込みを一緒にカプセル化することができます。

(6単一のレジスタに書き込む0x06

単一のレジスタを書くことは、単一の保持レジスタを操作することであり、データの形式は同じであり、実際のアプリケーションでは、浮動小数点演算ではなく、16ビット整数データ操作にのみ適用されます。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图19

コマンドの形式は、ドメイン名+機能コード+出力アドレス+出力値です。コマンドの具体的な内容は読み取り操作とは異なりますが、フォーマットはまったく同じであり、プログラミング中に実際の読み取りと書き込みを一緒にカプセル化することができます。

(7複数のコイルを書く。0x0F

複数のコイルを書く操作対象は、単一のコイルを書くのと全く同じですが、違いは量と操作値、特に値です。“1”と書くと“1”、“0”と書くと“0”と書くと、単一のコイルを書くのとの違いです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图20

コマンドの形式は、ドメイン名+関数コード+開始アドレス+出力数+バイト数+出力値です。コマンドメッセージは、これまでの読み書き操作とは大きく異なり、別々に処理する必要があります。

(8複数のレジスタを書く。0x10

複数のレジスタを書き込むのは、複数の読み書き可能なレジスタを同時に操作することであり、データパケットのフォーマットは複数のコイルを書き込むのと同じです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图21

コマンドの形式は、ドメイン名+関数コード+開始アドレス+出力数+バイト数+出力値です。

3基本データ単位のプログラミング

以上の分析の結果、どのような物理リンク上に実装されたアプリケーションデータであっても、基本データセグメントは同じであることがわかります。実際には、ドメイン名セクションのフォーマットも同じなので、そうします。

ドメイン名+PDUは、最も基本的なデータ単位として実装されます。

基本データユニットの実装は、マスター(クライアント)としての場合、スレーブ(サーバ)への送信コマンドと、スレーブ(サーバ)としての場合、マスター(クライアント)コマンドへの応答の2つのケースに分けられます。そこで、これらのケースを2つの基本関数にカプセル化します。

(1)RTUマスタ(TCPクライアント)として、RTUスレーブ(TCPサーバ)オブジェクトを読み書きするコマンドを生成します。

uint16_t GenerateReadWriteCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

パラメータは、それぞれ、PDUユニットの基本情報、書き込み対象の対応するデータ、および生成されたコマンドバイトである。戻り値は、生成されたコマンドの長さです。

(2)スレーブ(サーバ)とした場合には、親局のリードアクセスの応答を生成する。書き込みに対するレスポンスは実際にはレプリケーションマスター(クライアント)のコマンドの一部であるため、実際に生成する必要があるレスポンスは、0 x 0 1、0 x 0 2、0 x 0 3、0 x 0 4関数コードを含む場合になります。

uint16_t GenerateMasterAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

パラメータは、受信した情報、読み込まれたオブジェクトのデータ、および返されたレスポンスメッセージです。戻り値は、返された応答メッセージの長さです。

4、RTUアプリケーションデータユニットのプログラミング

RTUアプリケーションデータユニットの場合、メッセージ形式は“ドメイン名+PDU+CRC”であり、ドメイン名+PDUは前のセクションで実装したので、RTUデータユニットを実装するには、実際にはCRCチェックを追加するだけで完了します。

RTUデータユニットの実装は、マスターステーションとしての場合、スレーブステーションへの送信コマンドと、スレーブステーションとしての場合、マスターコマンドへの応答の2つのケースに分けられます。そこで、これらのケースを2つの基本関数にカプセル化します。

(1)RTUマスタとして、RTUスレーブオブジェクトの読み書きコマンドを生成します。

/* スレーブデータオブジェクトの読み書きコマンドを生成し、コマンド長は2つのチェックバイトを含む */

uint16_t SyntheticReadWriteSlaveCommand(ObjAccessInfo slaveInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

引数はそれぞれスレーブプロフィール,配信されるデータリスト,最終的に生成される命令配列である.戻り値は、コマンドの長さです。

(2)スレーブとして、マスターステーションの読み取りアクセス応答を生成する:

/* スレーブからの応答を生成する */

uint16_t SyntheticSlaveAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

パラメータはそれぞれ受信したメッセージ,返されたデータリスト,生成されたレスポンスメッセージのリストである.戻り値は応答情報のリストの長さです。

5、TCPアプリケーションデータユニットのプログラミング

TCPアプリケーションデータユニットの場合、RTUタイプでは、最初のメッセージフォーマットは“MBAPヘッダ+PDU”であり、PDUユニットは前に定義されているので、MBAPヘッダを追加するだけで済み、実際にはMBAPヘッダの実装フォーマットは固定されています。

TCPアプリケーションのデータユニットの実現も同様に,クライアントとした場合のサーバへの下りコマンドと,サーバとした場合のクライアントコマンドへの応答の2つのケースに分けられる.そこで、これらのケースを2つの基本関数にカプセル化します。

(1)TCPクライアントである場合,TCPサーバオブジェクトを読み書きするコマンドを生成する:

/* 読み書きサーバオブジェクトを生成するコマンド */

uint16_t SyntheticReadWriteTCPServerCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

(2)、NetWorkerサーバである場合、クライアントの読み書きアクセスの応答を生成します。

/* サーバーアクセスに対する応答を合成し、戻り値をコマンド长 */

uint16_t SyntheticServerAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

6最後に、結論

実際、ここまでModbusの基本プロトコルは基本的に実装されており、これらの基本的な操作を使ってもModbus通信を実現できます。実際、多くの人が書いているModbus通信プロトコルはこれよりも簡単で、Modbus通信機能の一部を実現できます。もちろん、これは私たちの目標ではありません。さもなければ、特別な開発ライブラリは必要ありません。さらにカプセル化して、より汎用的で使いやすくする必要があります。

これまでの作業が完了した後、ターゲットアプリケーションを実装することができます。まず、Modbus TCPのサーバーサイドアプリケーションを実装します。もちろん、特定のアプリケーションを行うのではなく、Modbus TCPのサーバーサイドアプリケーションをカプセル化し、必要に応じて呼び出すことができます。

ここではTCPプロトコルについては触れていません。Modbusとは必ずしも関係がありません。Modbusプロトコルをアプリケーション層で実行しています。

Modbus TCPサーバでは、いくつかの機能を実装する必要があります。まず、受信したクライアントコマンドを解析することで、前述の8つの一般的な機能のサポートのみを実装します。次に、解析が完了したら、各関数コードに対応する操作を実装します。具体的な構成は以下の通り。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图22

1命令の解析。

サーバはクライアントからのリクエストをパッシブ側として受信すると,リクエストをインストールして処理する.したがって、サーバは情報を受信した後、まずそれを解析します。ここでは、特定のアプリケーションでは、受信した情報を解析するためにこの関数を呼び出すだけで完了できます。

/* 受信した情報を解析し、応答コマンドの長さを返す */

uint16_t ParsingClientAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes)

実際には、この機能だけが外部から見られます。この関数は、TCPサーバ用のアプリケーションを開発するときに呼び出されます。

2·命令処理

コマンドが解析された後、異なる処理のための異なる機能コードに応じて、我々は8種類の機能コードをサポートしており、各機能は処理部分に対応しており、多くの場合、誰もが解析結果に応じて解析になります。操作を簡素化するために、各関数コードの処理部分を関数としてカプセル化しています。次に、関数ポインタの配列を使用してこれらの関数を動的に呼び出すことで、関数を単純化します。

まず、対応する関数コード操作を処理する8つの関数が必要です。

/* リードコイル状態コマンドを処理する */

static uint16_t HandleReadCoilStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 読み取り入力ステータスコマンドの処理 */

static uint16_t HandleReadInputStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* リード·ホールド·レジスタ·コマンドを処理 */

static uint16_t HandleReadHoldingRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 読み取り入力レジスタコマンドを処理する */

static uint16_t HandleReadInputRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 単一コイルの書き込みコマンドを処理 */

static uint16_t HandleWriteSingleCoilCommand(uint16_t coilAddress,uint16_t coilValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 単一レジスタの書き込みコマンドを処理する */

static uint16_t HandleWriteSingleRegisterCommand(uint16_t registerAddress,uint16_t registerValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 複数のコイル状態の書き込みを処理 */

static uint16_t HandleWriteMultipleCoilCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 複数のレジスタステータスの書き込みを処理 */

static uint16_t HandleWriteMultipleRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

次に、この関数を呼び出す関数ポインタの配列を定義します:

uint16_t (*HandleClientCommand[])(uint16_t,uint16_t,uint8_t *,uint8_t *)={HandleReadCoilStatusCommand,

                                                                          HandleReadInputStatusCommand,

                                                                          HandleReadHoldingRegisterCommand,

                                                                          HandleReadInputRegisterCommand,

                                                                          HandleWriteSingleCoilCommand,

                                                                          HandleWriteSingleRegisterCommand,

                                                                          HandleWriteMultipleCoilCommand,

                                                                          HandleWriteMultipleRegisterCommand};

3·応答の生成

応答コマンドの生成については、実際には第2部で述べましたが、記述する必要があるのは、書き込みデータの具体的な値と読み取りデータの取得のための様々な具体的な値です。TCPとRTUのこの部分は同じであるため、別のファイルで実装します。

今回はModbus TCPクライアントアプリケーションをカプセル化します。また、特定のアプリケーションを行うのではなく、TCPクライアントの基本機能を実装します。TCPクライアントの機能を関数としてカプセル化し、特定のアプリケーションを開発する際に呼び出すことができます。

TCPクライアントでは、主に2つの機能を実装しています。1つは、TCPサーバにアクセスするコマンドを生成することで、合計8つの関数コードをサポートします。2つ目はTCPサーバ側から返されたメッセージを解析し,その結果に応じて様々な操作を行うことであり,同様に8の機能をサポートする操作である.具体的なソフトウェアアクセス構造は以下のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图23

1アクセスコマンドの生成

アクティブな対話者として、クライアントはサーバに様々な操作要求コマンドを送信する必要があります。TCPクライアントでは、まずサーバにアクセスするコマンドを生成します。ビルドコマンドは標準プロトコル形式でビルドするだけで済み、現時点では前述の8つの関数コードしかサポートしていません。

/* サーバーにアクセスするコマンドを生成する */

uint16_t CreateAccessServerCommand(ObjAccessInfo objInfo,void *dataList,uint8_t *commandBytes)

これにより、特定のクライアントアプリケーションを開発する際には、この関数を呼び出してサーバーにアクセスするコマンドを生成するだけで済みます。

2応答情報の解析

前節で述べたように,サーバはコマンドを受信すると,コマンドに応じて処理を行い,応答メッセージを生成してクライアントに返す.クライアントはコマンドを受信した後、最初に応答情報を解析する必要がありますが、解析プロセスはサーバー側と一貫しています。違いは、解析結果から応答情報を生成する必要がなくなることです。

/* 受信したサーバ情報を解析する */

void ParsingServerRespondMessage(uint8_t *recievedMessage)

クライアントアプリケーションを開発する際には、この関数を呼び出してレスポンス情報を解析します。

3·対応処理

解析された情報については、変数の値を変更するなど、状況に応じて操作を実装する必要があります。主にサポートされているオペコードは8つである必要があります。理論的には、対応する各機能コードは異なる操作を持ちますが、実際には、書き込み操作コマンドは操作を行う必要がないため、対応する操作は実際には読み取り操作の4種類の機能コードのみです。

/* スレーブ状態量を読み取り、コイル状態ビット0 x 0 1関数コードを読み取る */

static void HandleReadCoilStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブ状態量の読み取りを処理し、入力状態ビット0 x 0 2関数コードを読み取り */

static void HandleReadInputStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブレジスタ値の読み取りを処理し、レジスタ0 x 0 3関数コードを読み取ります)*/

static void HandleReadHoldingRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブレジスタ値の読み取りを処理し、入力レジスタ0 x 0 4関数コードを読み取る */

static void HandleReadInputRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

同様に、これらの関数呼び出しを実装する関数ポインタの配列も定義します:

void (*HandleServerRespond[])(uint8_t *,uint16_t,uint16_t)={HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};

このようにして、TCPクライアントのカプセル化が完了しますが、もちろん、特定のデータ処理部分は特定のアプリケーションの開発で決定する必要があります。

Modbusはシリアルリンク上のSlaveとMasterに分かれており、このセクションではSlaveの開発を行います。Modbus RTUスレーブでは、Modbus TCPサーバ側と同じ機能を実装する必要があります。その操作過程も同じです。まず、マスターステーションからアクセスコマンドを受信し、コマンドメッセージを解析します。ここでは、前述の8つの機能コードを実装しています。次に解析の結果に応じて対応する操作を行い,具体的なソフトウェアアクセス構造は以下のとおりである:

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图24

上の図から、実際には3つのステップがあることがわかります。

ステップ1:命令を受けて解析する。これは前のセクションで説明したものと同じです。

ステップ2解析結果に基づいて作業を行う.変数の値を変更または取得するコマンドを含む。

ステップ3:応答を生成し、ホストに返します。

1ホストコマンドの解析

スレーブはホストからのコマンドメッセージを受信すると,それを解析し,上図に示す8種類の関数コードを一時的にサポートする.

/* 受信したメッセージを解析し、コールバック関数を介して合成された返信メッセージとメッセージのバイト長を返します */

uint16_t ParsingMasterAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes,uint16_t rxLength)

この関数は、受信したメッセージを受信し、レスポンスメッセージの長さを返します。アプリケーションの開発では、受信したメッセージをこの関数に送信し、生成された応答情報をクライアントに返すだけです。

2結果の操作。

解析後、読み取りコマンドと書き込みコマンドの両方に対応するアクションが必要です。関数コードごとに異なる操作をカプセル化します。

/* リードコイル状態コマンドを処理する */

static uint16_t HandleReadCoilStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 読み取り入力ステータスコマンドの処理 */

static uint16_t HandleReadInputStatusCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* リード·ホールド·レジスタ·コマンドを処理 */

static uint16_t HandleReadHoldingRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 読み取り入力レジスタコマンドを処理する */

static uint16_t HandleReadInputRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 単一コイルの書き込みコマンドを処理 */

static uint16_t HandleWriteSingleCoilCommand(uint16_t coilAddress,uint16_t coilValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 単一レジスタの書き込みコマンドを処理する */

static uint16_t HandleWriteSingleRegisterCommand(uint16_t registerAddress,uint16_t registerValue,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 複数のコイル状態の書き込みを処理 */

static uint16_t HandleWriteMultipleCoilCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

/* 複数のレジスタステータスの書き込みを処理 */

static uint16_t HandleWriteMultipleRegisterCommand(uint16_t startAddress,uint16_t quantity,uint8_t *receivedMessage,uint8_t *respondBytes)

同様に、これら8つの関数呼び出しを実装する関数ポインタの配列も定義します:

uint16_t (*HandleMasterCommand[])(uint16_t,uint16_t,uint8_t *,uint8_t *)={HandleReadCoilStatusCommand,

                                                                          HandleReadInputStatusCommand,

                                                                          HandleReadHoldingRegisterCommand,

                                                                          HandleReadInputRegisterCommand,

                                                                          HandleWriteSingleCoilCommand,

                                                                          HandleWriteSingleRegisterCommand,

                                                                          HandleWriteMultipleCoilCommand,

                                                                          HandleWriteMultipleRegisterCommand};

3機械応答の生成

処理の後、スレーブの対応する情報も生成する必要があります。読み取り操作コマンドでも書き込み操作コマンドでも、対応する機能コード処理時に応答メッセージを生成します。したがって、アプリケーションを開発するときは、解析関数データを呼び出すだけですべての機能を実現できます。

このセクションでは、最後のアプリケーション(Modbus RTUマスターアプリケーション)をカプセル化します。RTUマスターの開発は、TCPクライアントの開発と一貫しています。同様に、特定のアプリケーションを行うのではなく、RTUマスターの基本機能を実装します。RTUマスターの機能を関数としてカプセル化し、特定のアプリケーションを開発する際に呼び出すことができます。

RTUマスターでは、主に2つの機能を実装します。1つは、RTUスレーブステーションにアクセスするコマンドを生成し、合計8つの機能コードをサポートします。2つ目は、RTUがステーションから返す情報を解析し、結果に応じて様々な操作を行うことであり、8の機能をサポートする操作でもあります。具体的なソフトウェアアクセス構造は以下のとおりです。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图25

1アクセスコマンドの生成

アクティブな対話者として、クライアントはサーバに様々な操作要求コマンドを送信する必要があります。したがって、RTUマスターの場合、まずサーバにアクセスするコマンドを生成します。ビルドコマンドは標準プロトコル形式でビルドするだけで済み、現時点では前述の8つの関数コードしかサポートしていません。

/* スレーブにアクセスするコマンドを生成する */

uint16_t CreateAccessSlaveCommand(ObjAccessInfo objInfo,void *dataList,uint8_t *commandBytes)

これにより、特定のクライアントアプリケーションを開発する際には、この関数を呼び出してサーバーにアクセスするコマンドを生成するだけで済みます。

2応答情報の解析

前節で述べたように,サーバはコマンドを受信すると,コマンドに応じて処理を行い,応答メッセージを生成してクライアントに返す.クライアントはコマンドを受信した後、最初に応答情報を解析する必要がありますが、解析プロセスはサーバー側と一貫しています。違いは、解析結果から応答情報を生成する必要がなくなることです。

/* 受信した情報を解析する */

void ParsingSlaveRespondMessage(uint8_t *recievedMessage)

クライアントアプリケーションを開発する際には、この関数を呼び出してレスポンス情報を解析します。

3·対応処理

解析された情報については、変数の値を変更するなど、状況に応じて操作を実装する必要があります。主にサポートされているオペコードは8つである必要があります。理論的には、対応する各機能コードは異なる操作を持ちますが、実際には、書き込み操作コマンドは操作を行う必要がないため、対応する操作は実際には読み取り操作の4種類の機能コードのみです。

/* スレーブ状態量を読み取り、コイル状態ビット0 x 0 1関数コードを読み取る */

static void HandleReadCoilStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブ状態量の読み取りを処理し、入力状態ビット0 x 0 2関数コードを読み取り */

static void HandleReadInputStatusRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブレジスタ値の読み取りを処理し、レジスタ0 x 0 3関数コードを読み取ります)*/

static void HandleReadHoldingRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

/* スレーブレジスタ値の読み取りを処理し、入力レジスタ0 x 0 4関数コードを読み取る */

static void HandleReadInputRegisterRespond(uint8_t *receivedMessage,uint16_t startAddress,uint16_t quantity)

同様に、これらの関数呼び出しを実装する関数ポインタの配列も定義します:

void (*HandleServerRespond[])(uint8_t *,uint16_t,uint16_t)={HandleReadCoilStatusRespond, HandleReadInputStatusRespond, HandleReadHoldingRegisterRespond, HandleReadInputRegisterRespond};

ここでRTUマスターステーションのパッケージングが完了しますが、もちろん、特定のデータ処理部分は特定のアプリケーションの開発で決定する必要があります。

様々なアプリケーションが開発されましたが、問題に言及していない、特定のデータを読み書きすることです。Modbusにとって標準的なデータは、コイルデータ(アドレス:0000x)、入力状態量データ(アドレス:1000x)、保持レジスタデータ(アドレス:4000x)、入力レジスタデータ(アドレス:3000x)の4種類である。私たちの通信の目的は、このデータを操作することですが、上記のパッケージにはデータ処理については言及されていません。実際、特定のアプリケーションデータは非常に多様であり、カプセル化することはできないため、これを考慮する方法はありません。どうすればこの問題を解決できるでしょう?次に、この種の問題を解決します。

1データ処理関数のカプセル化

RTUマスター、RTUスレーブ、TCPクライアント、TCPサーバのいずれにおいても、データの処理は本質的に同じであり、特定のアプリケーションのデータ構造が決定された後処理方法が決定される限り、本質的に同じであると考えています。そこで、弱い型の関数を定義するアプローチを取ります。以下は以下の通り。

/* 読み取りたいコインの量を取得する */

__weak void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 読み込みたいInputStatusの値を取得する */

__weak void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 読み込みたい保持レジスタの値を取得する */

__weak void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 読み込みたい入力レジスタの値を取得する */

__weak void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 個々のコイルの値を設定する */

__weak void SetSingleCoil(uint16_t coilAddress,bool coilValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 個々のレジスタの値を設定する */

__weak void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 複数のコイルの値を設定する */

__weak void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 複数のレジスタの値を設定する */

__weak void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

 //Modbus TCP Server/RTU Slaveアプリケーションに実装する必要がある場合

}

/* 読み戻されたコイルの状態を更新 */

__weak void UpdateCoilStatus(uint16_t startAddress,uint16_t quantity,bool *stateValue)

{

 //クライアント(マスター)アプリケーションで実装

}

/* 読み戻された入力ステータス値を更新 */

__weak void UpdateInputStatus(uint16_t startAddress,uint16_t quantity,bool *stateValue)

{

 //クライアント(マスター)アプリケーションで実装

}

/* 読み戻されたコイルの状態を更新 */

__weak void UpdateHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

 //クライアント(マスター)アプリケーションで実装

}

/* 読み戻されたコイルの状態を更新 */

__weak void UpdateInputResgister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)

{

 //クライアント(マスター)アプリケーションで実装

}

特定のアプリケーションの開発では、アプリケーション内で対応する機能を実装するだけで機能を完全にすることができます。特定のデータの処理方法については、特定のアプリケーション内のデータフォーマットに依存します。もちろん、これらの機能はすべて実装する必要はなく、独自のニーズに応じて実装する必要があります。

2大小の問題点について

データ通信に関して言えば、避けることができない問題があります。それはサイズとサイズの問題です。Modbusプロトコルでは、ビッグエンディアンモードが採用されており、ローアドレスはハイバイトのデータを格納し、ハイアドレスはローバイトのデータを格納します。

マルチバイトのデータ通信を行う場合、浮動小数点数が異なるシステムで格納される順序が異なり、読み書きデータが誤って解析されるなど、大小の問題が明らかになります。データを処理する際にこれを考慮する必要があります。

Modbus通信といえば巡回冗長検査(CRC)は避けられず、特に標準的なシリアルRTUリンクでは必須である。他の開発ではCRCもよく使われます。 アルゴリズムのアルゴリズム各種データの検証を行う。そこで、巡回冗長検査(CRC)アルゴリズムを検討する必要がある。

1、CRC概要概要概要

     巡回冗長検査CRCはデータ伝送HARQ機能であり、データに対して多項式計算を行い、得られた結果をフレームの後ろに添付し、受信装置も同様のアルゴリズムを実行し、データ伝送の正確性と完全性を保証する。

CRCチェックの基本的な考え方は、線形符号化理論を利用して、送信側で送信するkビットバイナリコードシーケンスに応じて、特定のルールでチェック用の監視コード(CRCコード)rビットを生成し、情報の背後に添付して、新しいバイナリコードシーケンス(k+r)ビットを構成し、最後に送信することです。受信側では、情報コードとCRCコードの間に従う規則に基づいてテストされ、送信中にエラーがあるかどうかを判断します。

CRCの本質はモジュロ2除算の剰余であり、使用する除数が異なり、CRCの型も異なります。通常、CRCの除数は生成多項式で表される。最も一般的なCRCコード生成多項式は以下のとおりである。

mnModbusプロトコルスタックに基づくModbus開発チュートリアル(フルバージョン)插图26

     生成多項式が異なるので、結果は自然に異なります。実際、Modbus通信ではCRC-16方式が採用されている。

2アルゴリズム分析です

     CRCチェックコードの符号化方法は、送信されるバイナリデータt(x)を生成多項式g(x)で割り、最後の残りをCRCチェックコードとすることです。その実現ステップは以下の通り。

     送信されるデータブロックをmビットのバイナリ多項式t(x)とし,生成多項式をrオーダーのg(x)とする.データブロックの最後にr個の0を追加すると、データブロックの長さはm+rビットに増加し、対応する2進多項式はとなります。生成多項式g xで除算し、剰余が次数r-1の二進多項式y xを求める。このバイナリ多項式y(x)は、生成多項式g(x)によって符号化されたt(x)のCRCチェックコードです。y(x)をモジュロ2で引くと、二進多項式が得られる。CRCチェックコードを含む送信文字列です。

     CRCの符号化規則からわかるように、CRC符号化は実際に送信されたmビットバイナリ多項式t(x)をg(x)で割ることができるm+rビットバイナリ多項式に変換するので、復号時に受信データでg(x)を除去することができ、残りがゼロの場合、伝送プロセスにエラーがないことを示します。残りがゼロでない場合、伝送プロセスにエラーがあります。多くのCRCハードウェア復号回路は、このようにHARQを行う。同時に、t(x)とCRCチェックコードの組み合わせと見なすことができるので、デコード時に受信したバイナリデータを最後のrビットデータから削除し、元のデータを得ます。

      実際、真のCRC計算は通常、上記のものとは多少異なります。これは、この最も基本的なCRC除算には明らかな欠点があります。データストリームの先頭に0を追加しても、最終的な検証結果には影響しません。この欠点を補うために、2つの概念が導入されました。1つは“剰余の初期値”で、もう1つは“結果の排他的OR値”です。いわゆる剰余初期値は、CRC値を計算する前に格納変数に割り当てられる初期値です。対応する“結果の差異OR値”は、計算が完了した後、変数の値とこの値を最後の差異OR演算にして検証結果を得ることです。

名前はチェックサムビット幅多項式の生成除数(多項式)残りの初期値結果の違いや価値
CRC-44x4+x+13  
CRC-88x8+x5+x4+10x31  
CRC-88x8+x2+x1+10x07  
CRC-88x8+x6+x4+x3+x2+x10x5E  
CRC-1212x12+x11+x3+x+180F  
CRC-16 16x16+x15+x2+10x80050x00000x0000
CRC-CCITT16x16+x12+x5+10x10210xFFFF0x0000
CRC-3232x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+10x04C11DB70xFFFFFFFF0xFFFFFFFF
CRC-32c32x32+x28+x27+...+x8+x6+11EDC6F41  

     ここで、このアルゴリズムの実装を説明することができます。

ステップ1:CRCストア変数を定義し、“残りの初期値”を割り当てます。

ステップ2:データの最初の8ビット文字をCRC格納変数と排他的和し、結果をCRC格納変数に格納します。

ステップ3:CRC格納変数を右に1ビットシフトし、MSBはゼロで埋め、オフにしてLSBをチェックします。

ステップ4:LSBが0の場合、ステップ3を繰り返します。LSBが1の場合、CRCレジスタは0 x 31と異なります。

ステップ5:8つのシフトが完了するまでステップ3と4を繰り返します。これで8ビットのデータが処理されます。

ステップ6:すべてのデータが処理されるまで、ステップ2 ~ 5を繰り返します。

ステップ7:最終的なCRC格納変数の内容は、“結果の排他的和”または非操作の後にCRC値となります。

3コードの実装。

     前の準備では、実際にはCRCチェックコードを実装する必要がある非常に簡単で、このプロセスを実装するには、2つの一般的な方法があります。1つは、直接計算方法、前のステップに従って計算されます。2つ目は、駆動テーブル法、いくつかのデータを直接計算に格納することです。ModbusではCRC-16を使用しているので、一例として実装してみます。

(1)直接計算

直接計算法は簡単で簡単で、簡単なプログラムも比較的簡単であり、CRC − 16を例にとると、多項式は0 x 8005と表記されているが、その欠点や値は0 x 0000であるため、追加しないことができる。具体的なコードは:

#define Initial_Value    0x0000

#define EOR 0x0000

#define POLY16 0x8005

uint16_t CRC16(uint8_t *buf,uint16_t length)

{

  uint16_t crc16,data,val;

  crc16 = Initial_Value;

  for(int i=0;i<length;i++)

  {

    if((i % 8) == 0)

    {

      data = (*buf++)<<8;

     }

    val = crc16 ^ data;

    crc16 = crc16<<1;

    data = data <<1;

    if(val&0x8000)

    {

      crc16 = crc16 ^ POLY16;

    }

  }

  return crc16;

}

(2)駆動表法

    直接計算の場合、単純で直接的ですが、効率が問題になることがあるため、Modbus通信では通常、駆動テーブル法を使用して実現します。

//CRC_16高さ8ビットのデータ領域

const uint8_t auchCRCHi[] = {

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, 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, 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, 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

};

//CRC下位バイト値テーブル

const uint 8_t auchCRCLo[] = {//CRC_16下位8ビットデータ領域

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, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,

0x1E, 0xDE, 0xDF, 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, 0xF3,

0xF2, 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, 0xA8, 0x68,

0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,

0x7F, 0xBF, 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, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,

0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,

0x43, 0x83, 0x41, 0x81, 0x80, 0x40

};

/* 関数機能:CRCチェックコード生成

 入力パラメータ:puchMsggはCRCチェックの対象となるメッセージ、usDataLenはメッセージ内のバイト数

 関数出力:計算されたCRCチェックコード

 GenerateCRC16 Check Codeルックアップテーブル計算関数 */

static uint16_t GenerateCRC16CheckCode(uint8_t *puckMsg,uint8_t usDataLen)

{

 uint 8_t uchCRCHi = 0 x FF; //高CRCバイト初期化

 uint 8_t uchCRCLo = 0 x FF; //ロー CRCバイト初期化

 uint 32_t uInde x; //CRCループ内のインデックス

 //メッセージバッファの転送

  while (usDataLen--)

  {

   //CRCの計算

    uIndex = uchCRCLo ^ *puckMsg++ ; 

    uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex] ;

    uchCRCHi = auchCRCLo[uIndex] ;

  }

 //結果を返し、高い値を前に

  return (uchCRCLo << 8 |uchCRCHi) ;

}

4最後に、結論

     CRCのアプリケーションは非常に広く、特にコミュニケーションを行うときに頻繁に見られるので、少なくともそれを使用することは非常に必要です。我々はModbusライブラリ関数を開発する過程で、それについて少し浅い理解を持っているに過ぎないので、ここでは共進のために記述する。

Modbusプロトコルスタックの開発はすでに完了していますが、それが目的ではありません。開発の目的は、もちろん、実際の問題を解決するために使用することです。次に、開発したばかりのModbusプロトコルスタックを使用してModbus TCPサーバアプリケーションを開発します。

Modbus TCP Serverの開発には,まずTCP Serverのサポートやネットワークの構成などが必要であるが,これらはModbus自体とは関係がないので,ここでは触れない.ネットワークとTCPサーバが設定されています。次に、Modbus TCP Serverの実装について説明します。

前述のプロトコルスタックのカプセル化に基づいて、Modbus TCP Serverの関連するカプセル化を参照する必要があります。コマンドを受信した後に解析関数を呼び出して解析し、関数のプロトタイプを解析します。

uint16_t ParsingClientAccessCommand(uint8_t *receivedMessage,uint8_t *respondBytes);

この関数は関数ポインタとしてTCPサーバに渡され、受信した情報のリストを解析するコールバックがあります。

さらに、Modbusオブジェクトの実際の操作を行うために、特定のニーズに応じて8つのコールバック関数を実装します。これらの8つの関数のプロトタイプは以下のとおりです。

/* 読み取りたいコインの量を取得する */

void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList);

 /* 読み込みたいInputStatusの値を取得する */

void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /* 読み込みたい保持レジスタの値を取得する */

void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /* 読み込みたい入力レジスタの値を取得する */

void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /* 個々のコイルの値を設定する */

void SetSingleCoil(uint16_t coilAddress,bool coilValue);

 /* 個々のレジスタの値を設定する */

void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue);

 /* 複数のコイルの値を設定する */

void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /* 複数のレジスタの値を設定する */

void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

これらの8つの機能は、アプリケーションの特定のニーズに応じてデータオブジェクトの操作を実現し、いくつかの実装方法は、それぞれのアプリケーションのニーズとデータ構造に応じて決定されます。

前のセクションでは、プロトコルを使用してModbus TCP Serverアプリケーションを開発しました。次に、プロトコルスタックを使用して、シリアルリンクベースのMosbus RTU Slaveアプリケーションを開発します。

前述のプロトコルスタックのカプセル化に基づいて、Modbus TCP Serverの関連するカプセル化を参照する必要があります。コマンドを受信した後に解析関数を呼び出して解析し、関数のプロトタイプを解析します。

ParsingMasterAccessCommand(uint8_t *receivedMesasage,uint8_t *respondBytes,uint16_t rxLength);

RTU Slaveはシリアルポートを使用してメッセージの受信を中断し、メッセージを受信した後、上記関数を使用してメッセージを解析し、受信したメッセージ命令に従って操作を完了する。

もちろん、構文解析関数の呼び出しに加えて、Modbusオブジェクトの実際の操作を完了するために、特定のニーズに応じて8つのコールバック関数を実装します。

/* 読み取りたいコインの量を取得する */

void GetCoilStatus(uint16_t startAddress,uint16_t quantity,bool *statusList);

 /* 読み込みたいInputStatusの値を取得する */

void GetInputStatus(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /* 読み込みたい保持レジスタの値を取得する */

void GetHoldingRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /* 読み込みたい入力レジスタの値を取得する */

void GetInputRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

 /* 個々のコイルの値を設定する */

void SetSingleCoil(uint16_t coilAddress,bool coilValue);

 /* 個々のレジスタの値を設定する */

void SetSingleRegister(uint16_t registerAddress,uint16_t registerValue);

 /* 複数のコイルの値を設定する */

void SetMultipleCoil(uint16_t startAddress,uint16_t quantity,bool *statusValue);

 /* 複数のレジスタの値を設定する */

void SetMultipleRegister(uint16_t startAddress,uint16_t quantity,uint16_t *registerValue);

これらの8つの機能は、アプリケーションの特定のニーズに応じてデータオブジェクトの操作を実現し、いくつかの実装方法は、それぞれのアプリケーションのニーズとデータ構造に応じて決定されます。

もちろん、情報を受信するために割り込みを使用する必要はなく、クエリなどの方法を使用することもできますが、割り込みの使用は、マスターコマンドの送信が一般的に不規則であるため、より良い方法です。割り込み方法は、情報がタイムリーに応答することを保証することができ、あまりにも多くのスレーブリソースを消費することなく、他のタスクを処理するためのより多くのリソースがあります。

この資料を実際のプロジェクトに活用しませんか?

ツールセンターで電文解析、CRCチェック、デバイスデバッグを行うか、要件を送信して選定と接続のアドバイスを受けましょう。

エンジニア会員

この記事を実行可能なデバッグ資料に

登録すると、高度な電文解析、資料パックのダウンロード、コード例、エンジニアリングケース、優先技術サポートが利用可能に。実際のプロジェクト納品に最適です。

高度なツール無制限
資料パックとコードパック
完全なエンジニアリングケースライブラリ
優先技術サポート窓口

发表回复

あなたのメールアドレスアドレス不会被公开。 必須項目已用 * 标注