Modbus Protocol Basics
Modbusは1979年に策定されたシリアル通信プロトコルです。
シンプルで信頼性が高いため、現在でも広く利用されています。
Master/Slave Architecture
[Master/Client] ---request---> [Slave/Server]
<--response---
- Master (Client): Initiates requests 通信要求を開始する側
- Slave (Server): Responds to requests 要求に応答する側
- One master can talk to 247 slaves 1台のマスターは最大247台のスレーブと通信可能
- Slaves have unique IDs (1-247) スレーブはそれぞれ固有のID(1~247)を持つ
Data Model
Modbus has 4 data types:
| Type | Access | Size | Address Range | Common Use |
|---|---|---|---|---|
| Coils | Read/Write | 1 bit | 00001–09999 | Outputs, relays |
| Discrete Inputs | Read only | 1 bit | 10001–19999 | Switches, sensors |
| Input Registers | Read only | 16 bit | 30001–39999 | Measurements |
| Holding Registers | Read/Write | 16 bit | 40001–49999 | Configuration |
Warning : PyModbus uses 0-based addressing!
Coil 00001 = address 0
Register 40001 = address 0
Register 40100 = address 99
これは pymodbusでのアドレス指定ルールを説明しています。
重要なポイントは、pymodbusは必ず「0始まり(0-based)」のアドレスを使うという点です。
Modbus機器のマニュアルでは、コイルやレジスタは 1始まり(1-based) や 40001形式 で表記されることが一般的です。しかし、pymodbusでは内部的に番号を0から数えます。
そのため対応関係は次のようになります。
Coil 00001 → pymodbusでは address = 0
Holding Register 40001 → pymodbusでは address = 0
Holding Register 40100 → pymodbusでは address = 99
つまり、
「マニュアルに書かれたアドレス − 1」 が、pymodbusで指定するアドレスになります。
このズレを理解していないと、「通信はできるのに値が読めない」というトラブルが発生しやすいため、pymodbusを使う上で最も重要な基本ルールの一つです。
Function Codes
Main function codes you'll use:
# Reading
0x01 - Read Coils
0x02 - Read Discrete Inputs
0x03 - Read Holding Registers
0x04 - Read Input Registers
# Writing
0x05 - Write Single Coil
0x06 - Write Single Register
0x0F - Write Multiple Coils
0x10 - Write Multiple Registers
これは Modbus通信で使われる「Function Code(機能コード)」の一覧を示しています。
Function Codeは、「何をしたい通信なのか」を相手装置に伝えるための命令番号です。Modbusでは、このコードによって読み取りか書き込みか、どのデータ領域を扱うかが決まります。
読み取り系のFunction Code
-
0x01 – Read Coils
リレーや出力など、ON/OFF情報(1bit)を読み取ります。 -
0x02 – Read Discrete Inputs
スイッチやセンサ状態など、読み取り専用の1bit入力を取得します。 -
0x03 – Read Holding Registers
設定値や制御値として使われる、読み書き可能な16bitレジスタを読み取ります。 -
0x04 – Read Input Registers
温度・圧力などの測定値が格納される、読み取り専用の16bitレジスタを取得します。
書き込み系のFunction Code
-
0x05 – Write Single Coil
1つのコイルをON/OFFします。 -
0x06 – Write Single Register
1つの保持レジスタに値を書き込みます。 -
0x0F – Write Multiple Coils
複数のコイルをまとめて書き込みます。 -
0x10 – Write Multiple Registers
複数の保持レジスタを一括で更新します。
pymodbusでは、これらのFunction Codeを直接指定する必要はなく、read_holding_registers や write_registers といったAPIが内部で自動的に対応するコードを使ってくれます。
Pymodbus Implementation
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.100')
# Function code 0x03 - Read Holding Registers
result = client.read_holding_registers(address=0, count=10, slave=1)
# Function code 0x01 - Read Coils
result = client.read_coils(address=0, count=8, slave=1)
# Function code 0x06 - Write Single Register
client.write_register(address=0, value=123, slave=1)
# Function code 0x05 - Write Single Coil
client.write_coil(address=0, value=True, slave=1)
これは、ModbusのFunction CodeがpymodbusのAPI呼び出しとしてどのように対応しているかを示した実装例です。pymodbusでは、ユーザーがFunction Code(0x01や0x03など)を直接指定する必要はなく、目的に応じたメソッドを呼ぶだけで内部的に適切なFunction Codeが使われます。
read_holding_registers() は Function Code 0x03 に対応し、読み書き可能な保持レジスタを取得します。address=0, count=10 は、0番地から10個のレジスタを読む指定で、slave=1 は通信相手のSlave IDです。
read_coils() は Function Code 0x01 に対応し、リレーや出力状態などのON/OFF情報を読み取ります。write_register() は Function Code 0x06 を使って、単一の保持レジスタへ値を書き込みます。設定値の変更や制御指示に使われます。
write_coil() は Function Code 0x05 に対応し、1つのコイルをON/OFF制御します。pymodbusを使うことで、低レベルなModbus命令を意識せずに、安全で可読性の高いコードを書ける点が大きな利点です。
Frame Formats
これは、Modbusの3つの通信方式(TCP / RTU / ASCII)におけるフレーム構造の違いを示しています。フレームとは、装置間でやり取りされる通信データの最小単位です。
Modbus TCP Frame
[Transaction ID][Protocol ID][Length][Unit ID][Function Code][Data]
2 bytes 2 bytes 2 bytes 1 byte 1 byte n bytes
Modbus TCPはEthernet上で動作し、MBAPヘッダと呼ばれる情報を先頭に持ちます。Transaction ID は要求と応答を対応付けるための識別子で、複数通信を並行処理する際に使われます。Protocol ID は通常0で、Modbusを示します。Length は後続データ長、Unit ID は通信対象機器を指定します。その後に Function Code と実データが続きます。CRCはTCPが担保するため不要です。
Modbus RTU Frame
[Slave Address][Function Code][Data][CRC]
1 byte 1 byte n bytes 2 bytes
RTUはシリアル通信向けで、構成が非常にシンプルです。Slave Address で通信相手を指定し、Function Code とデータを送信、最後に CRC により通信エラーを検出します。高速で効率的ですが、通信タイミング管理が厳密です。
Modbus ASCII Frame
[:][Slave Address][Function Code][Data][LRC][CR][LF]
1 2 chars 2 chars n chars 2 1 1
ASCIIはRTUを人が読みやすい文字形式にした方式です。すべてのデータを16進ASCII文字で表現し、LRC によるエラーチェックを行います。可読性は高い一方、通信効率は最も低くなります。
用途に応じて、TCPはEthernet、RTU/ASCIIはシリアルと使い分けられます。
Exception Codes
When something goes wrong:
result = client.read_holding_registers(9999, 10)
if result.isError():
print(f"Exception code: {result.exception_code}")
| Code | Meaning | Fix |
|---|---|---|
| 01 | Illegal Function | Device doesn't support this function |
| 02 | Illegal Data Address | Address doesn't exist |
| 03 | Illegal Data Value | Value out of range |
| 04 | Slave Device Failure | Device error |
| 05 | Acknowledge | Request accepted, processing |
| 06 | Slave Device Busy | Try again later |
これは、Modbus通信でエラーが発生した際に返される「Exception Code(例外コード)」の扱い方を説明しています。Modbusでは、通信自体は成立しているものの、要求内容に問題がある場合、通常の応答ではなく例外応答が返されます。
コード例では、存在しない可能性の高いアドレス 9999 から保持レジスタを読み取っています。この場合、通信相手は「アドレスが不正」という判断を行い、result.isError() が True になります。result.exception_code には、装置が返した具体的な例外コードが格納されており、エラー原因の特定に使えます。
表にある例外コードは、代表的なものです。
01 は機器がその機能をサポートしていない場合、
02 は指定アドレスが存在しない場合を示します。
03 は値が許容範囲外、
04 は内部エラーです。
05 と 06 は一時的な状態を表し、再試行や待機が有効です。
例外コードを確認することで、「通信エラー」と「装置仕様エラー」を切り分けられる点が重要です。
Address Mapping Example
Device manual says "Temperature at register 40101"?
# WRONG - This won't work
result = client.read_holding_registers(40101, 1)
# RIGHT - Subtract 40001
result = client.read_holding_registers(100, 1) # 40101 - 40001 = 100
これは、Modbus機器のマニュアル表記とpymodbusで指定するアドレスの違いを説明する典型的な例です。多くの機器マニュアルでは、保持レジスタを 40101 のような形式(1始まり+種別番号付き) で記載します。しかし、pymodbusでは レジスタ種別番号(40000番台)を含めず、0始まりの番号で指定する必要があります。
そのため、マニュアル通りに 40101 を指定すると、存在しないアドレスとして扱われ、通信エラーになります。正しい指定方法は、40101 − 40001 = 100 と計算し、address=100 を指定することです。
このルールは保持レジスタに限らず、コイルや入力レジスタでも同様です。
「マニュアル表記 − ベース番号」
という変換を常に意識することが、Modbusで値を正しく取得するための最重要ポイントです。
Timing and Limits
これは、**Modbus通信を安定して運用するために重要な「通信タイミングと制限値」**を整理した内容です。Modbusはシンプルなプロトコルですが、これらの制約を理解していないと、タイムアウトや通信エラーが発生しやすくなります。
Modbus TCP
- Default timeout: 3 seconds
- Max registers per read: 125
- Max coils per read: 2000
Modbus TCPでは、ネットワーク通信を前提としているため、デフォルトのタイムアウトは約3秒に設定されています。1回の読み取りで取得できる保持レジスタは最大125個、コイルは最大2000点までという上限があります。これを超えると例外応答や通信失敗になります。遅延が大きい環境では、timeout を長めに設定することで安定性が向上します。
Modbus RTU
- Inter-frame delay: 3.5 character times
- Response timeout: depends on baud rate
- Max frame size: 256 bytes
RTUはシリアル通信のため、通信間隔のタイミング管理が非常に重要です。フレーム間には3.5文字分の無通信時間が必要で、応答タイムアウトはボーレートに強く依存します。低速(例:9600bps)ほど待ち時間は長くなります。フレームサイズも最大256バイトに制限されています。
# Set custom timeout
client = ModbusTcpClient('192.168.1.100', timeout=10)
# RTU timing depends on baudrate
client = ModbusSerialClient(
'/dev/ttyUSB0',
baudrate=9600, # Slower = longer delays
timeout=5
)
コード例では、TCPでのカスタムタイムアウト設定と、RTUでボーレートに応じた待ち時間調整の考え方を示しています。通信環境に合わせた設定が安定動作の鍵です。
Practical Tips
これは、pymodbusを使ったModbus通信を安定・安全に運用するための実践的なベストプラクティスをまとめたものです。現場で起こりやすいトラブルを防ぐための基本ルールと言えます。
1. Always check connections first まず接続確認を行う
if not client.connect():
print("Can't connect")
exit()
connect() の結果を必ず確認し、接続できていない状態で読み書きを行わないことが重要です。ネットワーク不通や機器停止を早期に検出できます。
2. Always check for errors エラー応答を必ず確認する
result = client.read_holding_registers(0, 10)
if result.isError():
print(f"Error: {result}")
exit()
Modbusでは、通信は成功しても内容がエラーの場合があります。isError() を確認することで、不正アドレスや未対応機能などを正しく判定できます。
3. Close connections 接続は必ずクローズする
try:
# Your code
pass
finally:
client.close()
通信終了時に close() を呼ばないと、ソケットやリソースが残り、不安定動作の原因になります。try-finally を使うことで例外発生時も安全に終了できます。
4. Use context managers コンテキストマネージャを使う
with ModbusTcpClient('192.168.1.100') as client:
result = client.read_holding_registers(0, 10)
with 構文を使うと、接続と切断を自動管理でき、コードが簡潔かつ安全になります。実運用では最も推奨される書き方です。


