実験 RS-485 (2) ArduinoでRS-485に対応する その1 電力量モニタKM-N1-FLK

 Arduino UNOには0、1ピンを利用するUARTと任意のピンが使えるソフトウェア・シリアルがあります。どちらもTTLレベルの全二重通信が物理層でできます。物理層とは電線を接続する部分です。ここにRS-485の変換ICを接続して使うと、RS-485のネットワークにつないで通信ができます。物理層の上で利用の多いと思われる通信プロトコルがModbusです。

 Arduino UNOがマスタになり、1:N のスレーブと通信ができます。スレーブは最大32個で、ツイスト・ペア線でつないでいきます。ツイスト・ペア線とは、2本の電線をよじったもので、専用の製品もありますがLANケーブルの内部に使われているので、古いケーブルから取り出して実験に利用できます。外来ノイズはツイスト・ペア線の両方を貫通するので、信号が差動で送られているためノイズはキャンセルされます。

 Modbusはマスタから通信を開始し、スレーブがそれに応答するという形で通信プロトコルが作られています。

Modbusのライブラリ

 Arduino IDEでライブラリにModbusがあるかを検索します。二つ見つかりました。

 ModbusMasterをインストールすると、サンプルにModbusMaster-RS485_HalfDuplexがあるので、読み込みます。

#define MAX485_DE 3
#define MAX485_RE_NEG 2

 半二重の方向を切り替える信号は独立して2番と3番ピンに割り当てています。GitHubにあるドキュメントを見ると、

void setup()
{
// use Serial (port 0); initialize Modbus communication baud rate
Serial.begin(19200);
// communicate with Modbus slave ID 2 over Serial (port 0)
node.begin(2, Serial);
}

 0、1番のUARTをコンソールのシリアル通信と共用しているように見えます。

接続するのはオムロンの小型電力量モニタKM-N1-FLK

 KM-N1-FLK は、最大で四つの交流電流センサCTをつなげられるコンパクトな形状をした電力計です。RS-485でアクセスできます。CTは5A/50Aの最小容量KM-NCT-5A/50Aを、接続ケーブル KM-NCB-1Mをアマゾンで入手しました。測定を兼ねているAC100Vをつなげることで、別途電源は不要です。

 Windows用設定アプリケーション「KM-N Setting Tool Ver.1.0」が利用できるので、USB-RS-485ケーブル・コンバータを購入しました(CH340のドライバが必要)。

 次の接続で設定アプリケーションを動かしました。KM-N1-FLK は100Vへつないであります。終端抵抗の120Ωはスレーブの端に取り付けるのだと思いますが、距離が30cmほどなので影響はないだろうと、このときはUSB側に取り付けました。あとで、KM-N1-FLK側に終端抵抗は移動しました。

 KM-N1-FLK の電源を入れ、USB-RS-485ケーブル・コンバータをPCのUSBハブに挿しこみ、設定アプリケーションを立ち上げます。COM portをプルダウンから選びます。COM13が出てきたので選択しました。Comunication Startをクリックします①。StatusがOnlineになりました。

 Search serial number②をクリックすると、KM-N1-FLK を探し出しました③。

 Readをクリックすると、ID=01を読み出しました。

 デフォルトのプロトコルがOMRONのCompoWay/Fになっていたので、プルダウンからModbunRTU①を選び、Write②で書き込みました。いったん接続を切って再度つなげると、ModbunRTUに変更されていることが確認できました。

 KM-N1-FLK からReadした内容は、CSV OutputでCSVファイルで書き出せます。

 CSVの内容の一部です。

 ParityをEvenからNonに変更はできましたが、そうするとStop bitが2から変更できませんでした。

Arduinoからアクセス

 Arduino UNOとアマゾンで購入したMAX485 RS-485 TTL-RS-485モジュールコンバータ、小型電力量モニタKM-N1-FLKを次のように接続しました。

 スケッチは、サンプルのRS485_HalfDuplexをベースに一部変更しました。データ長8ビット、パリティなし、ストップ・ビット2は、Serial.begin(9600, SERIAL_8N2);で設定しました。コンソール、RS-485のどちらもこの設定になります。

#include <ModbusMaster.h>
#define MAX485_DE 3
#define MAX485_RE_NEG 2
ModbusMaster node;

void preTransmission()
{
digitalWrite(MAX485_RE_NEG, 1);
digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
}

void setup()
{
pinMode(MAX485_RE_NEG, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
// Init in receive mode
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
Serial.begin(9600, SERIAL_8N2);
node.begin(1, Serial); // Modbus slave ID 1
// Callbacks allow us to configure the RS485 transceiver correctly
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
}

bool state = true;

void loop()
{
uint8_t result;
uint16_t data[6];

result = node.readHoldingRegisters(0x0000,2);
for (int j = 0; j < 6; j++)
{
data[j] = node.getResponseBuffer(j);
Serial.print(data[j]);
}
Serial.println(" ");
delay(1000);
}

 サンプルでの読み出し(0x0000は電圧値)は、

result = node.readInputRegisters(0x0000,2);

でしたが、下図のようにファンクションが04です。OMRONのは03が読み出しファンクションなので、上記のようにreadHoldingRegistersを使いました。0x0000は電圧1が保存されているアドレスで、10倍の値が読み出せます。

Modbus のプロトコル

 ModbusプロトコルにはASCIIとRTU(バイナリ)モードの2種類がありますが、多くはRTUモードを利用しているようです。マスタからスレーブに対してクエリを送り、当該スレーブから応答メッセージを受け取ります。

 クエリは次のメッセージ・フレームから構成されていて、

  ユニット番号ファンクション・コードデータCRC-16

の順番でデータを送ります。ユニット番号(上記のID=01)の前には3.5文字分の無通信時間が確保しますが、ライブラリ内もしくはpreTransmission/postTransmissionで処理がされると思われます。

 ユニット番号は、機器の購入時にデフォルトでは1番になっていることが多いようです。複数台つなぐときは、ユーティリティなどを利用して変更し重ならないようにします。

 ファンクション・コードは複数あります。KM-N1-FLKでは次の四つがサポートされています。

  • 0x03 変数読み出し(連続)
  • 0x10 変数書き込み(連続)
  • 0x06 変数書き込み(単一)
  • 0x08 エコーバック・テスト

 データは、2バイト単位のようです。ユニット番号からデータの最後までをCRC-16で計算して2バイトのデータとして最後につけます。CRC-16はライブラリが計算して追加してくれます。

 変数読み出し時、データには変数の収納されているアドレスを指定します。スレーブ・デバイスによって、アドレスとその内容は異なります。KM-N1-FLKには、約22のアドレスがあります。代表的なアドレスとその内容を示します。

アドレス 項目 モニタ値
0x00 電圧1[V] 10倍の値
0x02 電圧2[V] 10倍の値
0x04 電圧3[V] 10倍の値
0x06 電流1[A] 1000倍の値
0x08 電流2[A] 1000倍の値
0x0a 電流3[A] 1000倍の値
0x0c 力率 100倍の値
0x0e 周波数[Hz] 10倍の値
0x20 積算有効電力量[Wh] 1倍の値
0x22 積算回生電力量[Wh] 1倍の値 

 応答メッセージの構成です。データを複数要求していたら、バッファから2バイトずつ必要分読み出します。

  ユニット番号ファンクション・コードデータ(データ・バイト数、データ1の上位、データ1の下位、...)、CRC-16

Modbus ライブラリのファンクション

◆Discrete Coils/Flags( DO(Discrete Output)/DI(Discrete Input)のON/OFF状態を読んだり変更する)

  • 0x01 - Read Coils
  • 0x02 - Read Discrete Inputs
  • 0x05 - Write Single Coil
  • 0x0F - Write Multiple Coils

◆アドレスへの読み書き

  • 0x03 - Read Holding Registers
  • 0x04 - Read Input Registers
  • 0x06 - Write Single Register
  • 0x10 - Write Multiple Registers
  • 0x16 - Mask Write Register
  • 0x17 - Read Write Multiple Registers

はんだゴテの電流変化

 次のようにCTをはんだゴテの一方のラインに通し、「電流1」の値を読み出します。はんだゴテは、温度調節付きのFX600です。伝送エラー時の処理などは省略しています。

#include <ModbusMaster.h>
#define MAX485_DE 3
#define MAX485_RE_NEG 2
ModbusMaster node;

void preTransmission() {
digitalWrite(MAX485_RE_NEG, 1);
digitalWrite(MAX485_DE, 1);
}

void postTransmission() {
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
}

void setup() {
pinMode(MAX485_RE_NEG, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
// Init in receive mode
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
// Modbus slave ID 1
Serial.begin(9600, SERIAL_8N2);
node.begin(1, Serial);
// Callbacks allow us to configure the RS485 transceiver correctly
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
}

void loop() {
uint8_t result;
result = node.readHoldingRegisters(0x0006,2);
Serial.println( node.getResponseBuffer(1));
delay(1000);
}

 測定結果です。はんだゴテをつないだ直後は1Aの電流が流れますが、その後300mAのON/OFFを繰り返して温度調節が行われています。

前へ

実験 RS-485 (1) RS-232

次へ

実験 RS-485 (3) ArduinoでRS-485に対応する その2 熱電対ADAM-4011