Arduino Modbus RTU 相互通信 2019/09/23
2 台の Arduino を RS-485 で接続し、Modbus RTU で相互の入出力をやり取りします。
DI x 4 点、AI x 2 点を接続先の DO x 4 点、AO(PWM) x 2 点に出力します。
Amazon で販売されている UART-RS485 モジュールは、送受信自動切替タイプと、PIN で切り替えるタイプの2種類があります。
Arduino と RS485 モジュールの接続例はこちらです。(2020/10/14 RX), TX の間違いを修正)
Modbus-Master-Slave-for-Arduino をインストールし、
スレーブ側は、examples の advanced_slave.ino をほぼそのまま使用、マスタ側は、examples の advanced_mastar.ino
を元に改変してます。
// マスター側
/*
* 2 台の Arduino を RS-485 で接続し、Modbus RTU で相互の入出力をやり取りします。
* DI x 4 点、AI x 2 点を接続先の DO x 4 点、AO(PWM) x 2 点に出力します。
* 2019/09/23 f.izawa http://www.izawa-web.com/
*
* Modbus-Master-Slave-for-Arduino
* https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino
* スレーブ側は、スケッチ例 advanced_slave.ino をそのまま使用し、
* マスター側は、スケッチ例 advanced_master.ino をスレーブ側に合わせて改変しています。
*/
/**
* Modbus master example 2:
* The purpose of this example is to query several sets of data
* from an external Modbus slave device.
* The link media can be USB or RS232.
*
* Recommended Modbus slave:
* diagslave http://www.modbusdriver.com/diagslave.html
*
* In a Linux box, run
* "./diagslave /dev/ttyUSB0 -b 19200 -d 8 -s 1 -p none -m rtu -a 1"
* This is:
* serial port /dev/ttyUSB0 at 19200 baud 8N1
* RTU mode and address @1
*/
#include <ModbusRtu.h>
#define queryCount 4 //送信メッセージ数
uint16_t au16data[16]; //!< data array for modbus network sharing
uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query
/**
* Modbus object declaration
* u8id : node id = 0 for master, = 1..247 for slave
* u8serno : serial port (use 0 for Serial)
* u8txenpin : 0 for RS-232 and USB-FTDI
* or any pin number > 1 for RS-485
*/
//Modbus master(0, 0, 0); // this is master and RS-232 or USB-FTDI
Modbus master(0, 0, 12); // this is master and RS-232 or USB-FTDI
// スレーブ デバイスへのリクエストを含む構造体
// 送信メッセージ数分の構造体
modbus_t telegram[queryCount];
unsigned long u32wait;
void setup() {
// 送信メッセージ 1
telegram[0].u8id = 1; // スレーブ ID
telegram[0].u8fct = 3; // ファンクションコード (03 = Read Holding Register)
telegram[0].u16RegAdd = 0; // 読み出し先頭データアドレス
telegram[0].u16CoilsNo = 9; // 読み出しデータ数
telegram[0].au16reg = au16data; // データ格納ポインタ au16data[0]~[8]に格納される
// 送信メッセージ 2
// アドレス 2 へ PWM1 を出力
telegram[1].u8id = 1; // スレーブ ID
telegram[1].u8fct = 6; // ファンクションコード (06 = Preset Single Register)
telegram[1].u16RegAdd = 9; // 書き込みスレーブのデータアドレス
telegram[1].u16CoilsNo = 1; // 書き込みデータ数(無視される)
telegram[1].au16reg = au16data + 9; // 書き込みデータのポインタ (au16data[9] のデータを書き込む)
// 送信メッセージ 3
// アドレス 3 へ PWM2 を出力
telegram[2].u8id = 1; // スレーブ ID
telegram[2].u8fct = 6; // ファンクションコード (06 = Preset Single Register)
telegram[2].u16RegAdd = 10; // 書き込みスレーブのデータアドレス
telegram[2].u16CoilsNo = 11; // 無視される
telegram[2].au16reg = au16data + 10; // 書き込みデータのポインタ (au16data[10] のデータを書き込む)
// 送信メッセージ 4
// アドレス 4 へ DI 4 点を出力
telegram[3].u8id = 1; // スレーブ ID
telegram[3].u8fct = 6; // ファンクションコード (06 = Preset Single Register)
telegram[3].u16RegAdd = 11; // 書き込みスレーブのデータアドレス
telegram[3].u16CoilsNo = 1; // 無視される
telegram[3].au16reg = au16data + 11; // 書き込みデータのポインタ (au16data[11] のデータを書き込む)
master.begin( 19200 ); // baud-rate at 19200
master.setTimeOut( 1000 ); // if there is no answer in 5000 ms, roll over
u32wait = millis() + 100;
u8state = u8query = 0;
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT); // PWM
pinMode(11, OUTPUT); // PWM
pinMode(13, OUTPUT); // ボード上の LED
digitalWrite(6, LOW );
digitalWrite(7, LOW );
digitalWrite(8, LOW );
digitalWrite(9, LOW );
analogWrite(10, 0);
analogWrite(11, 0);
digitalWrite(13, HIGH);
}
void loop() {
switch( u8state ) {
case 0:
if (millis() > u32wait) u8state++; // wait state
break;
case 1:
master.query( telegram[u8query]); // send query (only once)
u8state++;
u8query++;
if (u8query >= queryCount) u8query = 0;// 指定メッセージ数送信済で0に戻す
digitalWrite(13, HIGH);
break;
case 2:
master.poll(); // check incoming messages
if (master.getState() == COM_IDLE) {
u8state = 0;
u32wait = millis() + 50;
digitalWrite(13, LOW);
}
break;
}
if (u8query == 0){
//スレーブのデジタル入力 4 点を、マスターのデジタル出力 4 点に出力する
// (au16data[0]0..3) -> (Pin 6..9)
for (int i = 0; i <= 3; i++){
digitalWrite(i + 6, bitRead(au16data[0], i));
}
// スレーブのアナログ入力 2 点を、マスターの PWM 出力 2 点 に出力する
// (au16data[4], au16data[5]) -> (Pin 10, Pin 11)
for (int i = 0; i <= 1; i++){
if (au16data[i + 4] > 1023) au16data[i + 4] = 1023;
uint16_t n = (uint16_t)(au16data[i + 4] / 1023.0 * 255.0);
if (n > 255) n = 255;
analogWrite(i + 10, n); // PWM Pin 10, 11
}
}
if (u8query == 1){
// マスタのアナログ入力 2 点を、スレーブの PWM 出力 2 点に出力する
au16data[9] = (uint16_t)(analogRead(9) / 1023.0 * 255.0);
if (au16data[9] > 255) au16data[9] = 255;
}
if (u8query == 2){
au16data[10] = (uint16_t)(analogRead(10) / 1023.0 * 255.0);
if (au16data[10] > 255) au16data[10] = 255;
}
if (u8query == 3){
//マスタのデジタル入力 4 点を、スレーブのデジタル出力 4 点に出力する
for (int i = 0; i <= 3; i++){
bitWrite( au16data[11], i, digitalRead( 2 + i ));
}
}
}
// スレーブ側
/*
* 2 台の Arduino を RS-485 で接続し、Modbus RTU で相互の入出力をやり取りします。
* DI x 4 点、AI x 2 点を接続先の DO x 4 点、AO(PWM) x 2 点に出力します。
* 2019/09/23 f.izawa http://www.izawa-web.com/
*
* Modbus-Master-Slave-for-Arduino
* https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino
* スレーブ側は、スケッチ例 advanced_slave.ino をそのまま使用し、
* マスター側は、スケッチ例 advanced_master.ino をスレーブ側に合わせて改変しています。
*/
/*
* 日本語コメントはスペイン語を直訳
* 2019/08/21 f.izawa http://www.izawa-web.com/
*/
/**
* Modbus slave example 2:
* The purpose of this example is to link the Arduino digital and analog
* pins to an external device.
*
* Recommended Modbus Master: QModbus
* http://qmodbus.sourceforge.net/
*
* Editado al español por LuxARTS
*/
//Modbus プロトコル ライブラリを含む
#include <ModbusRtu.h>
#define ID 1
// インスタンスの作成
// ノード ID。マスターは 0、スレーブの場合は 1~247
// シリアルポート (0 = TX: 1 - RX: 0)
// シリアル プロトコル。RS-232 + USB (デフォルト) の場合は 0、RS-485 の場合は 1 より大きいピン
//Modbus slave(ID, 0, 0);
Modbus slave(ID, 0, 12);
boolean led;
int8_t state = 0;
unsigned long tempus;
uint16_t au16data[16];
// ネットワーク上で共有するレコードのテーブル
/*********************************************************
プログラムの設定
*********************************************************/
void setup() {
io_setup(); // 入力と出力を構成する
slave.begin(19200); // スレーブとしてのオープンコミュニケーション
tempus = millis() + 100; // 現在の時間 + 100 ミリ秒を節約
digitalWrite(13, HIGH ); // ピン13 LED(ボード上のLED)をオンにする
}
/*********************************************************
プログラムの開始
*********************************************************/
void loop() {
/* 入力バッファを確認する
* パラメータ: 情報交換のレコードテーブル
* テーブルサイズ
* データ順序がない場合は 0 を返します。
* 通信エラーが発生した場合は 1 から 4 を返します。
* 注文が正常に処理された場合は 4 を超える値を返します。
*/
state = slave.poll( au16data, 12 );
// 4 より大きい場合は、順序が正しかった
if (state > 4) {
tempus = millis() + 50; // 現在の時刻 + 50 ミリ秒
digitalWrite(13, HIGH); // ボード上の LED
}
if (millis() > tempus) digitalWrite(13, LOW );//LED を 50 ミリ秒後でオフにする
// Modbus テーブルで Arduino ピンを更新する
io_poll();
}
/**
* pin maping:
* 2 - digital input
* 3 - digital input
* 4 - digital input
* 5 - digital input
* 6 - digital output
* 7 - digital output
* 8 - digital output
* 9 - digital output
* 10 - analog output
* 11 - analog output
* 14 - analog input
* 15 - analog input
*
* 通信の状態を確認するために予約されたピン 13
*/
void io_setup() {
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT); // PWM
pinMode(11, OUTPUT); // PWM
pinMode(13, OUTPUT); // ボード上の LED
digitalWrite(6, LOW );
digitalWrite(7, LOW );
digitalWrite(8, LOW );
digitalWrite(9, LOW );
// ボード上のピン13 LED
digitalWrite(13, HIGH ); //ボード上のピン13 LED
analogWrite(10, 0 ); //PWM 0%
analogWrite(11, 0 ); //PWM 0%
}
/*********************************************************
ログ テーブルをピンにリンクする
*********************************************************/
void io_poll() {
// デジタル入力 -> au16data[0]
// デジタル入力を読み取り、ベクトル内の最初の変数のビットに保存します。
// それはマスクを作るのと同じだ
// Arduinoピン 2~5 を読み取り、変数 au16data[0] のビット 0~3 に保存します。
for (int i = 0; i <= 3; i++){
bitWrite( au16data[0], i, digitalRead( 2+i ));
}
// digital outputs -> au16data[1]
// 2 番目の変数のビットを読み取り、デジタル出力に入れます。
// 可変 au16data[11]のビット0を読み取り、Arduinoのピン6に置く
for (int i = 0; i <= 3; i++){
digitalWrite( 6+i, bitRead( au16data[11], i ));
}
// PWM の値を変更する
// au16data[2]の値は、Arduinoピン10のPWM出力に書き込まれます。
analogWrite( 10, au16data[9] ); //(0=0% ~ 255=100%)
analogWrite( 11, au16data[10] );
// アナログ入力を読む
// ピンA0で読み取ったアナログ値はau16data[4]に保存されます。
au16data[4] = analogRead( 0 ); //(0=0v ~ 1023=5v)
au16data[5] = analogRead( 1 );
// 通信診断 (デバッグ用)
// 受信したメッセージの数を返します。
au16data[6] = slave.getInCnt();
// 送信されたメッセージの数を返します。
au16data[7] = slave.getOutCnt();
// エラーの数を返します。
au16data[8] = slave.getErrCnt();
}