M5Stack Modbus温度,湿度測定器XY-MD02の使い方

modbus温湿度表示器XY-MD02の使い方

「M5Stack」を使ってArduinoコマンドで「Modbus」通信を使用した「温湿度表示器 XY-MD02」の使い方を詳しく紹介します。

「Modbus」の動作確認をしたくて、Amazonで探したら一番簡単に入手できたので、仕様や通信コマンド等を調べてまとめました。これを使って「Modbus」の通信方法についても確認していきます。

「Modbus」通信と聞くと少し敷居が高かったですが、わかってしまえば意外と簡単で便利なものなので、ぜひチャレンジしてみましょう♪


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

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

「Mosbus」通信を使用したリレーユニットの使い方は以下のリンクで詳しく紹介しています。

M5Stack Modbus 485リレー4CHの使い方
M5StackでModbus(Arduinoライブラリ)を使用した4CHリレーの使い方を詳しく紹介。2本の配線だけで4つのリレーをPLCのように制御してみましょう。
スポンサーリンク

1.Modbusとは

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

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

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

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

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

スポンサーリンク

2.温湿度測定器 XY-MD02について

温湿度表示器「XY-MD02」の情報を以下にまとめました。

・基本仕様

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

項目内容
製品名Modbus RTU RS485 SHT20 温度湿度トランスミッタ
型式XY-MD02
使用温湿度センサSHT20
通信プロトコルModbus-RTU
通信速度(ボーレート)9600bps(14400、19200も選択可)
通信可能距離1000m(RS485)
通信(スレーブ)アドレス1〜247(初期値1)
出力端子RS485(A+, B-)
動作電圧DC 5V〜30V
温度範囲(保証値)-40〜60℃
温度精度(保証値)±0.5℃
温度分解能(表示値)0.1℃
湿度範囲(保証値)0% RH 〜 80% RH
湿度精度(保証値)±3% RH
湿度分解能(表示値)0.1% RH
消費電力0.2W以下
保存温度-40〜85℃
保存湿度0%~95% RH
サイズ(W x H x D)65 x 46 x 28.5 mm
取付方法M4ネジ止め2箇所 または DINレール

・外観、端子配列

温湿度表示器「XY-MD02」の外観は下画像のようになります。

modbus温湿度表示器XY-MD02の使い方
modbus温湿度表示器XY-MD02の使い方

4極のコネクタ(緑)が付属していて抜き差し可能です。
端子配列は本体表面の銘板に書かれている通りコネクタの左から「B-, A+, 電源ー, 電源+」です。

背面には制御盤の部品固定に使用されるDINレールへの固定機構があり、赤い部分を引き上げて、レールに固定後に下げると固定できます。
ネジ穴も2箇所あるので、M4ねじでの固定も可能です。

・通信仕様一覧表

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

「ファンクションコード」はいくつかありますが、この温湿度表示器では4つのみです。
よく使うものが多いので動作確認にはちょうどいいと思います。

温度と湿度のデータ測定だけなので、使用できるコマンドも限られていてとてもシンプルです。

modbus温湿度計XY-MD02通信仕様一覧表
「スレーブアドレス」や「ボーレート(通信速度)」も必要に応じて変更可能ですが、変更すると、次に通信するときには変更内容に合わせてプログラムの修正も必要になります。
変更したい場合は確実に使いこなせるようになってから変更を検討しましょう。
設定値の変更については「各補正値」の変更で試すことができます。
この「補正値」は、書き換えただけでは測定値は変わりません。
この値は内部に保存しておくことができる値で、実際は起動時に読み出して、測定値と演算して表示させる必要があります。
スポンサーリンク

3.通信方法の確認準備

温湿度測定器「XY-MD02」からのデータ取得や、設定値の確認、データ書替えについての通信方法を確認していきますが、使用する部品や配線図、ライブラリについても紹介しておきます。

・準備するもの

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

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

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

・RS485温度湿度トランスミッター
 こちらはAmazonでいろんなところで買えます。

・その他、端子、圧着工具
 今回使用したRS485モジュールでは「JST製のXHコネクタ4極」を使用します。
 私が買った時はコネクタ配線は付属していなかったので自作していますが、付属の配線を使用してもM5Stack側のコネクタは別途圧着する必要があります。
 圧着には以下の専用工具があると便利です。

・配線図

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

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

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

modbus温湿度表示器XY-MD02の使い方、配線図2
「GROVEコネクタ配線」は「SheeedStudio」社製のものを使用した場合の配線図です。
「M5Stack」社製のものとは「黄色」と「白色」の配列が逆になるので注意してください。

・使用ライブラリ

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

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

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

通信方法の確認に必要なライブラリは以上です。
最後に紹介する完成品のサンプルプログラムでは、液晶表示用ライブラリの「M5GFX」も使用しています。

「M5GFX」ライブラリを使用した液晶表示方法については、以下のリンクで詳しく紹介しています。

M5Stack 液晶表示の使い方まとめ - 完全版 -(Arduinoコマンド)
M5Stackの液晶の使い方を初期設定から文字表示(print,draw関数)中央揃えや右揃え,フォント,線,図形,M5GFXのスプライト等わかりやすくまとめました。

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

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

・動作紹介

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

modbus温湿度表示器XY-MD02の使い方

液晶画面の上から「温度」「湿度」1行開けて、内部設定値の「スレーブアドレス番号」「通信速度(ボーレート)」「温度補正値」「湿度補正値」が表示されます。

modbus温湿度表示器XY-MD02の使い方

「ボタンA」を押すと「温度補正値」に「10」が書き込まれ、温度表示が+10されます。

modbus温湿度表示器XY-MD02の使い方

「ボタンB」を押すと「湿度補正値」に「-10」が書き込まれ、湿度表示が-10されます。

modbus温湿度表示器XY-MD02の使い方

「ボタンC」を押すと「温度と湿度補正値」に「0」が書き込まれ、温度と湿度はセンサーで測定された値のまま表示されます。

「各補正値」は本体内部に保存されるだけのため、起動時や書込んだときに読み出して、センサーの測定値と演算して画面に表示するようにしています。

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

通信確認用のサンプルプログラムを以下に準備しました。
このサンプルプログラムを使用して、各通信動作について詳しく紹介していきます。


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

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

#define RX_PIN 16       // シリアル通信端子番号(RX)※CORE2は13
#define TX_PIN 17       // シリアル通信端子番号(TX)※CORE2は14
ModbusMaster node1;     // スレーブアドレスNo.1との通信用インスタンスを作成
// 変数宣言
uint8_t result = 0;     // Modbus通信結果取得用
float temp_correction;  // 温度補正値
float hum_correction;   // 湿度補正値

// 初期設定 ------------------------------------------------------
void setup() {
  M5.begin();  // 本体初期化
  // Modbus通信初期設定(SERIAL_8N1:データビット幅[8]、パリティチェック[No]、ストップビット[1])
  Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);  // シリアル通信2 初期化(RX, TX)
  node1.begin(1, Serial2);                          // シリアル通信2を介してスレーブ1と通信
  // 液晶画面初期設定
  M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色
  M5.Lcd.setTextFont(4);              // フォント
}
// メイン処理 ----------------------------------------------------------
void loop() {
  M5.update();      // ボタン状態初期化
  M5.Lcd.setCursor(0, 0); // 表示座標

  // 温度、湿度データ取得
  // 0x04:入力レジスタ連続読込み(温度、湿度データ読み取り)
  result = node1.readInputRegisters(0x0001, 2);   // (開始アドレス, 読取りレジスタ数)
  if (result == 0) {                              // 通信成功ならレスポンスデータ取得
    float data[2];                                // 取得データ格納用
    for (int i = 0; i < 2; i++) {                 // 読取りレジスタ数分繰返す
      data[i] = node1.getResponseBuffer(i);       // 受信データバッファ取得
    }
    // 測定値換算表示(取得値1/10 + 補正値)
    data[0] = (data[0] / 10.0) + temp_correction; // 温度データ換算
    data[1] = (data[1] / 10.0) + hum_correction;  // 湿度データ換算
    M5.Lcd.printf("%5.1f'c  \n", data[0]);        // 温度表示
    M5.Lcd.printf("%5.1f%%  \n", data[1]);        // 湿度表示
  }
  M5.Lcd.print("\n"); // 改行

  // 内部設定値の表示
  // 0x03:保持レジスタ連続読込み(内部設定データ読取り)
  result = node1.readHoldingRegisters(0x0101, 4);  // (開始アドレス, 読取りレジスタ数)
  if (result == 0) {                        // 通信成功ならレスポンスデータ取得
    uint16_t data[4];                       // 取得データ格納用
    for (int i = 0; i < 4; i++) {           // 読取りレジスタ数分繰返す
      data[i] = node1.getResponseBuffer(i); // 受信データバッファ取得
    }
    // 補正値格納(正負の値に変換)
    temp_correction = (short int)data[2];   // 温度補正値
    hum_correction = (short int)data[3];    // 湿度補正値
    // 内部設定データ表示
    M5.Lcd.printf("Slave No.%02d  \n", data[0]);            // スレーブアドレス表示
    M5.Lcd.printf("baud rate : %dbps  \n", data[1]);    // 通信速度(ボーレート)表示
    M5.Lcd.printf("Temp_Hosei : %.0f'c    \n", temp_correction);  // 温度補正値表示
    M5.Lcd.printf("Hum_Hosei : %.0f%%     ", hum_correction);     // 湿度補正値表示
  }

  // ボタン操作(設定値書込み)
  // 0x06:保持レジスタ単独書き込み(補正値単独書込み)
  if (M5.BtnA.wasPressed()) {                           // AボタンONなら温度補正値書込み
    result = node1.writeSingleRegister(0x0103, 0x000a); // 単独書込み実行(アドレス, 書込む値[10])
  }
  if (M5.BtnB.wasPressed()) {                           // BボタンONなら湿度補正値書込み
    result = node1.writeSingleRegister(0x0104, 0xFFF6); // 単独書込み実行(アドレス, 書込む値[-10])
  }
  // 0x10:保持レジスタ連続書き込み(補正値連続書込み)
  if (M5.BtnC.wasPressed()) {                           // CボタンONなら温湿度補正値連続書込み
    node1.setTransmitBuffer(0, 0x0000);                 // 書込む値[0]を準備
    node1.setTransmitBuffer(1, 0x0000);                 // 書込む値[0]を準備
    result = node1.writeMultipleRegisters(0x0103, 2);   // 連続書込み実行(アドレス, 書込みレジスタ数)
  }
  // エラー表示
  if (result != 0) {          // 通信成功以外ならエラー表示
    M5.Lcd.fillScreen(BLACK); // 背景色
    M5.Lcd.setCursor(0, 80);  // 表示座標
    M5.Lcd.println("ERROR");  // 「ERROR」表示
    M5.Lcd.printf("0x%02X", result);  // 16進数の「エラーコード」表示
  }
  delay(200); // 遅延時間
}

・初期設定詳細

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
ModbusMaster node1;     // スレーブアドレスNo.1との通信用インスタンスを作成
// 変数宣言
uint8_t result = 0;     // Modbus通信結果取得用
float temp_correction;  // 温度補正値
float hum_correction;   // 湿度補正値

// 初期設定 ------------------------------------------------------
void setup() {
  M5.begin();  // 本体初期化
  // Modbus通信初期設定(SERIAL_8N1:データビット幅[8]、パリティチェック[No]、ストップビット[1])
  Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);  // シリアル通信2 初期化(RX, TX)
  node1.begin(1, Serial2);                          // シリアル通信2を介してスレーブ1と通信
  // 液晶画面初期設定
  M5.Lcd.setTextColor(WHITE, BLACK);  // 文字色
  M5.Lcd.setTextFont(4);              // フォント
}

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

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

今回は通信相手のスレーブが1つだけですが、複数ある場合は同様に「node2node3」のように準備します。

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

15,16行目」で「Modbus」通信を行うためのシリアル通信の初期設定を行います。
SERIAL_8N1」で通信仕様を設定します。
今回は「データビット幅[8bit]、パリティチェック[No(なし)]、ストップビット[1bit]」としています。必要に応じて書き換えます。

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

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

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

node1.begin(スレーブアドレス, 使用するシリアル通信);
  • スレーブアドレス:通信対象機器のスレーブアドレスを指定。今回は「1
  • 使用するシリアル通信:Modbus通信に使用するシリアル通信名を指定。今回は「Serial2
複数のスレーブアドレスの機器と通信する場合は「node2, node3」のようにインスタンスを準備して「シリアル通信名」は同じでも良いので以下のように指定します。
 node2.begin(2, Serial2);
 node3.begin(3, Serial2);

・温度、湿度データの取得方法(入力レジスタ読込み:0x04)

温度と湿度データを取得するための方法について、サンプルプログラムから抜粋して紹介します。

// 0x04:入力レジスタ連続読込み(温度、湿度データ読み取り)
result = node1.readInputRegisters(0x0001, 2);   // (開始アドレス, 読取りレジスタ数)
if (result == 0) {                              // 通信成功ならレスポンスデータ取得
  float data[2];                                // 取得データ格納用
  for (int i = 0; i < 2; i++) {                 // 読取りレジスタ数分繰返す
    data[i] = node1.getResponseBuffer(i);       // 受信データバッファ取得
  }
  // 測定値換算表示(取得値1/10 + 補正値)
  data[0] = (data[0] / 10.0) + temp_correction; // 温度データ換算
  data[1] = (data[1] / 10.0) + hum_correction;  // 湿度データ換算
  M5.Lcd.printf("%5.1f'c  \n", data[0]);        // 温度表示
  M5.Lcd.printf("%5.1f%%  \n", data[1]);        // 湿度表示
}

上コードは長々と書いてますが、基本的には以下の2手順でデータを取得できます。

  1. 2行目」でスレーブアドレスを指定して、データをリクエストするコマンドを送信すると対象のスレーブ機器からレスポンスとしてデータが送信されてきます。
  2. 6行目」で送信されてきたデータを取得して変数に格納しています。

2行目」の詳細は以下のようになります。

result = node1.readHoldingRegisters(開始アドレス, 読み取りレジスタ数);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
  • readHoldingRegisters:Modbus通信のファンクションコード「0x04」を表し入力レジスタ」からデータを取得するためのコマンドです。
    通信仕様一覧表」によると、温度と湿度は「入力レジスタ」に格納されているため、このコマンドを使用します。
    連続したレジスタのデータであれば、まとめて取得することができます。
  • 開始アドレス:取得したいデータが格納されているアドレスを指定します。
    通信仕様一覧表」によると、「温度」測定値はアドレス「0x0001」に、「湿度」測定値はアドレス「0x0002」に格納されています。
    このように連続したレジスタに格納されているデータは「開始アドレス」の「0x0001」を指定して次の「読み取りレジスタ数」を指定することで、連続して取得することができます。
  • 読み取りレジスタ数:取得したいデータのレジスタ数を表します。1つであれば「1」ですが、今回は連続した2つのレジスタのデータを取得したいため「2」を指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

6行目」の詳細は以下のようになります。
連続してデータを取得するために「for文」を使用しています。

for (int i = 0; i < 2; i++) {                           // 読取りレジスタ数分繰返す
  data[i] = node1.getResponseBuffer(i);   // 受信データバッファ取得
}
  • node1.getResponseBuffer(i):これでスレーブから送信されてきたデータを取得できます。
    連続したデータは配列として取得して使用します。
  • data[i]:取得したデータを格納する配列です。
    受信するデータのバイト数に合わせて準備しておく必要があります。
    通信仕様一覧表」によると「入力レジスタ」の「データByte数」は「2byte」のため「16bit」以上の型で準備しておく必要があります。
    今回は、この後のデータ換算のために「float型」としました。

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

温度、湿度の測定値を取得するために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x04:入力レジスタ連続読込み(温度、湿度データ読み取り)
result = node1.readInputRegisters(0x0001, 2);   // (開始アドレス, 読取りレジスタ数)

送信されるデータは16進数で表すと以下のようになります。

送信データ:01 04 00 01 00 02 20 0B

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


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

受信データ:01 04 04 ** ** ** ** ** **
※*印部は測定データによって変動します。

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(入力レジスタ連続読出し)
 3byte目:データバイト数
 4byte目:温度データ上位
 5byte目:温度データ下位
 6byte目:湿度データ上位
 7byte目:湿度データ下位

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

受信した温度、湿度データは上位と下位で構成されており、16bitの変数に格納したときに10倍のデータになっているため、1/10にして表示させます。
受信するデータについて、「2byte」の場合「1byte」づつ「上位byte」「下位byte」のように扱われることが多いです。中には「上位」と「下位」が入れ替わって送信されてくるものもあるため、この場合には入れ替える処理が必要になることがあります。
送受信データの仕様については、各スレーブ機器の通信仕様でよく確認しましょう。

・内部設定値の取得方法(保持レジスタ読込:0x03)

内部設定値を取得するための方法について、サンプルプログラムから抜粋して紹介します。

// 0x03:保持レジスタ連続読込み(内部設定データ読取り)
result = node1.readHoldingRegisters(0x0101, 4);  // (開始アドレス, 読取りレジスタ数)
if (result == 0) {                        // 通信成功ならレスポンスデータ取得
  uint16_t data[4];                       // 取得データ格納用
  for (int i = 0; i < 4; i++) {           // 読取りレジスタ分繰返す
    data[i] = node1.getResponseBuffer(i); // 受信データバッファ取得
  }
  // 補正値格納(正負の値に変換)
  temp_correction = (short int)data[2];   // 温度補正値
  hum_correction = (short int)data[3];    // 湿度補正値
  // 内部設定データ表示
  M5.Lcd.printf("Slave No.%02d  \n", data[0]);            // スレーブアドレス表示
  M5.Lcd.printf("baud rate : %dbps  \n", data[1]);    // 通信速度(ボーレート)表示
  M5.Lcd.printf("Temp_Hosei : %.0f'c    \n", temp_correction);  // 温度補正値表示
  M5.Lcd.printf("Hum_Hosei : %.0f%%     ", hum_correction);     // 湿度補正値表示
}

上コードも長々と書いてますが、温度、湿度データの取得の時とほぼ同じで、以下の2手順でデータを取得します。

  1. 2行目」でスレーブアドレスを指定して、データをリクエストするコマンドを送信すると対象のスレーブ機器からレスポンスとしてデータが送信されてきます。
  2. 6行目」で送信されてきたデータを取得して、変数に格納しています。

2行目」の詳細は以下のようになります。

result = node1.readHoldingRegisters(開始アドレス, 読み取りレジスタ数);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
  • readHoldingRegisters:Modbus通信のファンクションコード「0x03」を表し保持レジスタ」からデータを取得するためのコマンドです。
    通信仕様一覧表」によると、「スレーブアドレス」等の設定値は「保持レジスタ」に格納されているため、このコマンドを使用します。
    連続したレジスタのデータであれば、まとめて取得することができます。
  • 開始アドレス:取得したいデータが格納されているアドレスを指定します。
    通信仕様一覧表」によると、「スレーブアドレス」等の設定値は、アドレス「0x0101」から4つ連続したレジスタに格納されているため「開始アドレス」には「0x0101」を指定しています。
  • 読み取りレジスタ数:取得したいデータのレジスタ数を表します。1つであれば「1」ですが、今回は連続した4つのレジスタのデータを取得したいため「4」を指定しています。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。

6行目」の処理は、取得するデータ数が4つになっただけで、温度、湿度データの取得の時と同じです。

データを格納する配列は「2byte(16bit)」の「uint16_t」型としています。

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

内部設定値を取得するために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x03:保持レジスタ連続読込み(内部設定データ読取り)
result = node1.readHoldingRegisters(0x0101, 4);  // (開始アドレス, 読取りレジスタ数)

送信されるデータは16進数で表すと以下のようになります。

送信データ:01 03 01 01 00 04 14 35

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


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

受信データ:01 03 08 00 01 25 85 00 00 00 00 83 FC
※スレーブアドレス「1」通信速度「9600bps」各補正値「0」の場合のデータです。

データ構成は左から
 1byte目:スレーブアドレス
 2byte目:ファンクションコード(保持レジスタ連続読出し)
 3byte目:データバイト数
 4byte目:スレーブアドレス上位
 5byte目:スレーブアドレス下位
 6byte目:通信速度(ボーレート)上位
 7byte目:通信速度(ボーレート)下位
 8byte目:温度補正値上位
 9byte目:温度補正値下位
 10byte目:湿度補正値上位
 11byte目:湿度補正値下位

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

受信した各データは上位と下位で構成されているので、16bitの変数に格納して表示させます。
補正値のマイナスの値は「short int」型で処理しています。

・内部設定値の単独書込み方法(保持レジスタ単独書込み:0x06)

内部設定値を単独で書込み(変更)するための方法について、サンプルプログラムから抜粋して紹介します。

// 0x06:保持レジスタ単独書き込み(補正値単独書込み)
if (M5.BtnA.wasPressed()) {                           // AボタンONなら温度補正値書込み
  result = node1.writeSingleRegister(0x0103, 0x000a); // 単独書込み実行(アドレス, 書込む値[10])
}
if (M5.BtnB.wasPressed()) {                           // BボタンONなら湿度補正値書込み
  result = node1.writeSingleRegister(0x0104, 0xFFF6); // 単独書込み実行(アドレス, 書込む値[-10])
}

上コードの3行目」と「6行目」で単独書込みを実行して、それぞれ1つのレジスタの値を変更しています。

データの書込みは外部から参照・変更が可能な「保持レジスタ」に対して行います。
「入力レジスタ」は外部参照のみで変更はできません。

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

result = node1.writeSingleRegister(アドレス, 書込みデータ);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
  • writeSingleRegister:Modbus通信のファンクションコード「0x06」を表し保持レジスタ」の内容を単独書込みするためのコマンドです。
    通信仕様一覧表」によると「保持レジスタ」には4つのデータが格納されているため、個別に変更したい場合はこのコマンドを使用します。
  • アドレス:書込み対象の「保持レジスタ」のアドレスを指定します。
  • 書込みデータ:書込みデータを指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
書込みデータとして、今回は「各補正値」の「10 を 0x000a 」「-10 を 0xFFF6」のように「2byte」の16進数で指定しています。
マイナスの値は「short int」型にキャストして処理しています。
※「-10 〜 -1」=「0xFFF6 〜 0xFFFF」

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

「温度補正値」を単独で書込むために以下を実行したときに、実際に送受信されるデータについて確認します。

// 0x06:保持レジスタ単独書き込み(補正値単独書込み)
result = node1.writeSingleRegister(0x0103, 0x000a); // 単独書込み実行(アドレス, 書込む値[10])

送信されるデータは16進数で表すと以下のようになります。

送信データ:01 06 01 03 00 0a F8 31
※温度補正値として「10」を書き込んだ場合

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

スレーブ側から送信されてくる受信データは送信データと同じです。

・内部設定値の連続書込み方法(保持レジスタ連続書込み:0x10)

内部設定値を連続して書込み(変更)をするための方法について、サンプルプログラムから抜粋して紹介します。

// 0x10:保持レジスタ連続書き込み(補正値連続書込み)
if (M5.BtnC.wasPressed()) {                           // CボタンONなら温湿度補正値連続書込み
  node1.setTransmitBuffer(0, 0x0000);                 // 書込む値[0]を準備
  node1.setTransmitBuffer(1, 0x0000);                 // 書込む値[0]を準備
  result = node1.writeMultipleRegisters(0x0103, 2);   // 連続書込み実行(アドレス, 書込みレジスタ数)
}

上コードの「3〜5行目」で連続書込みを実行して、連続した複数のレジスタの値をまとめて変更しています。


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

node1.setTransmitBuffer(送信データバッファ番号, 書込みデータ);
result = node1.writeMultipleRegisters(開始アドレス, 書込みレジスタ数);
  • node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
  • setTransmitBuffer:複数ある送信データは、このコマンドで事前に送信データバッファとして準備しておきます。
  • 送信データバッファ番号:書込むために送信するデータ番号を「0」から順番に指定します。
  • 書込みデータ:書込むデータを「送信データバッファ番号」ごとに指定します。
  • writeMultipleRegisters:Modbus通信のファンクションコード「0x10」を表し保持レジスタ」の内容を連続書込みするためのコマンドです。
    通信仕様一覧表」によると「保持レジスタ」には4つのデータが格納されているため「開始アドレス」と「書込みレジスタ数」を指定することで、連続して値を変更することができます。
  • 開始アドレス:連続書込み対象の「保持レジスタ」の最初のレジスタアドレスを指定します。
  • 書込みレジスタ数:連続書き込みを行うレジスタ数を指定します。
  • result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
「単独書込み」や「連続書込み」でアドレス「0x101(スレーブアドレス)」や「0x102(通信速度)」を変更すると、次に読み書きするときにはプログラムの修正が必要になるため、誤って変更しないよう慎重に行いましょう。

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

「温度補正値」と「湿度補正値」を連続で書込むために以下を実行したときに、実際に送受信されるデータについて確認します。

node1.setTransmitBuffer(0, 0x0000);                 // 書込む値[0]を準備
node1.setTransmitBuffer(1, 0x0000);                 // 書込む値[0]を準備
result = node1.writeMultipleRegisters(0x0103, 2);   // 連続書込み実行(アドレス, 書込みレジスタ数)

送信されるデータは16進数で表すと以下のようになります。

送信データ:01 10 01 03 00 02 04 00 00 00 00 BE 2A
※各補正値としてそれぞれ「10」を書き込んだ場合

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


スレーブ側から送信されてくる受信データは以下のようになります。
受信データ:01 03 01 03 00 02 B0 34

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

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

5.完成した温湿度表示器

動作確認用のサンプルプログラムの動作では見た目が寂しいので、液晶表示部もそれなりに作成して以下のように完成させました。
通信部分は同じ内容なので、自分好みの温湿度表示器を作ってみましょう。

・動作紹介

表示内容は通信確認用のサンプルプログラムの時と同じです。
「M5GFX」ライブラリを使用して日本語表示にしてわかりやすくしました。

modbus温湿度表示器XY-MD02の使い方

「スレーブアドレス」と「ボーレート(通信速度)」も無いと寂しいので表示させてます。
「補正値」については個人的には必要性は感じられませんがせっかくなので残しました。

以下、通信確認の時と同じ動作です。

modbus温湿度表示器XY-MD02の使い方

「ボタンA」を押すと「温度補正値」に「10」が書き込まれ、温度表示が+10されます。

modbus温湿度表示器XY-MD02の使い方

「ボタンB」を押すと「湿度補正値」に「-10」が書き込まれ、湿度表示が-10されます。

modbus温湿度表示器XY-MD02の使い方

「ボタンC」を押すと「温度と湿度補正値」に「0」が書き込まれ、温度と湿度はセンサーで測定された値のまま表示されます。

modbus温湿度表示器XY-MD02の使い方

エラー表示は最後に1度確認しているだけなので、その前に発生したエラーは確認できません(手抜きです^^;)
上画像の「0xE2」はタイムアウトのエラーコードで、通信コネクタを抜くと表示されます。

・サンプルプログラム(コピペ)

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

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

#define RX_PIN 16       // シリアル通信端子番号(RX)
#define TX_PIN 17       // シリアル通信端子番号(TX)

M5GFX lcd;              // 直接表示のインスタンスを作成(M5GFXクラスを使ってlcdコマンドでいろいろできるようにする)
M5Canvas canvas(&lcd);  // メモリ描画領域表示(スプライト)のインスタンスを作成(必要に応じて複数作成)
ModbusMaster node1;     // スレーブアドレスNo.1との通信用インスタンスを作成

// 変数宣言
uint8_t result = 0;     // Modbus通信結果取得用
bool error = false;     // エラーフラグ
char buff[20];          // 表示データ格納バッファ
float temp_correction;  // 温度補正値
float hum_correction;   // 湿度補正値

// 関数 -----------------------------------------------------------
/************************** 初期画面表示 **************************/
void displayInit() {
  // 画面初期化
  canvas.fillScreen(BLACK);
  // ライン表示
  canvas.drawFastHLine(0, 0, 320, WHITE);   // タイトルエリア境界線(上)
  canvas.drawFastHLine(0, 29, 320, WHITE);  // タイトルエリア境界線(下)
  canvas.drawRect(0, 100, 320, 2, WHITE);   // 境界線1
  canvas.drawRect(0, 170, 320, 2, WHITE);   // 境界線2
  // タイトル表示
  canvas.setTextSize(1);                    // 文字サイズ倍率
  canvas.setTextColor(WHITE);               // 文字色
  canvas.fillRect(0, 1, 320, 27, M5.Lcd.color565(180, 40, 50));   // タイトルエリア背景
  canvas.drawCentreString("Modbus 温湿度表示器 XY-MD02", lcd.width()/2, 4, &fonts::lgfxJapanGothicP_20);  // タイトル表示
  // 単位表示
  canvas.setTextSize(1.8);                          // 文字サイズ倍率
  canvas.setFont(&fonts::lgfxJapanGothicP_20);      // フォント
  canvas.setCursor(270, 60);   canvas.print("℃");   // 温度単位
  canvas.setCursor(268, 125);  canvas.print("%");  // 湿度単位
}
/************************ 設定値読出し表示 ************************/
void settingRead() {
  // 設定値読出し
  // 0x03:保持レジスタ連続読込み(内部設定データ読取り)
  result = node1.readHoldingRegisters(0x0101, 4);  // (開始アドレス, 読取りレジスタ数)
  if (result == 0) {    // 通信成功なら
    uint16_t data[4];   // 取得データ格納用
    for (int i = 0; i < 4; i++) {
      data[i] = node1.getResponseBuffer(i);  // 受信データバッファ取得
    }
    // 補正値格納(正負の値に変換)
    temp_correction = (short int)data[2];  // 温度補正値
    hum_correction = (short int)data[3];   // 湿度補正値
    
    // 内部設定データ表示
    canvas.setTextSize(1);                    // 文字サイズ倍率
    canvas.fillRect(0, 175, 320, 65, BLACK);  // 前回表示消去(塗潰し四角)
    canvas.setTextColor(WHITE);               // 文字色
    canvas.setFont(&fonts::Font4);            // フォント
    canvas.setCursor(0, 178);                 // 表示座標
    canvas.printf("No.%02d    baud rate: %dbps", data[0], data[1]); // スレーブアドレス、通信速度表示
    canvas.setCursor(62, 205);                // 表示座標
    canvas.printf(":temp %.0f'c / hum %.0f%%", temp_correction, hum_correction);  // 温度、湿度補正値
    canvas.setCursor(0, 205);                 // 表示座標
    canvas.setFont(&fonts::lgfxJapanGothicP_20);  // フォント
    canvas.print("補正値");                    //
  }
}
// 初期設定 ------------------------------------------------------
void setup() {
  M5.begin(true, true, true, false);  // 本体初期化(LCD, SD, Serial, I2C)
  // Modbus通信初期設定(SERIAL_8N1:データビット幅[8]、パリティチェック[No]、ストップビット[1])
  Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);  // シリアル通信2 初期化(RX, TX)
  node1.begin(1, Serial2);                          // シリアル通信2を介してスレーブ1と通信
  // 液晶画面初期設定
  lcd.begin();                                    // ベース画面初期化
  // メモリ描画領域の初期設定(スプライト)
  canvas.setColorDepth(8);                        // カラーモード設定
  canvas.createSprite(lcd.width(), lcd.height()); // メモリ描画領域を画面サイズで準備

  displayInit();  // 初期画面表示
  settingRead();  // 設定値読出し表示
}
// メイン処理 ----------------------------------------------------------
void loop() {
  M5.update(); // ボタン状態初期化

  // 温度、湿度データ取得
  // 0x04:入力レジスタ連続読込み(温度、湿度データ読み取り)
  result = node1.readInputRegisters(0x0001, 2);   // (開始アドレス, 読取りレジスタ数)
  if (result == 0) {                              // 通信成功なら
    float data[2];                                // 取得データ格納用
    // Modbusレスポンスデータ取得
    for (int i = 0; i < 2; i++) {                 // 読取りレジスタ分繰返す
      data[i] = node1.getResponseBuffer(i);       // 受信データバッファ取得
    }
    // 測定値換算(取得値1/10 + 補正値)
    data[0] = (data[0] / 10.0) + temp_correction; // 温度データ換算
    data[1] = (data[1] / 10.0) + hum_correction;  // 湿度データ換算
    // 取得データ換算表示
    canvas.setTextSize(0.8);                                // 文字サイズ倍率
    // 温度(℃)換算値表示
    canvas.fillRect(0, 35, 220, 65, BLACK);                 // 前回表示消去
    sprintf(buff, "%5.1f", data[0]);                        // 数値を文字列に変換
    canvas.setTextColor((uint16_t)0xaf7d , BLACK);          // 文字色
    canvas.drawRightString(buff, 220, 35, &fonts::Font8);   // 文字表示(右揃え)
    // 湿度(%)換算値表示
    canvas.fillRect(0, 105, 220, 65, BLACK);                // 前回表示消去
    sprintf(buff, "%5.1f", data[1]);                        // 数値を文字列に変換
    canvas.setTextColor((uint16_t)0x07ef , BLACK);          // 文字色
    canvas.drawRightString(buff, 220, 105, &fonts::Font8);  // 文字表示(右揃え) 
  }
  // ボタン操作(設定値書込み)
  // 0x06:保持レジスタ単独書き込み(補正値単独書込み)
  if (M5.BtnA.wasPressed()) {                           // AボタンONなら温度補正値書込み
    result = node1.writeSingleRegister(0x0103, 0x000a); // 単独書込み実行(アドレス, 書込む値[10])
    settingRead();                                      // 設定値読み出し表示
  }
  if (M5.BtnB.wasPressed()) {                           // BボタンONなら湿度補正値書込み
    result = node1.writeSingleRegister(0x0104, 0xFFF6); // 単独書込み実行(アドレス, 書込む値[-10])
    settingRead();                                      // 設定値読み出し表示
  }
  // 0x10:保持レジスタ連続書き込み(補正値連続書込み)
  if (M5.BtnC.wasPressed()) {                               // CボタンONなら温湿度補正値連続書込み
    node1.setTransmitBuffer(0, 0x0000);                     // 書込む値[0]を準備
    node1.setTransmitBuffer(1, 0x0000);                     // 書込む値[0]を準備
    result = node1.writeMultipleRegisters(0x0103, 0x0002);  // 連続書込み実行(アドレス, 書込みレジスタ数n)
    settingRead();                                          // 設定値読み出し表示
  }
  // エラー表示
  if (result != 0) {              // 通信成功以外ならエラー表示
    error = true;                 // エラーフラグをtrueへ
    canvas.fillScreen(RED);       // 背景
    canvas.setTextColor(WHITE);   // 文字色
    canvas.setTextSize(1);        // 文字サイズ倍率
    canvas.drawCenterString("ERORR", lcd.width()/2, 90,  &fonts::Font4);  // 表示内容(中央表示)
    sprintf(buff, "0x%02X", result);                  // 16進数のエラーコードを文字列バッファに準備
    canvas.drawCenterString(buff, lcd.width()/2, 120,  &fonts::Font4);    // エラーコードを表示
  }
  if (result == 0 && error == true) { // 通信成功でエラーフラグがtrueなら画面初期化
    error = false;  // エラーフラグをfalseへ
    displayInit();  // 初期画面表示
    settingRead();  // 設定値読み出し表示
  }
  // メモリ描画領域を座標を指定して一括表示(スプライト)
  canvas.pushSprite(&lcd, 0, 0); // 画面出力実行
  delay(200); // 遅延時間
}

6.まとめ

「M5Stack」でModobus温湿度測定器「XY-MD02」の使い方を「Modbus」通信の方法と合わせて詳しく紹介しました。

シリアル通信の「UART」や「I2C」に対応した機器は多いですが「Modbus」に対応した機器は産業用途が多く、個人で入手できるものは限られるため、習得の機会が少ないように思います。

今回使用した「XD-MD02」はAmazonでも入手できる数少ない「Modbus」対応機器の1つです。
価格も安価で、レジスタ構成もシンプルなため「Modbus」通信の理解には最適と思います。

実際に工場等では、その信頼性の高さや、設置の容易さ、通信距離の長さから、多くの機器が「Modbus」通信に対応しているため、そっち方面の職種を目指す方は事前に身につけておくと役に立つと思います。

今回は「入力レジスタ」と「保持レジスタ」の操作しか紹介できませんでしたが、他にもスレーブデバイス内のON/OFFを制御する「コイル」や、センサ入力等のON/OFF状態を確認する「入力ステータス」等があります。

次回はこれらについて「4チャンネルのリレーユニット」を使用して、スレーブアドレスの変更方法と合わせて、実際に動作確認しながら詳しく紹介したいと思います。

M5Stack Modbus 485リレー4CHの使い方
M5StackでModbus(Arduinoライブラリ)を使用した4CHリレーの使い方を詳しく紹介。2本の配線だけで4つのリレーをPLCのように制御してみましょう。

コメント

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