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