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();
}