MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C Sharpの基礎 - MODBUSのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C Sharpの基礎 - MODBUS
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == MODBUSは1979年にModicon (現Schneider Electric) により開発された産業用通信プロトコルである。<br> 単純で堅牢な設計により、製造業、ビル管理、エネルギー管理等の様々な産業分野で使用されている。<br> <br> 通信方式として、マスター・スレーブ方式を採用しており、1台のマスター機器が複数のスレーブ機器と通信を行う。<br> マスターからの要求に対して、スレーブが応答するという単純な構造になっている。<br> <br> 特に、MODBUSは産業オートメーションの基盤となる通信規格 (レガシーシステムとの互換性維持やシンプルな制御システムの構築) として、今日でも広く使用されている。<br> <br> 通信媒体としては、RS-485やRS-232等のシリアル通信 (MODBUS RTU / ASCII)、TCP/IPネットワーク (MODBUS TCP) が使用される。<br> 特に、MODBUS TCPは、産業用イーサネットの標準プロトコルとして普及している。<br> <br> データモデルの構造<br> * コイル *: 1ビットの読み書き可能なデジタル出力 * ディスクリート入力 *: 1ビットの読み取り専用デジタル入力 * 保持レジスタ *: 16ビットの読み書き可能なデータ * 入力レジスタ *: 16ビットの読み取り専用データ <br> MODBUSの特徴<br> * オープンな仕様で、ロイヤリティフリーである。 * 実装が簡単であり、開発コストを抑えられる。 * 異なるメーカーの機器間での相互運用性が高い。 <br> 通信手順は、ファンクションコードと呼ばれる命令コードを使用してデータの読み書きを行う。<br> 例えば、スレーブ機器のレジスタを読み取る場合、マスターは対象のスレーブアドレス、ファンクションコード、データアドレス、データ数等を含むメッセージを送信する。<br> <br> セキュリティおいては、認証機能は備えていないため、閉じたネットワーク内での使用が推奨される。<br> 近年では、暗号化やセキュリティ機能を追加した拡張仕様も提案されている。<br> <br><br> == Modbus RS-485 と Modbus RS-232C の使い分け == <center> {| class="wikitable" |+ Modbus RS-485 と Modbus RS-232C の比較 |- ! !! RS-485 !! RS-232C |- | 接続形態 || マルチドロップ (複数デバイス) || ポイント・ツー・ポイント (1対1) |- | 伝送距離 || 最大1200m程度 || 最大15m程度 |- | ノイズ耐性 || 高い (差動信号) || 低い |- | 用途 || 工場の自動化、ビル管理 || 近距離の機器接続 |} </center> <br><br> == Modbus RTU / Modbus ASCII == * Modbus RTU *: RS-485またはRS-232Cで使用 *: バイナリ形式でデータを送信 (効率的) *: RS-485では最大32台 (または拡張で247台) のデバイスをマルチドロップ接続可能 *: <br> * Modbus ASCII *: RS-485またはRS-232Cで使用 *: ASCII文字形式でデータを送信 *: デバッグが容易だが、RTUより速度が遅い <br><br> == MODBUSのデータ型 == Modbusには、4種類の主要なデータ型がある。<br> <br> ==== コイル ==== 1ビットのデジタル出力 (読み書き可能) が可能である。<br> <br> * アドレス範囲 *: 00001 - 09999 <br> * ファンクションコード ** 01 **: Read Coils ** 05 **: Write Single Coil ** 15 **: Write Multiple Coils <br> 用途例を以下に示す。<br> * ON / OFF制御 *: バルブの開閉状態, モータの起動 / 停止, ランプの点灯 / 消灯 <br> ==== ディスクリート入力 ==== 1ビットのデジタル入力 (読み取り専用) が可能である。<br> <br> * アドレス範囲 *: 10001 - 19999 <br> * ファンクションコード ** 02 **: Read Discrete Inputs <br> 用途例を以下に示す。<br> * リミットスイッチの状態 * センサのON / OFF信号 * アラーム信号 <br> ==== 入力レジスタ ==== 16ビットのデータレジスタ (読み取り専用) である。<br> <br> * アドレス範囲 *: 30001 - 39999 <br> * ファンクションコード ** 04 **: Read Input Registers <br> 用途例を以下に示す。<br> * アナログ入力値 * 測定値 (温度、圧力等) * カウンタ値 <br> ==== 保持レジスタ ==== 16ビットのデータレジスタ (読み書き可能) である。<br> <br> * アドレス範囲 *: 40001 - 49999 <br> * ファンクションコード ** 03 **: Read Holding Registers ** 06 **: Write Single Register ** 16 **: Write Multiple Registers <br> 用途例を以下に示す。<br> * 設定値 *: 温度、速度、時間等 * 制御パラメータ * 出力値の調整 * アナログ出力の制御 <br> ==== 実装上の注意点 ==== * アドレッシング *: プログラム内でのアドレス指定は通常0ベースである。 *: 例 : コイル00001は、実際にはアドレス0として指定する。 <br> * データ形式 *: 保持レジスタは、16ビット整数値を扱う。 *: 浮動小数点数を扱う場合は、2つのレジスタを組み合わせて使用する。 *: ビッグエンディアン / リトルエンディアンを考慮する必要がある。 <br> * アクセス制御 *: 読み取り専用、あるいは、読み書き可能かを判断する必要がある。 <br> * エラーハンドリング *: 不正なアドレスへのアクセス *: 範囲外の値の書き込み *: 通信エラー <br> ==== 使用例 ==== 以下の例では、コイルと保持レジスタを使用している。<br> <br> コイルはデジタル制御 (ON / OFF)、保持レジスタはアナログ値や設定値の制御に使用する。<br> <br> <syntaxhighlight lang="c#"> // コイルの制御 public async Task ControlValveExample(IModbusMaster master) { // バルブの開閉制御 await master.WriteSingleCoilAsync( slaveId: 1, coilAddress: 0, // コイル00001 value: true // バルブを開く ); // 複数のコイルの状態を一度に読み取る bool[] valveStates = await master.ReadCoilsAsync( slaveId: 1, startAddress: 0, // コイル00001から numberOfPoints: 4 // 4つのコイルの状態を読む ); } </syntaxhighlight> <br> <syntaxhighlight lang="c#"> // 保持レジスタの制御 public async Task ControlTemperatureExample(IModbusMaster master) { // 温度設定値の書き込み (例 : 25.5[℃]) ushort temperatureValue = 255; // 0.1[℃]単位で設定 await master.WriteSingleRegisterAsync( slaveId: 1, registerAddress: 0, // レジスタ40001 value: temperatureValue ); // 現在の設定値を読み取る ushort[] settings = await master.ReadHoldingRegistersAsync( slaveId: 1, startAddress: 0, // レジスタ40001から numberOfPoints: 1 // 1つのレジスタを読む ); decimal actualTemperature = settings[0] / 10.0m; // 実際の温度値に変換 } </syntaxhighlight> <br><br> == MODBUS ASCII == ==== 初期化とシリアルポート設定 ==== MODBUS ASCII通信に必要なシリアルポートの設定を行う。<br> 以下の例では、MODBUS ASCIIの標準的な設定であるデータ長7ビット、偶数パリティとして設定している。<br> <br> <syntaxhighlight lang="c#"> public ModbusAsciiClient(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) { _serialPort = new SerialPort { PortName = portName, BaudRate = baudRate, Parity = parity, DataBits = dataBits, StopBits = stopBits, ReadTimeout = 1000, WriteTimeout = 1000 }; } </syntaxhighlight> <br> ==== リクエストメッセージの作成 ==== MODBUS ASCIIメッセージを作成する。<br> 各バイトを16進数の文字列に変換して、開始文字 (:)、データ、LRC、終了文字 (CR LF) の順に構築する。<br> <br> <syntaxhighlight lang="c#"> private byte[] CreateAsciiMessage(byte slaveAddress, byte functionCode, ushort startAddress, ushort length) { // バイナリデータの作成 byte[] data = new byte[] { slaveAddress, functionCode, (byte)(startAddress >> 8), (byte)startAddress, (byte)(length >> 8), (byte)length }; // LRCの計算 byte lrc = CalculateLRC(data); // ASCII文字列の作成 StringBuilder asciiMessage = new StringBuilder(); asciiMessage.Append(':'); // 開始文字 foreach (byte b in data) { asciiMessage.Append(b.ToString("X2")); } asciiMessage.Append(lrc.ToString("X2")); asciiMessage.Append("\r\n"); // 終了文字 return Encoding.ASCII.GetBytes(asciiMessage.ToString()); } </syntaxhighlight> <br> ==== メッセージの送受信 ==== 実際の通信処理を行う。<br> <br> データ送信時はスレッドセーフで処理、データ受信時は開始文字から終了文字までを非同期で読み取る。<br> <br> <syntaxhighlight lang="c#"> private async Task<byte[]> SendReceiveAsync(byte[] request) { if (!_serialPort.IsOpen) { throw new InvalidOperationException("シリアルポートが開かれていません"); } lock (_lockObject) { // バッファをクリア _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); // リクエストを送信 _serialPort.Write(request, 0, request.Length); } // 応答を待機 return await Task.Run(() => { byte[] buffer = new byte[256]; int bytesRead = 0; // 開始文字 ":" を待機 while (_serialPort.ReadByte() != ':') { if (!_serialPort.IsOpen) throw new Exception("ポートが閉じられました"); } // 終了文字まで読み込み while (bytesRead < buffer.Length) { int b = _serialPort.ReadByte(); if (b == -1) throw new Exception("タイムアウトが発生"); buffer[bytesRead++] = (byte)b; if (bytesRead >= 2 && buffer[bytesRead - 2] == '\r' && buffer[bytesRead - 1] == '\n') { break; } } byte[] response = new byte[bytesRead]; Array.Copy(buffer, response, bytesRead); return response; }); } </syntaxhighlight> <br> ==== 応答の検証 ==== 受信したデータの妥当性を検証する。<br> * メッセージの最小長 * スレーブアドレスとファンクションコードの一致 * LRCによるデータ整合性 <br> <syntaxhighlight lang="c#"> private bool ValidateResponse(byte[] response, byte expectedSlaveAddress, byte expectedFunctionCode) { if (response == null || response.Length < 9) { return false; } // ASCII文字列からバイナリデータに変換 string asciiHex = Encoding.ASCII.GetString(response, 1, response.Length - 3); byte[] binary = new byte[asciiHex.Length / 2]; for (int i = 0; i < binary.Length; i++) { binary[i] = Convert.ToByte(asciiHex.Substring(i * 2, 2), 16); } // スレーブアドレスとファンクションコードの検証 if (binary[0] != expectedSlaveAddress || binary[1] != expectedFunctionCode) { return false; } // LRCの検証 byte calculatedLrc = CalculateLRC(binary.AsSpan(0, binary.Length - 1).ToArray()); byte receivedLrc = binary[binary.Length - 1]; return calculatedLrc == receivedLrc; } </syntaxhighlight> <br> ==== データの抽出 ==== 応答メッセージからデータ部分を抽出して、ASCII形式からバイナリデータに変換する。<br> <br> <syntaxhighlight lang="c#"> private byte[] ConvertAsciiResponseToData(byte[] response) { // ":" と CR LF を除いたASCII文字列を取得 string asciiHex = Encoding.ASCII.GetString(response, 1, response.Length - 3); // データ長を取得 int dataLength = Convert.ToByte(asciiHex.Substring(4, 2), 16); byte[] data = new byte[dataLength]; // データ部分を変換 for (int i = 0; i < dataLength; i++) { data[i] = Convert.ToByte(asciiHex.Substring(6 + i * 2, 2), 16); } return data; } </syntaxhighlight> <br> ==== リソース管理 ==== 最後に、シリアルポートのクリーンアップを行う。<br> <br> <syntaxhighlight lang="c#"> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed) { if (disposing) { if (_serialPort != null) { if (_serialPort.IsOpen) { _serialPort.Close(); } _serialPort.Dispose(); _serialPort = null; } } _isDisposed = true; } } </syntaxhighlight> <br> ==== 使用方法 ==== <syntaxhighlight lang="c#"> async Task ExampleUsage() { using (var client = new ModbusAsciiClient("COM1", 9600)) { if (await client.OpenAsync()) { try { // スレーブアドレス1、開始アドレス0から2レジスタ分読み込み byte[] data = await client.ReadHoldingRegistersAsync(1, 0, 2); // データの処理 // ...略 } catch (Exception ex) { Console.WriteLine($"エラーが発生しました: {ex.Message}"); } } } } </syntaxhighlight> <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:C_Sharp]]
C Sharpの基礎 - MODBUS
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse