M5Stack Modbus 485リレー4CHの使い方

Modbus 485 Relayの使い方

M5StackでModbus通信を使用した4チャンネルのリレーユニットの使い方を紹介します。

Modbus通信用の2本の配線だけで4つのリレーのON/OFFを制御できるため、出力点数の限られたデバイスでPLC(Programmable Logic Controller、シーケンサとも言う)のような制御や有線での多点遠隔操作をしたい場合に最適です。

今回使用したリレーユニットには「入力端子」も4点あるため、これ単体で入力4点、出力4点の制御ができます。
さらに増設すれば、数十点〜数百点の入出力を制御することも可能になります。

Modbus通信で入力状態を確認して、Modbus通信で出力を指定するだけなので複数のI/O端子を持ったPLC(シーケンサ)を安価に実現することができます。

今回は基本的な入力状態の確認から、リレー出力の方法、増設するためのスレーブアドレスの指定方法等を詳しく紹介していきます。


動作確認には「M5Stack Basic Ver2.6」を使用しました。
「M5Stack Basic」の使い方や開発環境の準備については以下のリンクで詳しく紹介しています。

M5Stack Basicの使い方、端子配列、サンプルプログラムで詳しく紹介
基本仕様、端子配列、サンプルプログラムで「Lチカ」やアナログ入力(ADC)、アナログ出力(DAC)、シリアル通信(UART)、液晶表示、バッテリー残量表示等の基本的な動作まで詳しく紹介します。
M5StackシリーズのためのArduino IDEのインストール方法と初期設定、使い方紹介
ArduinoIDEバージョン2のインストール方法から初期設定、スケッチ例の書き込み、コピペでの使い方まで詳しく紹介します。インストールはArduinoでも同じです。

Modbus通信を使用した温湿度計「XY-MD02」の使い方は、以下のリンクで詳しく紹介しています。

M5Stack Modbus温度,湿度測定器XY-MD02の使い方
M5Stackを使ってArduinoコマンドで、Modbus通信を使用した温湿度表示器XY-MD02の使い方をModbusプロトコルと合わせて詳しく紹介します。
スポンサーリンク

1.Modbusとは

「Modbus」とは、通信プロトコルの1つで、主に産業機器(PLC、センサー、モーターなど)の制御に使用されます。

マスタースレーブ方式で動作し、1つのマスターに対して複数のスレーブ機器を制御できます。

プロトコルはシリアル通信や、TCP/IP上のEthernet通信(LAN、WAN)でも使用でき、通信に必要な配線は2本(A+、B-)だけです。

通信距離はシリアル通信(RS485)では数百m〜1km程、TCP / IPではLANで100m程(中継可)WANならネットワーク環境によって広範囲の通信が可能です。

比較的簡単に実装でき柔軟性があるため、工場やプロセスオートメーション、エネルギー管理などの分野で広く使用されています。

スポンサーリンク

2.リレーユニット 485 Relay 4CHについて

今回使用したリレーユニット「485 Relay 4CH」についての情報を以下にまとめました。

・基本仕様

基本仕様は下表のようになります。

項目内容
製品名Modbus RTU 4チャンネル12Vリレー出力ボードモジュールスイッチ入力RS485
型式485 Relay 4CH V1.1
コントローラーSTM社製 8S003F3P6
通信プロトコルModbus-RTU
通信速度(ボーレート)9600bps(8bit、パリティ無し[NONE]、ストップビット1bit)
通信可能距離1000m(RS485)
通信(スレーブ)アドレス1〜247(初期値1)※ブロードキャスト0
Modbus通信端子A+, B-
入力端子IN1, IN2, IN3, IN4, GNDx2
リレーリレー1〜4(C接点 NO/NC)
動作電圧DC 5V〜12V(アダプター入力、プラグ外径φ5.5)
サイズ(W x H x D)H67 x W76 x (D19) mm

・外観、端子配列

今回使用したリレーユニット「485 Relay 4CH」の外観は下画像のようになります。

Modbus 485 Relayの使い方、外観
Modbus 485 Relayの使い方、外観
Modbus 485 Relayの使い方、外観
Modbus 485 Relayの使い方、外観

電源入力はACアダプター(プラグ外径φ5.5)が使用でき、リレー用出力端子はC接点(NO/NC)でコネクタになっているため、抜き差しが可能です。
電源は端子台でも入力可能で、他にも入力用の端子台と、Modbus通信用の端子「 A+, B−」があります。

・通信仕様一覧表

通信仕様については下表のようになります。
Modbus通信では「ファンクションコード」によって通信内容が異なります。

「ファンクションコード」はいくつかありますが、このリレーユニットでは以下表のものが使用できます。

「保持レジスタ」に格納されているスレーブアドレスの書き換えにはファンクションコード「0x10」の「保持レジスタ連続書込み」を使用します。
「0x06」の「保持レジスタ単独書込み」は反応はしますが変更は反映されませんでしたので今回は使用しません。

スレーブアドレスの初期値は「1」ですが、変更することも可能です。
変更するためにはスレーブアドレスにブロードキャストの「0」を指定して行います。
今回使用したリレーユニットの場合は、スレーブアドレス「0」で、他にも以下表のようにバージョン確認等ができます。

Modbus 485 Relayの使い方
ブロードキャストとは、複数のスレーブデバイスに同時に送信することを意味します。
例えば、1つのマスターが10台のスレーブに同じコマンドを送信する必要がある場合、それぞれのスレーブに個別に送信するよりも効率的です。
ただし、すべてのスレーブが同じリクエストに対して、同じ応答を返す必要があるため、使用には注意が必要です。

以下表はリレーのON/OFF制御や入出力端子の状態を確認する方法をまとめたものです。
リレーの制御には「コイル」の読み書きで行います。
入力端子の状態確認には「入力ステータス」を読み込んで行います。

Modbus 485 Relayの使い方
ファンクションコード「0x0F」で全てのリレーのON/OFFを一括指定できますが、個別のON/OFFの組み合わせを一括指定することはできませんでした。
このため、ファンクションコード「0x05」で個別に指定しています。
スポンサーリンク

3.通信方法の確認準備

リレーユニットの動作確認をするために使用した部品や配線図、ライブラリについては以下のようになります。

・準備するもの

動作確認に必要なものは以下になります。

・M5Stack Basic(Core2でもシリアル通信端子を合わせれば使用可能です。)
 ※2023/6/17時点では「yahoo!ショッピング」が6千円台で一番安かったです。

・RS485シリアル通信モジュール
 UARTとRS485の変換モジュールであればなんでも良いです。
 私はAmazonで買える以下のものを使ってますが「Yahoo!ショッピング」で買える「M5Stack用RS485ユニット(GROVEコネクタタイプ)」も扱いやすいです。

・リレーユニット 485 Relay 4CH
 ここで紹介したものはちょっと前に購入したもので同じものはAmazonではありませんでしたので以下同等品になります。基本的なリレーのON/OFF方法は同じですが保持レジスタの内容は違う可能性あります。

・配線図

配線図は下画像のようになります。
M5StackのUART(RX/TX)端子とRS485の変換モジュールは、使用するものに合わせて配線してください。

Modbus 485 Relayの使い方、配線図
「M5Stack」と「RS485通信モジュール」の通信配線「RXD/TXD」の接続は「R2とRXD」「T2とTXD」のように接続しています。
RX端子とTX端子は交差して接続するイメージですが、これはそうではありません。
※M5Stack用RS485ユニットの場合は交差して接続します。
(この下の配線図やアイキャッチ画像を参照)
他のものを使用する場合は、お使いの通信モジュールでよく確認して使用してください。

「M5Stack用RS485ユニット」を使用した場合の配線図は下画像になります。(アイキャッチ画像の配線です。)

Modbus 485 Relayの使い方、配線図
「GROVEコネクタ配線」は「SheeedStudio」社製のものを使用した場合の配線図です。
「M5Stack」社製のものとは「黄色」と「白色」の配列が逆になるので注意してください。

・使用ライブラリ

使用したライブラリは「ModbusMaster」です。
Modbus通信用のライブラリはたくさんありますが「ArduinoIDE」のライブラリマネージャで検索した場合は、下画像の「Doc Walker」さんのライブラリです。

modbus温湿度表示器XY-MD02使用ライブラリ

お使いの開発環境で検索してインストールしてください。

4.基本的な通信方法の確認

サンプルプログラムを使用して、基本的な通信方法の確認を行っていきます。

・動作紹介

通信確認用サンプルプログラムを書き込んで実行したものは下画像のようになります。

Modbus 485 Relayの使い方、動作確認

液晶画面の上から「スレーブアドレス番号」「入力端子(IN)1〜4のON/OFF状態」「リレー1〜4のON/OFF状態」が表示されます。

Modbus 485 Relayの使い方、動作確認

4つのスイッチは入力端子IN1〜4に接続され「IN1」のスイッチを押すと「リレー1」がONし「IN2」のスイッチを押すと「リレー2」がONのように各リレーがONするようにプログラムされています。

Modbus 485 Relayの使い方、動作確認

「ボタンA」を押すと、スレーブアドレスが「2」に変更されて動作が継続できます。

Modbus 485 Relayの使い方、動作確認

「ボタンB」を押すと、スレーブアドレスが「3」に変更されて動作が継続できます。

Modbus 485 Relayの使い方、動作確認

「ボタンC」を押すと、全てのリレーがONし、離すと全てOFFします。

電源ON時には初期設定でスレーブアドレスは「1」になるようにプログラムされています。

・通信確認用サンプルプログラム(コピペ)

通信確認用のサンプルプログラムを以下に準備しました。
各通信の動作は関数としてまとめてますので、それぞれの関数ごとに詳しく紹介していきます。


サンプルプログラムは以下になります。コピペで貼り付けて書き込んで実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。

#include <M5Stack.h>      // CORE2の場合は <M5Core2.h>
#include "ModbusMaster.h" // modbusライブラリのインクルード

#define RX_PIN 16     // シリアル通信端子番号(RX)※CORE2は13
#define TX_PIN 17     // シリアル通信端子番号(TX)※CORE2は14
#define SLAVE_NO 1    // スレーブアドレス指定

ModbusMaster node0;   // スレーブアドレス読み書き専用の通信用インスタンスを作成
ModbusMaster node1;   // 指定したスレーブアドレスとの通信用インスタンスを作成

// 変数宣言
uint8_t result = 0;         // 通信結果取得用
uint8_t read_slave_address; // スレーブアドレス 確認用
uint8_t read_relay_state;   // リレー状態 確認用
uint8_t read_input_state;   // 入力状態 確認用
bool relay[4];              // リレー1〜4 ON/OFFビット格納用
bool input[4];              // 入力端子1〜4 ON/OFFビット格納用
bool in_state[4];           // 入力状態保持用

// 関数 ********************************************************************
// スレーブアドレス設定関数(node0で実行)---------------------------------------
void setSlaveAddress(uint8_t address) {
  // 0x10:保持レジスタ連続書込み
  node0.setTransmitBuffer(0, address);              // 書込む値を準備(バッファ番号, スレーブアドレスNo)
  result = node0.writeMultipleRegisters(0x0000, 1); // 連続書込み実行(アドレス, 書込みレジスタ数)
  delay(10);  // 通信完了待ち
}
// スレーブアドレス読込み、接続設定関数 -----------------------------------------
void slaveConnectionSetting() {
  // 0x03:保持レジスタ連続読込み
  result = node0.readHoldingRegisters(0x0000, 1);   // 保持レジスタ読込み(開始アドレス, 読込みレジスタ数)
  read_slave_address = node0.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち

  // Modbus通信初期化
  node1.begin(read_slave_address, Serial2);  // 読み取ったスレーブアドレスでシリアル通信2を介して通信開始
}
// リレーON/OFF状態確認関数 --------------------------------------------------
void readRelayState() {
  // 0x01:コイルON/OFF状態読込み(リレー1〜4状態読込み、下位4bitで取得)
  result = node1.readCoils(0x0000, 8);            // (開始アドレス, 読込みコイル数)
  read_relay_state = node1.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち
  // リレーON/OFF確認ビットセット
  relay[0] = read_relay_state & 0b0001; // リレー1ビットがONだったなら1セット
  relay[1] = read_relay_state & 0b0010; // リレー2ビットがONだったなら1セット
  relay[2] = read_relay_state & 0b0100; // リレー3ビットがONだったなら1セット
  relay[3] = read_relay_state & 0b1000; // リレー4ビットがONだったなら1セット
}
// 入力端子ON/OFF状態確認関数 ------------------------------------------------
void readInputStatus() {
  // 0x02:入力ON/OFF状態読込み(入力端子状態読込み)
  result = node1.readDiscreteInputs(0x0000, 8);   // (開始アドレス, 読込み入力数)
  read_input_state = node1.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち
  // 入力端子ON/OFFビットセット
  input[0] = read_input_state & 0b0001; // IN1が1なら1セット
  input[1] = read_input_state & 0b0010; // IN1が2なら1セット
  input[2] = read_input_state & 0b0100; // IN1が3なら1セット
  input[3] = read_input_state & 0b1000; // IN1が4なら1セット
}
// リレーON/OFF出力関数 -----------------------------------------------------
void outputSingleRelay(uint8_t relay_num, uint16_t address, bool signal) {
  // 0x05:コイルのON/OFF単独書込み
  if (signal == false) {                  // リレー信号がfalseなら
    node1.writeSingleCoil(address, 0x00); // リレー OFF実行
  } else {
    node1.writeSingleCoil(address, 0xFF); // リレー ON実行
  }
  delay(10);        // 通信完了待ち
  readRelayState(); // リレーON/OFF状態確認関数呼び出し
}
// 全リレーON/OFF出力関数 ----------------------------------------------------
void outputAllRelay(uint16_t value) {
  // 0x0F:コイル状態連続書込み
  node1.setTransmitBuffer(0, value);                  // 書込む値を準備(0x01FF/0x0100 = 全ON/全OFF)
  result = node1.writeMultipleCoils(0x0000, 0x0008);  // コイル状態連続書込み(アドレス, 書込みコイル数)
  delay(10);                                          // 通信完了待ち
  readRelayState();                                   // リレーON/OFF状態確認関数呼び出し 
}
// 初期設定 *******************************************************************
void setup() {
  M5.begin();   // 本体初期化
  // Modbus通信初期設定(SERIAL_8N1:データビット幅[8]、パリティチェック[No]、ストップビット[1])
  Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);  // シリアル通信2 初期化(RX, TX)
  node0.begin(0, Serial2);                          // スレーブアドレス読み書き専用の通信をシリアル通信2で設定
  delay(10);                                        // 通信安定待ち
  // スレーブアドレス設定
  setSlaveAddress(SLAVE_NO);          // スレーブアドレス設定関数呼び出し
  slaveConnectionSetting();           // スレーブアドレス読込み、接続設定関数呼び出し
  // 液晶画面初期設定
  M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色
  M5.Lcd.setTextFont(4);              // フォント
}
// メイン処理 *******************************************************************
void loop() {
  M5.update();        // ボタン状態初期化
  // 入力端子の状態確認
  readInputStatus();  // 入力端子ON/OFF状態確認関数呼び出し

  // 入力端子によるリレーON/OFF単独出力、ON/OFF状態確認(入力状態保持により入力変化時のみ実行)
  for(int i=0; i<4; i++) {                // 入力端子の数だけ繰り返す
    if(input[i] != in_state[i]) {         // 入力状態が変化した場合のみ実行
      outputSingleRelay(i, 0x0000+i, input[i]); // リレー出力実行(リレー番号, Coilアドレス, ON/OFF=True/false)
      in_state[i] = input[i];             // 入力状態を更新する
    }
  }
  // ボタン操作(設定値書込み)
  if (M5.BtnA.wasPressed()) {   // AボタンONならスレーブアドレス2に変更
    setSlaveAddress(2);         // スレーブアドレス設定関数呼び出し
    slaveConnectionSetting();   // スレーブアドレス読込み、接続設定関数呼び出し
  }
  if (M5.BtnB.wasPressed()) {   // BボタンONならスレーブアドレス3に変更
    setSlaveAddress(3);         // スレーブアドレス設定関数呼び出し
    slaveConnectionSetting();   // スレーブアドレス読込み、接続設定関数呼び出し
  }
  if (M5.BtnC.wasPressed()) {   // CボタンONで全リレー ON
    outputAllRelay(0x01FF);     // 全リレーON/OFF出力関数呼び出し(書込む値を指定:0x01FF/0x0100 = 全ON/全OFF)
  }
  if (M5.BtnC.wasReleased()) {  // CボタンOFFで全リレー OFF
    outputAllRelay(0x0100);     // 全リレーON/OFF出力関数呼び出し(書込む値を指定:0x01FF/0x0100 = 全ON/全OFF)
  }
  // 液晶表示
  M5.Lcd.setCursor(0, 0); // 表示座標
  M5.Lcd.printf("Slave No.%02d\n", read_slave_address); // スレーブアドレス表示
  // 入力端子1〜4 状態確認表示
  M5.Lcd.printf("Input  : %d, %d, %d, %d\n", input[0], input[1], input[2], input[3]);
  // リレー1〜4 状態確認表示
  M5.Lcd.printf("Relay : %d, %d, %d, %d\n", relay[0], relay[1], relay[2], relay[3]);

  // エラー表示
  if (result != 0) {                  // 通信成功以外ならエラー表示
    M5.Lcd.fillScreen(BLACK);         // 背景色
    M5.Lcd.setCursor(0, 0);           // 表示座標
    M5.Lcd.println("ERROR");          // 「ERROR」表示
    M5.Lcd.printf("0x%02X", result);  // 16進数の「エラーコード」表示
  }
  delay(100); // 遅延時間
}

・初期設定詳細

Modbus通信のための初期設定について、サンプルプログラムから抜粋して紹介します。

#include <M5Stack.h>      // CORE2の場合は <M5Core2.h>
#include "ModbusMaster.h" // modbusライブラリのインクルード

#define RX_PIN 16     // シリアル通信端子番号(RX)※CORE2は13
#define TX_PIN 17     // シリアル通信端子番号(TX)※CORE2は14
#define SLAVE_NO 1    // スレーブアドレス指定

ModbusMaster node0;   // スレーブアドレス読み書き専用の通信用インスタンスを作成
ModbusMaster node1;   // 指定したスレーブアドレスとの通信用インスタンスを作成

// 変数宣言
uint8_t result = 0;         // 通信結果取得用

まずは上コードの「2行目」で「ModbusMaster」ライブラリをインクルードします。

次に「8,9行目」で「ModbusMaster」のインスタンスを作成し「node0」「node1」として使用できるようにします。

node0」はブロードキャストとして使用され、全てのデバイスに一括で送信したいときに使用されます。今回のリレーユニットではスレーブアドレスの変更に使用されるため、複数使用する場合は、1台づつ接続して設定してからそれぞれを接続して使用します。

12行目」では通信結果を格納するための変数「result」を宣言します。
通信成功の場合は「0」が格納されるため「0以外」の場合は通信エラーとなります。
エラー時には格納されたエラー番号を確認できるため、エラーの原因を探ることができます。

次に初期設定を行います。以下にサンプルプログラムから抜粋して紹介します。

void setup() {
  M5.begin();   // 本体初期化
  // Modbus通信初期設定(SERIAL_8N1:データビット幅[8]、パリティチェック[No]、ストップビット[1])
  Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);  // シリアル通信2 初期化(RX, TX)
  node0.begin(0, Serial2);                          // スレーブアドレス読み書き専用の通信をシリアル通信2で設定
  delay(10);                                        // 通信安定待ち
  // スレーブアドレス設定
  setSlaveAddress(SLAVE_NO);          // スレーブアドレス設定関数呼び出し
  slaveConnectionSetting();           // スレーブアドレス読込み、接続設定関数呼び出し
  // 液晶画面初期設定
  M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色
  M5.Lcd.setTextFont(4);              // フォント
}

上コードの「4行目」で「Modbus」通信を行うためのシリアル通信の初期設定を行います。
SERIAL_8N1」で通信仕様を設定します。
今回のリレーユニットでは「データビット幅[8bit]、パリティチェック[No(なし)]、ストップビット[1bit]」です。

シリアル通信の端子番号は「Serial2」の「M5Stack Basic」の標準端子「RX=16/TX=17」を指定しています。

マスター側に「Basic」ではなく「Core2」を使用する場合は、ヘッダーを「M5Core2.h」に変更し、シリアル通信端子を「RX=13/TX=14」にするだけで同様に動作確認ができます。

5行目」で以下のように指定して「node0」でModbus通信を開始します。

node0.begin(スレーブアドレス, 使用するシリアル通信);
  • スレーブアドレス:通信対象機器のスレーブアドレスを指定。ここでは「0
  • 使用するシリアル通信:Modbus通信に使用するシリアル通信名を指定。今回は「Serial2
複数のスレーブアドレスの機器と通信する場合は「node1, node2」のようにインスタンスを準備して「シリアル通信名」は同じでも良いので以下のように指定します。
 node1.begin(1, Serial2);
 node2.begin(2, Serial2);
8行目」でスレーブアドレスを設定する関数を呼び出し、起動時に毎回スレーブアドレスを設定するようにしています。

9行目」では現在のスレーブアドレスを読込んで、現在のスレーブアドレスでModbus通信を開始できるように「node1」の通信設定を行う関数を呼び出します。

連続してModbusコマンドが実行されると通信エラーになります。
このため、Modbus通信コマンドを実行した後には、通信完了を待つために毎回「delay(10)」を書いてあります。

・スレーブアドレスの設定方法(保持レジスタ連続書込み:0x10)

スレーブアドレスを設定するための方法について、サンプルプログラムから抜粋して紹介します。

// スレーブアドレス設定関数(node0で実行)---------------------------------------
void setSlaveAddress(uint8_t address) {
  // 0x10:保持レジスタ連続書込み
  node0.setTransmitBuffer(0, address);              // 書込む値を準備(バッファ番号, スレーブアドレスNo)
  result = node0.writeMultipleRegisters(0x0000, 1); // 連続書込み実行(アドレス, 書込みレジスタ数)
  delay(10);  // 通信完了待ち
}

上コードの「4,5行目」で保持レジスタの連続書込みを実行して、スレーブアドレスが格納されている「保持レジスタ」の値を変更しています。

変更するレジスタは1つですが、今回使用したリレーユニットでは単独書込みで変更できなかったため、連続書込みを使用して変更します。

連続書込みコマンドの詳細は以下のようになります。

node0.setTransmitBuffer(送信データバッファ番号, 書込みデータ);
result = node0.writeMultipleRegisters(開始アドレス, 書込みレジスタ数);
  • node0:スレーブアドレス「0」との通信を表します。(アドレスは初期設定で設定しておく)
  • setTransmitBuffer:複数ある送信データは、このコマンドで事前に送信データバッファとして準備しておきます。(今回は1つだけです。)
  • 送信データバッファ番号:書込むために送信するデータ番号を送信データ数分「0」から順番に「0, 1, 2〜」のように指定します。
  • 書込みデータ:書込むデータを「送信データバッファ番号」ごとに指定します。
  • writeMultipleRegisters:Modbus通信のファンクションコード「0x10」を表し保持レジスタ」の内容を連続書込みするためのコマンドです。
    開始アドレス」と「書込みレジスタ数」を指定することで、連続して値を変更することができます。
    今回使用したリレーユニットは「node0」の「保持レジスタ」のアドレス「0x0000」にスレーブアドレスが格納されているためこの1つだけを変更しています。
  • 開始アドレス:連続書込み対象の「保持レジスタ」の最初のレジスタアドレスを指定します。
  • 書込みレジスタ数:連続書き込みを行うレジスタ数を指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

送受信データ構成について(保持レジスタ連続書込み時)

スレーブアドレスを書込むために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x10:保持レジスタ連続書込み
node0.setTransmitBuffer(0, 1);              // 書込む値を準備(バッファ番号, スレーブアドレスNo)
result = node0.writeMultipleRegisters(0x0000, 1); // 連続書込み実行(アドレス, 書込みレジスタ数)

送信される(リクエスト)データは16進数で表すと以下のようになります。

送信データ:00 10 00 00 00 01 02 00 01 6A 00
※スレーブアドレス「1」を書き込んだ場合

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(保持レジスタ連続書込み)
 3byte目:開始アドレス上位
 4byte目:開始アドレス下位
 5byte目:書込みレジスタ数上位
 6byte目:書込みレジスタ数下位
 7byte目:データバイト数
 8byte目:送信データバッファ番号「0」の書込みデータ上位
 9byte目:送信データバッファ番号「0」の書込みデータ下位
 12,13byte目:送信データから生成されたエラーチェック用の「CRCコード」


スレーブ側から送信されてくる受信(レスポンス)データは以下のようになります。
受信データ:00 10 00 00 00 01 00 18

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(保持レジスタ連続書込み)
 3byte目:スレーブアドレス上位
 4byte目:スレーブアドレス下位
 5byte目:書込みレジスタ数上位
 6byte目:書込みレジスタ数下位

 7, 8byte目:受信データから生成されたエラーチェック用の「CRCコード」


・スレーブアドレスの確認方法(保持レジスタ読込:0x03)

スレーブアドレスを確認するための方法について、サンプルプログラムから抜粋して紹介します。

// スレーブアドレス読込み、接続設定関数 -----------------------------------------
void slaveConnectionSetting() {
  // 0x03:保持レジスタ連続読込み
  result = node0.readHoldingRegisters(0x0000, 1);   // 保持レジスタ読込み(開始アドレス, 読込みレジスタ数)
  read_slave_address = node0.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち

  // Modbus通信初期化
  node1.begin(read_slave_address, Serial2);  // 読み取ったスレーブアドレスでシリアル通信2を介して通信開始
}

上コードの「4行目」でスレーブアドレスが格納されている「保持レジスタ」の値を読み込んでいます。


保持レジスタ読込みコマンドの詳細は以下のようになります。

result = node0.readHoldingRegisters(開始アドレス, 読込みレジスタ数);
  • node0:スレーブアドレス「0」との通信を表します。(アドレスは初期設定で設定しておく)
  • readHoldingRegisters:Modbus通信のファンクションコード「0x03」を表し保持レジスタ」の内容を読込みするためのコマンドです。
    スレーブアドレスは「保持レジスタ」に格納されているため「開始アドレス」と「書込みレジスタ数」を指定することで、値を読み込むことができます。
  • 開始アドレス:読込み対象の「保持レジスタ」の最初のレジスタアドレスを指定します。
  • 読込みレジスタ数:読込みを行うレジスタ数を指定します。1つだけ読み込む場合は「1」です。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

5行目」で以下のように受信したデータをバッファとして以下のように取得します。

read_slave_address = node0.getResponseBuffer(0); // 受信データバッファ取得
  • node0.getResponseBuffer(0):これでスレーブから送信されてきたデータを取得できます。
  • read_slave_address:取得したデータを格納する変数です。
    受信するデータのバイト数に合わせて準備しておく必要があります。
スレーブアドレスの「データByte数」は「2byte」のため「16bit」以上の型で準備しておきます。

9行目」で受信したスレーブアドレスで、Modbus通信できるように「node1」の初期設定を以下のように行っています。

node1.begin(read_slave_address, Serial2); 
通常のリレーON/OFFやリレーの状態、入力端子の状態確認には「node1」を使用して行います。

送受信データ構成について(保持レジスタ読取り時)

「保持レジスタ」に格納されているスレーブアドレスを取得するために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x03:保持レジスタ連続読込み
result = node0.readHoldingRegisters(0x0000, 1);   // 保持レジスタ読込み(開始アドレス, 読込みレジスタ数)

送信される(リクエスト)データは16進数で表すと以下のようになります。

送信データ:00 03 00 00 00 01 85 DB

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(保持レジスタ連続読出し)
 3byte目:開始アドレス上位
 4byte目:開始アドレス下位
 5byte目:読出しレジスタ数上位
 6byte目:読出しレジスタ数下位
 7,8byte目:送信データから生成されたエラーチェック用の「CRCコード」


スレーブ側から送信されてくる受信(レスポンス)データは以下のようになります。

受信データ:00 03 02 00 01 44 44

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(保持レジスタ連続読出し)
 3byte目:データバイト数
 4byte目:スレーブアドレス上位
 5byte目:スレーブアドレス下位
 6, 7byte目:受信データから生成されたエラーチェック用の「CRCコード」

受信した各データは上位と下位で構成されているので、16bitの変数に格納して表示させます。

・リレーのON/OFF状態確認方法(コイルON/OFF状態読込み:0x01)

リレーのON/OFF状態を確認するための方法について、サンプルプログラムから抜粋して紹介します。

// リレーON/OFF状態確認関数 --------------------------------------------------
void readRelayState() {
  // 0x01:コイルON/OFF状態読込み(リレー1〜4状態読込み、下位4bitで取得)
  result = node1.readCoils(0x0000, 8);            // (開始アドレス, 読込みコイル数)
  read_relay_state = node1.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち
  // リレーON/OFF確認ビットセット
  relay[0] = read_relay_state & 0b0001; // リレー1ビットがONだったなら1セット
  relay[1] = read_relay_state & 0b0010; // リレー2ビットがONだったなら1セット
  relay[2] = read_relay_state & 0b0100; // リレー3ビットがONだったなら1セット
  relay[3] = read_relay_state & 0b1000; // リレー4ビットがONだったなら1セット
}

上コードの「4行目」でリレーのON/OFF状態が格納されている「コイル」の値を読み込んでいます。


「コイル」の値を読み込むための詳細は以下のようになります。

result = node1.readCoils(開始アドレス, 読込みコイル数);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは関数内で事前に設定しています。)
  • readCoils:Modbus通信のファンクションコード「0x01」を表しコイル」の内容を読込みするためのコマンドです。
    開始アドレス」と「読込みコイル」を指定することで、値を読み込むことができます。
  • 開始アドレス:読込み対象の「コイル」の最初のアドレスを指定します。
  • 読込みコイル数:読込みを行うコイル数を指定します。このリレーユニットでは「8」を指定した時のみ、4つのリレーの状態を「下位4bit」で取得することができます。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

5行目」で以下のように受信したデータをバッファとして以下のように取得します。

read_relay_state = node1.getResponseBuffer(0); // 受信データバッファ取得
  • node1.getResponseBuffer(0):これでスレーブから送信されてきたデータを取得できます。
  • read_relay_state:取得したデータを格納する変数です。
受信した「1byte」のデータの「下位4bit」でリレー1〜4のON/OFF状態を確認できます。

送受信データ構成について(コイルON/OFF状態読込み時)

リレーのON/OFF状態を取得するために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x01:コイルON/OFF状態読込み(リレー1〜4状態読込み、下位4bitで取得)
result = node1.readCoils(0x0000, 8);            // (開始アドレス, 読込みコイル数)

送信される(リクエスト)データは16進数で表すと以下のようになります。

送信データ:01 01 00 00 00 08 3D CC

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(コイルON/OFF状態読込)
 3byte目:開始アドレス上位
 4byte目:開始アドレス下位
 5byte目:読出しコイル数上位
 6byte目:読出しコイル数下位
 7,8byte目:送信データから生成されたエラーチェック用の「CRCコード」


スレーブ側から送信されてくる受信(レスポンス)データは以下のようになります。

受信データ:01 01 01 00 51 88
※全てのリレーがOFFの場合

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(コイルON/OFF状態読込)
 3byte目:データバイト数
 4byte目:コイルON/OFF状態(下位4bitで確認)
 5, 6byte目:受信データから生成されたエラーチェック用の「CRCコード」

受信したコイルON/OFF状態のデータから、各リレーのON/OFF状態を取得し、個別の変数に格納しています。
(全リレーON = 0b1111/全リレーOFF = 0b0000)

・入力端子のON/OFF状態確認方法(入力ステータス読込み:0x02)

入力端子のON/OFF状態を確認するための方法について、サンプルプログラムから抜粋して紹介します。

// 入力端子ON/OFF状態確認関数 ------------------------------------------------
void readInputStatus() {
  // 0x02:入力ON/OFF状態読込み(入力端子状態読込み)
  result = node1.readDiscreteInputs(0x0000, 8);   // (開始アドレス, 読込み入力数)
  read_input_state = node1.getResponseBuffer(0);  // 受信データバッファ取得
  delay(10);  // 通信完了待ち
  // 入力端子ON/OFFビットセット
  input[0] = read_input_state & 0b0001; // IN1が1なら1セット
  input[1] = read_input_state & 0b0010; // IN1が2なら1セット
  input[2] = read_input_state & 0b0100; // IN1が3なら1セット
  input[3] = read_input_state & 0b1000; // IN1が4なら1セット
}

入力端子のON/OFF状態(入力ステータス)を確認する方法は、上で紹介した、リレーのON/OFF(コイル)の状態を読込むための方法とほぼ同じで、上コード「4行目」のように行います。

result = node1.readDiscreteInputs(開始アドレス, 読込み入力数);
受信したデータは「コイル」の時と同様に「5行目」バッファで受信して、下位4bitで確認します。

送受信データ構成について(入力ステータス読込み時)

送受信されるデータも「コイル」の時とほぼ同じで「ファンクションコード(以下の2byte目)」の部分が「01」から「02」に変わるだけで、16進数で表すと送受信でそれぞれ以下のようになります。

送信データ:01 02 00 00 00 08 79 CC
受信データ:01 02 01 0F E1 8C
※全ての入力端子がONの場合
受信した入力ステータスのデータから、各入力ステータスの状態を取得し、個別の変数に格納しています。
(全入力ON = 0b1111/全入力OFF = 0b0000)

・リレーのON/OFF方法(コイルON/OFF単独書込み:0x05)

リレーのON/OFF出力を制御する方法について、サンプルプログラムから抜粋して紹介します。

それぞれのリレーを順番にON/OFFさせるため、同時にON/OFFを指定したくても若干のタイムラグが発生します。
// リレーON/OFF出力関数 -----------------------------------------------------
void outputSingleRelay(uint8_t relay_num, uint16_t address, bool signal) {
  // 0x05:コイルのON/OFF単独書込み
  if (signal == false) {                  // リレー信号がfalseなら
    node1.writeSingleCoil(address, 0x00); // リレー OFF実行
  } else {
    node1.writeSingleCoil(address, 0xFF); // リレー ON実行
  }
  delay(10);        // 通信完了待ち
  readRelayState(); // リレーON/OFF状態確認関数呼び出し
}

上コードの5行目」と「7行目」でコイル」のON/OFF状態を変更して、リレーのON/OFF出力を行います。


コイルのON/OFF状態書込みコマンドの詳細は以下のようになります。

result = node1.writeSingleCoil(アドレス, 書込みデータ);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは関数内で事前に設定しています。)
  • writeSingleCoil:Modbus通信のファンクションコード「0x05」を表し「コイル」のON/OFF状態を変更するためのコマンドです。
    コイル」は「リレー1〜4」の4つあり、個別に変更したい場合はこのコマンドを使用します。
  • アドレス:書込み対象の「コイル」のアドレス「0x0000〜0x0003」を指定します。
  • 書込みデータ:書込みデータとして「0xFF00 = ON/ 0x0000 = OFF」を指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

送受信データ構成について(コイルON/OFF単独書込み時)

リレー1をONするために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x05:コイルのON/OFF単独書込み
node1.writeSingleCoil(0x0000, 0xFF); // リレー ON実行

送信される(リクエスト)データは16進数で表すと以下のようになります。

送信データ:01 05 00 00 FF 00 8C 3A

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(コイルON/OFF状態書込み)
 3byte目:レジスタアドレス上位
 4byte目:レジスタアドレス下位
 5byte目:書込みデータ上位(ON = FF/ OFF = 00)
 6byte目:書込みデータ下位
 7,8byte目:送信データから生成されたエラーチェック用の「CRCコード」

スレーブ側から送信されてくる受信(レスポンス)データは送信(リクエスト)データと同じです。

・全リレーのON/OFF方法(コイルON/OFF連続書込み:0x0F)

全てのリレーのON/OFF出力を一括で制御する方法について、サンプルプログラムから抜粋して紹介します。

一括で操作できるのは全てのリレーの一括ONか一括OFFのみです。
// 全リレーON/OFF出力関数 ----------------------------------------------------
void outputAllRelay(uint16_t value) {
  // 0x0F:コイル状態連続書込み
  node1.setTransmitBuffer(0, value);                  // 書込む値を準備(0x01FF/0x0100 = 全ON/全OFF)
  result = node1.writeMultipleCoils(0x0000, 0x0008);  // コイル状態連続書込み(アドレス, 書込みコイル数)
  delay(10);                                          // 通信完了待ち
  readRelayState();                                   // リレーON/OFF状態確認関数呼び出し 
}

上コードの「4,5行目」でコイルの連続書込みを実行して、コイル」のON/OFF状態を連続で変更して、4つのリレーを一括でON/OFF出力を行います。


連続書込みコマンドの詳細は以下のようになります。

node1.setTransmitBuffer(送信データバッファ番号, 書込みデータ);
result = node1.writeMultipleRegisters(開始アドレス, 書込みコイル数);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは関数内で事前に設定しています。)
  • setTransmitBuffer:複数ある送信データは、このコマンドで事前に送信データバッファとして準備しておきます。(今回の場合は1つだけ)
  • 送信データバッファ番号:書込むために送信するデータ番号を「0」から順番に指定します。
  • 書込みデータ:書込むデータを「送信データバッファ番号」ごとに指定します。
    (0x01FF/0x0100 = 全ON/全OFF)
  • writeMultipleRegisters:Modbus通信のファンクションコード「0x0F」を表しコイル」の内容を連続書込みするためのコマンドです。
    4つのリレーに対して4つの「コイル」があり「開始アドレス」と「書込みコイル数」を指定することで、連続して値を変更することができます。
  • 開始アドレス:連続書込み対象の「コイル」の最初のアドレスを指定します。
  • 書込みレジスタ数:連続書き込みを行うコイル数を指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

送受信データ構成について(コイルON/OFF連続書込み時)

4つのリレーの全てONするために「コイル」の値を連続で書込むために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x0F:コイル状態連続書込み
node1.setTransmitBuffer(0, 0x01FF);                 // 書込む値を準備(0x01FF/0x0100 = 全ON/全OFF)
result = node1.writeMultipleCoils(0x0000, 0x0008);  // コイル状態連続書込み(アドレス, 書込みコイル数)

送信される(リクエスト)データは16進数で表すと以下のようになります。

送信データ:01 0F 00 00 00 08 01 FF BE D5
※全てのリレーをONする場合

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(コイルON/OFF連続書込み)
 3byte目:開始アドレス上位
 4byte目:開始アドレス下位
 5byte目:書込みレジスタ数上位
 6byte目:書込みレジスタ数下位
 7byte目:データバイト数
 8byte目:書込みデータ(全ON = FF/ 全OFF = 00)
 9,10byte目:送信データから生成されたエラーチェック用の「CRCコード」


スレーブ側から送信されてくる受信(レスポンス)データは以下のようになります。
受信データ:01 0F 00 00 00 08 31 DB

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(コイルON/OFF連続書込み)
 3byte目:スレーブアドレス上位
 4byte目:スレーブアドレス下位
 5byte目:書込みレジスタ数上位
 6byte目:書込みレジスタ数下位

 7, 8byte目:受信データから生成されたエラーチェック用の「CRCコード」

5.まとめ

M5StackでModbus通信を使用した4チャンネルのリレーユニットの使い方を詳しく紹介しました。

Modbus通信を使用したリレーユニットにはいろいろなものがあり、今回は4チャンネルでしたが1チャンネル〜8チャンネルと種類も豊富です。

通信プロトコルについては、それぞれで固有のものもあると思いますが、基本的なリレーのON/OFF方法は「コイル(0x05)」を操作し、入力端子を持ったものは「入力ステータス(0x02)」で入力状態の確認ができると思います。

M5Stack等のマイコンボードでは入出力端子の数が限られており、各端子1つづつでリレー出力や入力確認を行おうとすると足りなくなったり、配線も複雑になります。

今回のリレーユニットを使用すれば、Modbus通信用の2本の配線だけで4つのリレーのON/OFF制御と4つの入出力端子の状態確認ができるため、複数の入出力を持ったPLC(シーケンサ)としても使用できます。

複数の入出力を制御したいときには、安価に実現できると思うので使用を検討してみてください。

M5Stack Modbus温度,湿度測定器XY-MD02の使い方
M5Stackを使ってArduinoコマンドで、Modbus通信を使用した温湿度表示器XY-MD02の使い方をModbusプロトコルと合わせて詳しく紹介します。

コメント

タイトルとURLをコピーしました