実験 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を繰り返して温度調節が行われています。