「M5Stack」を使ってArduinoコマンドで「Modbus」通信を使用した「温湿度表示器 XY-MD02」の使い方を詳しく紹介します。
「Modbus」の動作確認をしたくて、Amazonで探したら一番簡単に入手できたので、仕様や通信コマンド等を調べてまとめました。これを使って「Modbus」の通信方法についても確認していきます。
「Modbus」通信と聞くと少し敷居が高かったですが、わかってしまえば意外と簡単で便利なものなので、ぜひチャレンジしてみましょう♪
動作確認には「M5Stack Basic Ver2.6」を使用しました。
「M5Stack Basic」の使い方や開発環境の準備については以下のリンクで詳しく紹介しています。
「Mosbus」通信を使用したリレーユニットの使い方は以下のリンクで詳しく紹介しています。
1.Modbusとは
2.温湿度測定器 XY-MD02 について
・基本仕様
・外観、端子配列
・通信仕様一覧表
3.通信方法の確認準備
・準備するもの
・配線図
・使用ライブラリ
4.基本的な通信方法の確認
・動作紹介
・通信確認用サンプルプログラム(コピペ)
・初期設定詳細
・温度、湿度データの取得方法(入力レジスタ読込み:0x04)
・内部設定値の取得方法(保持レジスタ読込:0x03)
・内部設定値の単独書込み方法(保持レジスタ単独書込み:0x06)
・内部設定値の連続書込み方法(保持レジスタ連続書込み:0x10)
5.完成した温湿度表示器
・動作紹介
・サンプルプログラム(コピペ)
6.まとめ
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」の外観は下画像のようになります。
4極のコネクタ(緑)が付属していて抜き差し可能です。
端子配列は本体表面の銘板に書かれている通りコネクタの左から「B-, A+, 電源ー, 電源+」です。
・通信仕様一覧表
通信仕様については下表のようになります。
Modbus通信では「ファンクションコード」によって通信内容が異なります。
「ファンクションコード」はいくつかありますが、この温湿度表示器では4つのみです。
よく使うものが多いので動作確認にはちょうどいいと思います。
温度と湿度のデータ測定だけなので、使用できるコマンドも限られていてとてもシンプルです。
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の変換モジュールは、使用するものに合わせて配線してください。
「M5Stack用RS485ユニット」を使用した場合の配線図は下画像になります。(アイキャッチ画像の配線です。)
・使用ライブラリ
使用したライブラリは「ModbusMaster」です。
Modbus通信用のライブラリはたくさんありますが「ArduinoIDE」のライブラリマネージャで検索した場合は、下画像の「Doc Walker」さんのライブラリです。
お使いの開発環境で検索してインストールしてください。
「M5GFX」ライブラリを使用した液晶表示方法については、以下のリンクで詳しく紹介しています。
4.基本的な通信方法の確認
サンプルプログラムを使用して、基本的な通信方法の確認を行っていきます。
・動作紹介
通信確認用サンプルプログラムを書き込んで実行したものは下画像のようになります。
液晶画面の上から「温度」「湿度」1行開けて、内部設定値の「スレーブアドレス番号」「通信速度(ボーレート)」「温度補正値」「湿度補正値」が表示されます。
「ボタンA」を押すと「温度補正値」に「10」が書き込まれ、温度表示が+10されます。
「ボタンB」を押すと「湿度補正値」に「-10」が書き込まれ、湿度表示が-10されます。
「ボタン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」として使用できるようにします。
「8行目」では通信結果を格納するための変数「result」を宣言します。
通信成功の場合は「0」が格納されるため「0以外」の場合は通信エラーとなります。
エラー時には格納されたエラー番号を確認できるため、エラーの原因を探ることができます。
「15,16行目」で「Modbus」通信を行うためのシリアル通信の初期設定を行います。
「SERIAL_8N1」で通信仕様を設定します。
今回は「データビット幅[8bit]、パリティチェック[No(なし)]、ストップビット[1bit]」としています。必要に応じて書き換えます。
シリアル通信の端子番号は「Serial2」の標準端子「RX=16/TX=17」を指定しています。
最後に「17行目」で以下のように指定して「node1」でModbus通信を開始します。
- スレーブアドレス:通信対象機器のスレーブアドレスを指定。今回は「1」
- 使用するシリアル通信:Modbus通信に使用するシリアル通信名を指定。今回は「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手順でデータを取得できます。
- 「2行目」でスレーブアドレスを指定して、データをリクエストするコマンドを送信すると対象のスレーブ機器からレスポンスとしてデータが送信されてきます。
- 「6行目」で送信されてきたデータを取得して変数に格納しています。
「2行目」の詳細は以下のようになります。
- node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
- readHoldingRegisters:Modbus通信のファンクションコード「0x04」を表し「入力レジスタ」からデータを取得するためのコマンドです。
「通信仕様一覧表」によると、温度と湿度は「入力レジスタ」に格納されているため、このコマンドを使用します。
連続したレジスタのデータであれば、まとめて取得することができます。 - 開始アドレス:取得したいデータが格納されているアドレスを指定します。
「通信仕様一覧表」によると、「温度」測定値はアドレス「0x0001」に、「湿度」測定値はアドレス「0x0002」に格納されています。
このように連続したレジスタに格納されているデータは「開始アドレス」の「0x0001」を指定して次の「読み取りレジスタ数」を指定することで、連続して取得することができます。 - 読み取りレジスタ数:取得したいデータのレジスタ数を表します。1つであれば「1」ですが、今回は連続した2つのレジスタのデータを取得したいため「2」を指定します。
- result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
「6行目」の詳細は以下のようになります。
連続してデータを取得するために「for文」を使用しています。
data[i] = node1.getResponseBuffer(i); // 受信データバッファ取得
}
- node1.getResponseBuffer(i):これでスレーブから送信されてきたデータを取得できます。
連続したデータは配列として取得して使用します。 - data[i]:取得したデータを格納する配列です。
受信するデータのバイト数に合わせて準備しておく必要があります。
「通信仕様一覧表」によると「入力レジスタ」の「データByte数」は「2byte」のため「16bit」以上の型で準備しておく必要があります。
今回は、この後のデータ換算のために「float型」としました。
送受信データ構成について(入力レジスタの読込み時)
温度、湿度の測定値を取得するために以下を実行したときに、実際に送受信されるデータについて確認します。
// 0x04:入力レジスタ連続読込み(温度、湿度データ読み取り)
result = node1.readInputRegisters(0x0001, 2); // (開始アドレス, 読取りレジスタ数)
送信されるデータは16進数で表すと以下のようになります。
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(入力レジスタ連続読出し)
3byte目:レジスタアドレス上位
4byte目:レジスタアドレス下位
5byte目:読出しレジスタ数上位
6byte目:読出しレジスタ数下位
7,8byte目:送信データから生成されたエラーチェック用の「CRCコード」
スレーブ側から送信されてくる受信データは以下のようになります。
※*印部は測定データによって変動します。
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(入力レジスタ連続読出し)
3byte目:データバイト数
4byte目:温度データ上位
5byte目:温度データ下位
6byte目:湿度データ上位
7byte目:湿度データ下位
8,9byte目:受信データから生成されたエラーチェック用の「CRCコード」
・内部設定値の取得方法(保持レジスタ読込: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手順でデータを取得します。
- 「2行目」でスレーブアドレスを指定して、データをリクエストするコマンドを送信すると対象のスレーブ機器からレスポンスとしてデータが送信されてきます。
- 「6行目」で送信されてきたデータを取得して、変数に格納しています。
「2行目」の詳細は以下のようになります。
- node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
- readHoldingRegisters:Modbus通信のファンクションコード「0x03」を表し「保持レジスタ」からデータを取得するためのコマンドです。
「通信仕様一覧表」によると、「スレーブアドレス」等の設定値は「保持レジスタ」に格納されているため、このコマンドを使用します。
連続したレジスタのデータであれば、まとめて取得することができます。 - 開始アドレス:取得したいデータが格納されているアドレスを指定します。
「通信仕様一覧表」によると、「スレーブアドレス」等の設定値は、アドレス「0x0101」から4つ連続したレジスタに格納されているため「開始アドレス」には「0x0101」を指定しています。 - 読み取りレジスタ数:取得したいデータのレジスタ数を表します。1つであれば「1」ですが、今回は連続した4つのレジスタのデータを取得したいため「4」を指定しています。
- result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
「6行目」の処理は、取得するデータ数が4つになっただけで、温度、湿度データの取得の時と同じです。
送受信データ構成について(保持レジスタ連続読取り時)
内部設定値を取得するために以下を実行したときに、実際に送受信されるデータについて確認します。
// 0x03:保持レジスタ連続読込み(内部設定データ読取り)
result = node1.readHoldingRegisters(0x0101, 4); // (開始アドレス, 読取りレジスタ数)
送信されるデータは16進数で表すと以下のようになります。
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(保持レジスタ連続読出し)
3byte目:開始アドレス上位
4byte目:開始アドレス下位
5byte目:読出しレジスタ数上位
6byte目:読出しレジスタ数下位
7,8byte目:送信データから生成されたエラーチェック用の「CRCコード」
スレーブ側から送信されてくる受信データは以下のようになります。
※スレーブアドレス「1」通信速度「9600bps」各補正値「0」の場合のデータです。
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(保持レジスタ連続読出し)
3byte目:データバイト数
4byte目:スレーブアドレス上位
5byte目:スレーブアドレス下位
6byte目:通信速度(ボーレート)上位
7byte目:通信速度(ボーレート)下位
8byte目:温度補正値上位
9byte目:温度補正値下位
10byte目:湿度補正値上位
11byte目:湿度補正値下位
12,13byte目:受信データから生成されたエラーチェック用の「CRCコード」
・内部設定値の単独書込み方法(保持レジスタ単独書込み: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つのレジスタの値を変更しています。
単独書込みコマンドの詳細は以下のようになります。
- node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
- writeSingleRegister:Modbus通信のファンクションコード「0x06」を表し「保持レジスタ」の内容を単独書込みするためのコマンドです。
「通信仕様一覧表」によると「保持レジスタ」には4つのデータが格納されているため、個別に変更したい場合はこのコマンドを使用します。 - アドレス:書込み対象の「保持レジスタ」のアドレスを指定します。
- 書込みデータ:書込みデータを指定します。
- result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
送受信データ構成について(保持レジスタ単独書込み時)
「温度補正値」を単独で書込むために以下を実行したときに、実際に送受信されるデータについて確認します。
// 0x06:保持レジスタ単独書き込み(補正値単独書込み)
result = node1.writeSingleRegister(0x0103, 0x000a); // 単独書込み実行(アドレス, 書込む値[10])
送信されるデータは16進数で表すと以下のようになります。
※温度補正値として「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行目」で連続書込みを実行して、連続した複数のレジスタの値をまとめて変更しています。
連続書込みコマンドの詳細は以下のようになります。
result = node1.writeMultipleRegisters(開始アドレス, 書込みレジスタ数);
- node1:スレーブアドレス「1」との通信を表します。(アドレスは初期設定で設定しておく)
- setTransmitBuffer:複数ある送信データは、このコマンドで事前に送信データバッファとして準備しておきます。
- 送信データバッファ番号:書込むために送信するデータ番号を「0」から順番に指定します。
- 書込みデータ:書込むデータを「送信データバッファ番号」ごとに指定します。
- writeMultipleRegisters:Modbus通信のファンクションコード「0x10」を表し「保持レジスタ」の内容を連続書込みするためのコマンドです。
「通信仕様一覧表」によると「保持レジスタ」には4つのデータが格納されているため「開始アドレス」と「書込みレジスタ数」を指定することで、連続して値を変更することができます。 - 開始アドレス:連続書込み対象の「保持レジスタ」の最初のレジスタアドレスを指定します。
- 書込みレジスタ数:連続書き込みを行うレジスタ数を指定します。
- result:通信結果が格納されます。通信成功の場合は「0」で「0以外」は通信エラーです。
送受信データ構成について(保持レジスタ連続書込み時)
「温度補正値」と「湿度補正値」を連続で書込むために以下を実行したときに、実際に送受信されるデータについて確認します。
node1.setTransmitBuffer(0, 0x0000); // 書込む値[0]を準備
node1.setTransmitBuffer(1, 0x0000); // 書込む値[0]を準備
result = node1.writeMultipleRegisters(0x0103, 2); // 連続書込み実行(アドレス, 書込みレジスタ数)
送信されるデータは16進数で表すと以下のようになります。
※各補正値としてそれぞれ「10」を書き込んだ場合
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(保持レジスタ連続書込み)
3byte目:開始アドレス上位
4byte目:開始アドレス下位
5byte目:書込みレジスタ数上位
6byte目:書込みレジスタ数下位
7byte目:データバイト数
8byte目:送信データバッファ番号「0」の書込みデータ上位
9byte目:送信データバッファ番号「0」の書込みデータ下位
10byte目:送信データバッファ番号「1」の書込みデータ上位
11byte目:送信データバッファ番号「1」の書込みデータ下位
12,13byte目:送信データから生成されたエラーチェック用の「CRCコード」
データ構成は左から
1byte目:スレーブアドレス
2byte目:ファンクションコード(保持レジスタ連続書込み)
3byte目:スレーブアドレス上位
4byte目:スレーブアドレス下位
5byte目:書込みレジスタ数上位
6byte目:書込みレジスタ数下位
7, 8byte目:受信データから生成されたエラーチェック用の「CRCコード」
5.完成した温湿度表示器
動作確認用のサンプルプログラムの動作では見た目が寂しいので、液晶表示部もそれなりに作成して以下のように完成させました。
通信部分は同じ内容なので、自分好みの温湿度表示器を作ってみましょう。
・動作紹介
表示内容は通信確認用のサンプルプログラムの時と同じです。
「M5GFX」ライブラリを使用して日本語表示にしてわかりやすくしました。
「スレーブアドレス」と「ボーレート(通信速度)」も無いと寂しいので表示させてます。
「補正値」については個人的には必要性は感じられませんがせっかくなので残しました。
以下、通信確認の時と同じ動作です。
「ボタンA」を押すと「温度補正値」に「10」が書き込まれ、温度表示が+10されます。
「ボタンB」を押すと「湿度補正値」に「-10」が書き込まれ、湿度表示が-10されます。
「ボタンC」を押すと「温度と湿度補正値」に「0」が書き込まれ、温度と湿度はセンサーで測定された値のまま表示されます。
エラー表示は最後に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チャンネルのリレーユニット」を使用して、スレーブアドレスの変更方法と合わせて、実際に動作確認しながら詳しく紹介したいと思います。
コメント