Future Tech Blog
フューチャー技術ブログ

産業用機器向けフィールドネットワークModbusで温湿度センシング


フューチャー夏休みの自由研究連載の1回目です。

はじめに

TIG DXユニット真野です。三菱系のPLCと通信するMCプロトコルはちょっと飛び道具的な持ちネタの1つですが、同様にメジャーな規格であるModbusについて自由研究という名の調査をしました。

工場、倉庫、プラントなどのIoT化を行うと、Web周りに無い技術も触れて楽しいなって最近思ってます。

Modbusとは

ModbusはModicon社が1979年、同社のプログラマブルロジックコントローラ (PLC) 向けに策定したシリアル通信プロトコルである。産業界におけるデ・ファクト標準の通信プロトコルとなり、現在では産業用電子機器を接続する最も一般的手段となっている。
https://ja.wikipedia.org/wiki/Modbus - WikipediaのModbusページ

Wikipediaの説明が分かりやすいので引用しました。

書いてあるようにModbus(モドバス)とは通信プロトコルです。由来はModicon社から来ているのでしょう。通信プロトコルとあるように、WebでいうHTTPのようなものです。サーバ・クライアントモデルで、Modbusでセンサーモジュールにリクエストを投げると、温度・湿度がレスポンスで取得できるようなものです(後述します)。

PLCというのは工場の生産設備で用いる機器のことです。製造現場のFA(ファクトリーオートメーション)に使われるシーケンス制御を行う装置です。

シリアル接続はWeb系のエンジニアには馴染みが薄い気がします。簡単に言う1本だけの通信線を使い、1/0の電圧レベルを連続的(=シリアル)に変化させて、意味のあるデータを送信/受信する通信方式です。USBとか、自作PCをする人にとってはシリアルATAが有名でしょうか。工場系の人にとっては、RS-232CとかRS485が有名な気がします。

Modbusの使い所

利用用途がPLC接続だけだとすると「オレには関係無いな」ってなりそうですが、Modbusはもう少し広く利用されています。掲題の温湿度センサーや、カメラモジュール、産業用ロボットの制御などでも利用されています。

いやいや、今どきはラズベリーパイなどのシングルボードで完結させることが増えているよってIoT界隈のエンジニアに言われそうですが、倉庫や工場などで導入すると、高い可用性が求められる(5年10年は故障しないとか)ため、こういった産業用の機器の選定を行うこともあると思います。そうすると必然、Modbusのような通信規格を抑える必要がでてきます。ラズパイにセンサーモジュール接続で完結できるならそっちが良いと僕もそう思います。

MCプロトコルの違いですが、MCプロトコルもSLMP (Seamless Message Protocol)という形で標準化されているのですが、Modbusはそれより古くからあり、仕様もシンプルなため、センサーやカメラやロボットなどのセンサー・アクチュエータにも搭載されていることが一つの差かなと思います。

(最近だとOPC-UAですかね。まだ私は触ったことがないです。個人ユースで1万円以下で手に入るOPC-UA機器がありましたらどなたか教えて下さい)

Modbusプロトコル

Modbusはサーバ・クライアントモデルです。

そのためクライアント側がセンサー値の取得やアクチュエータの操作を行います。クライアントが処理の管理役なため、クライアント側をModbusのマスター、サーバ側をModbusのスレーブとも呼びます。Modbusの親玉に複数の子機をぶら下げて一連の制御をするイメージがよく出てくる気がします。この記事ではマスターをマネージャ、スレーブをワーカと呼び替えて記載します。

Modbusには大きく分けて3つの通信方式があります。先ほどシリアル接続のプロトコルだよって言いましたが、TCP/IP対応しているバージョンもあります。

  1. Modbus RTU: シリアル接続で、バイナリ表現
  2. Modbus ASCII: シリアル接続で、ASCIIフォーマット。RTUに比べてデータサイズが増えるが、人間に読みやすい
  3. Modbus/TCP: TCP/IPを用いる

状況次第かと思いますが、Modbusワーカーそのものを開発している方はともかく、対応機器を利用する分にはシリアル接続の場合は性能上の理由から ModbusRTUを選ぶことが多いと思います。

メッセージフレーム

Modbus ASCIIのメッセージフレーム(HTTPでいうと、HTTPリクエストのペイロード)は以下のような属性を持ちます。Modbus RTUはバイナリフレームのため詳細はここでは割愛しますが、ブロックの構造は同じです。


※PI–MBUS–300 Rev. Jより

START移行の、利用可能な文字は 0-9, A-Fからの文字列のみです。最後はCRLFで終わります。

  • START: 1文字で:(0x3A)
  • ADDRESS: 2文字。マネージャが要求するワーカーアドレス(1~247)。0 はブロードキャスト。
  • FUNCTION: 2文字。要求の種類を示します。下記に示します。1~255が設定可能。どのファンクションコードがが有効化はワーカデバイスに依存
  • DATA: N文字で可変。ファンクションごとにデータフォーマットが決まります。
  • LRC CHECK: 2文字。水平パリティチェック(Longitudinal Redundancy ChecK)のこと。転送時にノイズによるビットが反転していないかを受信側がチェックできるようにするもの
  • 改行: CRLF

ファンクションコード

PI–MBUS–300 Rev. Jに記載されていたコードは全部で24つありました。コードを上から3つ抜粋します。

CODE NAME Note
01 Read Coil Status ワーカのDO(Discrete Output)のON/OFFステータス読み取り
02 Read Input Status ワーカのDI(Discrete Input)のON/OFFステータス読み取り
03 Read Holding Registers ワーカの保持レジスタの内容を読み取り
以下省略。

DATA部について03の例で説明します。

Read Holding RegistersDATA は保持レジスタの開始アドレスと保持レジスタの数を指定します。

  • START: 1文字
  • FUNCTION: 2文字。Read Holding Registers03になる
  • DATA: 03の場合は以下4つを指定
    • 開始アドレス(Hi): Hi+Loなので、201を指す場合は Hiに00、LoにC8を指定。開始アドレスは1少ない
    • 開始アドレス(Lo): 省略
    • 取得数(Hi): Hi+Loなので、3点取得する場合は、Hiを00, Loに03を指定
    • 取得数(Lo): 省略
  • LRC CHECK: 2文字
  • 改行

まとめると、Modbusワーカ番号1にRead Holding Registersで、レジスタ201から3点取得する場合は、
:0300C8003とLRCチェックとCRLFで1要求になります。

応答もほぼ似たような形式で、以下の用に指定されたレジスタ値が取得できます。

  • START: 1文字
  • FUNCTION: 2文字。Read Holding Registers03になる
  • Byte Count: バイト数
  • DATA1: 取得データ1
  • DATA2: 取得データ2
  • DATA3: 取得データ3
  • LRC CHECK: 2文字
  • 改行

実機を触ってみる

Amazonで探した中で一番安く買えたModbus-RTUセンサー KKmoonRS485温度湿度 トランスミッタ です。商品名がModbus-TRUってなっていますがtypoなので安心ください。先ほどまではModbus-Asciiで説明していましたが、ここからはModbus-RTUでバイナリ表現になります。

https://www.amazon.co.jp/dp/B07KS3WJRM

この真ん中で光っているのがセンサーです。

今回はシリアル接続にチャレンジしたかったので、RS485からUSBに変換するこのあたりの変換アダプタを追加で購入して、Win機からUSBでこのセンサーに接続しています。

あと、電源ですが範囲がDC8V〜48Vということで、USBからだと給電できないのでACアダプターDCジャックのDIP化キット を買って、キットの方をハンダ付けして、ブレッドボードで通電させています。ブレッドボード以外はジャンパー線とドライバーがあれば後はどうにかなるともいます。(抵抗とか不要)。この辺の機材調達は弊社の電子工作部の方に色々教えてもらいました。

KKmoonRS485センサーの接続情報ですが仕様書などが入っていなく、こちらのレビューが正直一番参考になりました。

まずは既存のツールを使ってセンサー値を取得してみましょう。

色々な種類があるようですが、以下のツールが一番使い勝手が良かったです。
https://www.fa.hdl.co.jp/jp/html/app/Modbus_Tool/index.html

レビューコメント通りに設定していきます。

  • 伝送モード: RTU
  • 通信速度 9600bps
  • 送信クエリ: 01 03 00 00 00 02 になります
    • MobusワーカID=1(ケース内のスイッチで切り替えられるらしい)
    • Read Holding Registers(0x03)
    • 開始アドレス: 0x0000
    • データサイズ: 0x0002

「接続設定」でLANかUSBを選択できるので、ポート選択でUSBのCOMポート割当を確認しましょう。通電できていればプルダウンで選択肢にセンサーモジュールが出てくるとお見ます。(私の環境ではCOM3に割り当てられていました)

リクエストのTx(要求)を送信して、結果が成功するとRx(応答)の緑の行が表示されます。

Modbusはワーカのシミュレータがいくつかありますが、やはり実機から値が取得できると気持ち良いです。シミュレータを最初に利用しても訳が分からないと思いますので、このセンサーの購入は本当にオススメです。

応答について

01 03 04 00 FA 03 28 DA EC が応答でした。

それぞれの意味です。

  • 01: ModbusワーカID(送信したのと同じ)
  • 03: ファンクションコード(送ったのと同じ)
  • 04: データ部のByte数
  • 00FA: 湿度のデータ
  • 0328: 温度のデータ
  • DAFC: LRCチェックの値

00FA0328も、どこの国の温湿度ですか?って言いたくなりますが、符号付き整数(16進数)でかつ10倍した値(温度も湿度も小数点以下まで取れるようにということだと思われます)ということです。お手軽に値を見るには、このあたりのWebツールを利用して変換すると、00FAは10進数で250、0328は808でした。前者は湿度25%でいいとして、後者は80.8℃?この部屋、サウナかな?って思いましたが、どうも華氏表記(°F)のようです。

華氏 摂氏 でググると、Googleで変換できるんですね。室温が27℃だということがわかりました。(エアコンの設定は25℃なのに解せぬという知見が得られました。)

Goからアクセスする

Modbusで温湿度センサーにアクセスして値を取得することはできました。これをGo言語で行ってみます。Modbusは流石に広く使われているだけあって、Goでもライブラリ実装があります。

Modbusでリクエストを出しレスポンスを受け取るところまで、ほぼサンプル通りで実行できるため特に難しくありません。

コードの全文はこちらにあげておきました。

GoでKKmoonRS485温度湿度センサにアクセスするサンプル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/goburrow/modbus"
"log"
"time"
)

func main() {

h := modbus.NewRTUClientHandler("COM3") // RTU, COM3ポート
h.BaudRate = 9600 // Bit/秒
h.DataBits = 8 // データビット
h.Parity = "N" // パリティ
h.StopBits = 1 // トップビット
h.SlaveId = 1 // スレーブID
h.Timeout = 5 * time.Second

if err := h.Connect(); err != nil {
// ポートの指定間違いや、他のプロセスがポートを開いている場合
log.Fatal("connect: ", err)
}
defer h.Close()

mc := modbus.NewClient(h)
results, err := mc.ReadHoldingRegisters(0, 2) // アドレス0x00、データサイズ2
if err != nil {
log.Fatal("mc.ReadHoldingRegisters", err)
}

humidity := results[0:2]
temperature := results[2:4]

fmt.Println(hex.EncodeToString(humidity), hex.EncodeToString(temperature))

humidityNum := float32(int16(binary.BigEndian.Uint16(humidity))) * 1 / 10
temperatureFNum := float32(int16(binary.BigEndian.Uint16(temperature))) * 1 / 10
temperatureCNum := (temperatureFNum - 32) * 5 / 9

fmt.Println(humidityNum, temperatureCNum)
}

さきほどのModbusToolで行ったのと同じ要領で設定していきます。関数がReadHoldingRegistersなどそのままの名前で準備されているため、Modbusで何を行いたいかがわかれば、利用することは難しくないと思います。

1点実行前の注意として、先ほどのModbusToolでコネクションをOPENにしたままにしておくと、上記コード実行時に connect: Access is denied. が発生してしまいます。Modbus RTUでは同時接続はできませんのでご注意ください。

また、データを取得してからは多少厄介です。 binary.BigEndian.Uint16(humidity) とあるように、2byte(16bit)をBigEndian形式(数値の上位桁がメモリーの下位にある)でUnit16で読み取って、float32に変換しています。

ここからはセンサーの仕様で、値を1/10にし、温度のみ華氏→摂氏変換を行っています。

実行すると以下のように出力されます。(暑かったのでエアコンの温度を下げたら急に冷えました)

1
2
3
4
>go run main.go
湿度 温度
00fb 02e2
25.1 23.222223

後はこの情報を定期間隔で実行し、データをクラウドに上げておけば可視化も自由自在だと思います。

エラー時の挙動

いくつか設定を間違ってみてアクセスして見て結果を確認します。

  • SlaveIDを異なる値(例えば2にした場合)
    • 結果: * mc.ReadHoldingRegistersserial: timeout
  • ReadHoldingRegistersserialの代わりにReadCoilsを利用
    • 結果: * mc.ReadHoldingRegistersserial: timeout
  • ReadHoldingRegistersのAddressを仕様上にない値(例えば1)に変更
    • 結果: * mc.ReadHoldingRegistersserial: timeout
  • ReadHoldingRegistersのデータサイズを仕様上にない値(例えば5)に変更
    • 結果: * mc.ReadHoldingRegistersserial: timeout

全て結果は timeout でした。これでわかるのは、Modbus RTUの仕様にはエラーレスポンスということは存在しないということです。
そのため、機器が正常か異常かといった判定は、定期的に値を確認し想定通り内の応答が返ってくるかどうかといったことでのチェックになると思います。

まとめ

「Modbusでシリアル接続」を最初見た時は馴染みがないだけに、凄く難しいことではないか?と思いましたが、Web系の技術に比べ情報が少ないのはありそうですが、データを取得することだけでだとそこまで難しくもありません。

実際の工場で、ライン制御を直接Goなどから行うことはほぼないと思います。しかしセンサー類から情報をもらい、何かしらのアルゴリズムや機械学習の推論結果を、フィールド制御側にフィードバックすることは今後のFA/IoT化の流れから増えてくると思います。

その場合の通信プロトコルがModbusであれば上記の知識と、goburrow/modbusのようなライブラリを活用すれば、データ連携を行うこと自体はそこまで大きな障壁ではないと思います。

また、Modbusを学ぶ上で特にワーカーの扱いに関しては、シミュレータだけで行うのではなく実機を用いるととてもイメージが湧きます。もし、なるべくお安く済ませたい場合は、AliExpressでの購入もおすすめです。リードタイムが30日ほどかかることもありますが、格安で手に入ること間違いないです。

参考


  1. 1.Technology Innovation Groupの略で、フューチャーの中でも特にIT技術に特化した部隊です。その中でもDXチームは特にデジタルトランスフォーメーションに関わる仕事を推進していくチームです。