Re: note

技術的な知見やポエムなど役に立たない情報を書き連ねる場所

【UniRx & Coroutine & async/await】Unityでシリアルポート読み取り

f:id:hik0leaf:20190831160452p:plain

Unityでシリアル通信(受信)する方法です。UniRx版、コルーチン版、async/await版を記載します。

1. 準備

スクリプトからシリアルポートを操作できるようにするために、Unityの Project Settings > Player > Other Settings の項目の Api Compatibility Level.NET 4.x に設定します。

f:id:hik0leaf:20190831161258p:plain

また、受信データ表示用にヒエラルキーにTextを追加してください。 Debug.Logで確認する場合は不要です。

2. スクリプト (UniRx版)

以下のスクリプトを適当なゲームオブジェクトにアタッチします。 ポート名は環境に合わせて変更してください。

using System.IO.Ports;
using UnityEngine;
using UniRx;
using UnityEngine.UI;
using System;

public class MainScript : MonoBehaviour
{
    private string portName = "COM4";
    private int baurate = 9600;
    private SerialPort serial;
    private bool isLoop = true;
    [SerializeField] Text countText = default;

    void Start() {
        serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);
        serial.Open();

        Observable.Create<string>(ReadData)
            .SubscribeOn(Scheduler.ThreadPool)
            .ObserveOn(Scheduler.MainThread)
            .Subscribe(data => countText.text = data)
            .AddTo(this);
    }

    private IDisposable ReadData(IObserver<string> observer) {
        while (isLoop) {
            observer.OnNext(serial.ReadLine());
        }
        return Disposable.Empty;
    }
    
    private void OnDestroy() {
        isLoop = false;
        serial.Close();
    }
}

内容としてはシリアルポートの読み取りをThreadPoolで、結果をTextに表示するために読み取ったデータをMainThreadで処理しています。 (GUIを操作する場合はMainThreadで行う必要があります。Debug.Logするだけであればスレッドの切り替えは不要です)

3. スクリプト (コルーチン版)

以下のスクリプトを適当なゲームオブジェクトにアタッチします。 ポート名は環境に合わせて変更してください。

using System.Collections;
using UnityEngine;
using System.IO.Ports;
using UnityEngine.UI;

public class MainScript : MonoBehaviour
{
    private string portName = "COM4";
    private int baurate = 9600;
    private SerialPort serial;
    private bool isLoop = true;
    [SerializeField] Text countText = default;

    void Start() {
        serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);
        serial.Open();

        StartCoroutine(ReadData());
    }
    
    private IEnumerator ReadData() {
        while (isLoop) {
            countText.text = this.serial.ReadLine();
            yield return null;
        }
    }

    private void OnDestroy() {
        isLoop = false;
        serial.Close();
    }
}

4. スクリプト (async/await版)

(2019.12.07 async/await版も追加しました)

using UnityEngine;
using System.IO.Ports;
using System.Threading.Tasks;

public class MainScript : MonoBehaviour
{
    private string portName = "COM4";
    private int baurate = 9600;
    private SerialPort serial;
    private bool isLoop = true;
    [SerializeField] Text countText = default;

    async void Start() {
        serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);
        serial.Open();

        await ReadData();
    }

    private async Task ReadData() {
        await Task.Run(() => {
            while (isLoop) {
                countText.text = this.serial.ReadLine();
            }
        });
    }

    private void OnDestroy() {
        isLoop = false;
        serial.Close();
    }
}

5. 動作確認準備

今回、動作確認用にArduino Unoを使用します。Arduinoに以下のコードを書き込みます。 ボーレートはUnity側で設定した値と同じにします。(例:9600bps)

byte a = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println(a++);
  delay(500);
}

6. 結果

正常に動作していれば、0.5秒間隔で数字がカウントアップしていきます。 f:id:hik0leaf:20190831170432g:plain

7. Tips

今回、動作確認用にArduino Unoを使用しましたが、Mac OS環境でかつArduino Leonardo(ATmega32u4チップ)を使用する場合は、SerialPortクラスを以下のように設定することでUnityのフリーズを防ぐことができます。

serial = new SerialPort(portName, baurate, Parity.None, 8, StopBits.One);
serial.Open();
serial.DtrEnable = true;
serial.RtsEnable = true;
serial.DiscardInBuffer();
serial.ReadTimeout = 5000;// msec
  • DtrEnable / RtsEnableのどちらかをEnableにするだけでフリーズは回避できます。
  • DiscardInBuffer()することで接続前のバッファをクリアしています。
  • ReadTimeoutを設定しておくとSerialPortのトラブルでUnityのプログラム全体がフリーズするのを防ぐことができます。

8. 参考サイト

nn-hokuson.hatenablog.com

nn-hokuson.hatenablog.com

ENC28J60を使ってESP32でEthernet UDP通信する

f:id:hik0leaf:20190623161903j:plain

MICROCHIPのENC28J60を使ってESP32(Arduino core for the ESP32)でUDP通信を行ってみます。

1. 準備

ENC28J60を入手します。aitendoやAmazonで1,000円程度で購入できます。入手に時間がかかってもよければAliexpressを使うと$2~$3で購入できます。今回はおなじみのチップサイズがSOPではなくSSOPの小さいモジュール版を使用しました。SOP版と同じように使うことができます。

f:id:hik0leaf:20190623175750j:plain

f:id:hik0leaf:20190623175808j:plain
SOP(左)とSSOP(右)のサイズ比較

ESP32はESP32-DevKitCを使います。秋月電子で購入できます。

akizukidenshi.com

2. 接続

ENC28J60とESP32を以下の図のように接続します。接続情報はこれから使用するEthernetライブラリ(UIPEthernet)のこちらのリンクを参考にしました。

f:id:hik0leaf:20190623161925j:plain
接続図

ESP32 ENC28J60
IO5 (SS) CS
IO23 (VS_MOSI) SI
IO19 (VSMISO) SO
IO18 (VS_SCK) SCK
3V3 VCC
GND GND

ESP32-DevKitCは3.3Vのレギュレーターに1A以上出力可能なNCP1117を使用しているため、ENC28J60と直接つなげています。とはいえUSBからの給電のため3.3V全体で500mA以上を超えないように気をつける必要はあります。(3.3VラインはESP32のチップへの電源供給にも使われています)

秋月電子電子の商品ページに貼られているリンクからESP32-DevKitCの回路図を確認することができます。

http://akizukidenshi.com/download/ds/espressifsystems/ESP32-Core-Board-V2_sch.pdf

NCP1117データシート

https://www.onsemi.jp/PowerSolutions/document/NCP1117JP-D.PDF

3. ボードライブラリ修正

Arduino core for the ESP32 Ver 1.0.2にはEthernetを使用するためのライブラリに不備があり、コンパイルすることができません。Arduino core for the ESP32 Ver 1.0.1であればビルドが可能になるため、Ver 1.0.1を使う場合はこの項目は飛ばしてください。

Ver 1.0.2でコンパイルできるようにするためにはArduino core for the ESP32のClient.hを修正する必要があります。 Client.hはボードマネージャを使用してインストールしてあれば以下のパスにがあります。(Windows)

C:\Users\<アカウント名>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.2\cores\esp32\Client.h

テキストエディタでClient.hを開いて以下の行をコメントアウトします。

// virtual int connect(IPAddress ip, uint16_t port, int timeout) =0;
// virtual int connect(const char *host, uint16_t port, int timeout) =0;

これに関する経緯については前回の記事の「ライブラリの修正」項目で少し触れているのでそちらを参考ください。 W5500を使ってESP32でEthernet UDP通信する - Re: note

4. Ethernetライブラリのインストール

EthernetライブラリはESP32とENC28J60をサポートしている以下のものを使用します。

github.com

Arduino IDEのライブラリマネージャから「UIPEthernet」で検索してインストールすることができます。

f:id:hik0leaf:20190622185938j:plain

ESP32の公式フォーラムESP32 with ENC28J60 Ethernet Module では別のライブラリであるEtherCardからforkされたESP32対応ライブラリも試してみましたが、残念ながらビルドすることができませんでしたので、今回はUIPEthernetを使用して進めます。

5. コード

以下のコードをESP32に書き込みます。

#include <UIPEthernet.h>

uint8_t mac[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
uint8_t IP[] = { 10, 0, 1, 8 };
uint8_t MASK[] = { 255, 255, 255, 0 };
uint8_t DNS[] = { 10, 0, 1, 1 };
uint8_t GW[] = { 10, 0, 1, 1 };

EthernetUDP udp;

#define UDP_TX_PACKET_MAX_SIZE 700
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
char ReplyBuffer[] = "acknowledged";

void setup() {
  Serial.begin(115200);
  Serial.println("starting...");

//  Ethernet.begin(mac);// Use DHCP
  Ethernet.begin(mac, IP, DNS, GW, MASK);

  Serial.print("Local IP : ");
  Serial.println(Ethernet.localIP());
  Serial.print("Subnet Mask : ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("Gateway IP : ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("DNS Server : ");
  Serial.println(Ethernet.dnsServerIP());

  udp.begin(3001);
}

void loop() {

  int packetSize = udp.parsePacket();
  if (packetSize) {
    Serial.printf("Received packet of size %d\n", packetSize);

    IPAddress remote = udp.remoteIP();
    Serial.printf("From %d.%d.%d.%d:%d - ", remote[0], remote[1], remote[2], remote[3], udp.remotePort());

    udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Serial.print("Contents: ");
    Serial.println(packetBuffer);

    // send a reply to the IP address and port that sent us the packet we received
    udp.beginPacket(udp.remoteIP(), udp.remotePort());
    udp.write(ReplyBuffer);
    udp.endPacket();
  }
}

UIPEthernetを使って他の端末からpingを打って応答を確認するだけの場合は、loop()関数の中に

server.available();

だけを入れると良いでしょう。これを書かないとIPを取得できてもpingの応答が返りません。

6. 実行

シリアルモニタでログを確認します。通信相手はNode.jsを使うと簡単に用意することができます。正常に動作していれば、ログに受信したメッセージと相手先にReplyBufferで設定した文字列が表示されます。

f:id:hik0leaf:20190623164515j:plain
シリアルモニタの動作ログ (例:カウントアップデータを受信している様子)

動作が不安定だったり動作しなかったりする場合は、電源を別のものに変えたり接続のワイヤーを短くしたりするなどしてみてください。私は最初3.3VラインをESP32からではなく、外部電源から供給していましたがまったく動作しませず、ESP32から直接供給するように変更すると安定動作するようになりました。

7. まとめ

ENC28J60を使ってESP32(Arduino core for the ESP32)でUDP通信を行ってみました。Arduino core for the ESP32は現在でも頻繁に開発が行われており、未完成の部分や不安定な部分がありますが、ESP32は他のマイコンと比較すると入手性も良く、安価・高性能と大きなポテンシャルを持っています。これからも多くの人に使われることによってライブラリも洗練されると嬉しいですね。

W5500を使ってESP32でEthernet MQTT通信する

f:id:hik0leaf:20190617230354j:plain

WIZnetのW5500を使ってESP32(Arduino core for the ESP32)でMQTT通信を行ってみます。

1. 準備

過去の記事を参考に3までを行います。

  • 1.準備
  • 2.接続
  • 3.ライブラリ修正

hikoleaf.hatenablog.jp

2. MQTTライブラリのインストール

MQTT通信するために以下のライブラリをインストールします。

github.com

ライブラリはArduino IDEのライブラリマネージャから名前で検索してインストールすることができます。

f:id:hik0leaf:20190615212349j:plain

PubSubClientについてはAPIドキュメントが用意されています。

Arduino Client for MQTT

3. コード

以下のコードをESP32に書き込みます。

#include <Ethernet.h>
#include <PubSubClient.h>


byte mac[] = { 0x70, 0x69, 0x69, 0x2D, 0x30, 0x31 };
byte server[] = { 10, 0, 1, 5 };

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String msg = String((char*) payload);
  Serial.println(msg);
}

EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);

void reconnect() {
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
    
    if (client.connect("myClient")) {
      Serial.println("MQTT PubSub Ready");
      client.publish("output", "ready");
      client.subscribe("input");
      Serial.println("subscribe input");
    } else {
      Serial.println("MQTT PubSub failer");
      Serial.println("try again in 2 seconds");
      delay(2000);
    }
  }  
}


void setup() {
  Serial.begin(115200);
  Serial.println("starting...");
  
  Ethernet.init(33);// CS pin 33
  Ethernet.begin(mac);// use DHCP

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());
  
  delay(1500);
}


void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

MACアドレスは環境に合わせて変更してください。IPアドレスの設定はDHCPを使っていますが、Ethernet.begin()の引数にIPアドレスgatewayを指定することで固定IPを設定することもできます。

固定IPで設定する際のdnsgateway・subnetはデフォルト値が設定されているため、MACアドレスIPアドレスだけで動作する場合がありますが、指定しないと動作しなかったり通信が不安定だったりするので面倒でも全て指定した方が良いでしょう。Ethernet.begin()の詳細は以下の公式リファレンスから確認できます。

Arduino - EthernetBegin

またserverアドレスはMQTT Brokerのアドレスで、動作確認するためにはMQTT Brokerが必要になります。MQTT BrokerはRaspberry Pimosqittoをインストールして構築するのが一番簡単でおすすめです。

4. 実行

MQTT Brokerが動作していることが確認できたら、ESP32に繋がっているシリアルモニタでログを確認します。正常に動作していれば以下のようになります。

f:id:hik0leaf:20190616110611j:plain

mosquittoをインストールすると、Brokerの他にPubSubクライアントもインストールされるのでPublish(Pub)用とSubscribe(Sub)用のターミナルを立ち上げて動作を確認してみます。

f:id:hik0leaf:20190615214945j:plain
Subscribe画面

トピックはoutputをSubscribeしておきます。ESP32が立ち上がるとreadyが送られてくるのを確認できます。見逃してしまったらESP32をリセットするともう一度送られてきます。

f:id:hik0leaf:20190615214907j:plain
Publish画面

inputトピックに対してメッセージ(payload)を送るとシリアルモニタで受信を確認することができます。

5. まとめ

ESP32でEthernetを使ったMQTT通信を行ってみました。ESP32にはWiFiが搭載されているため無線通信の方が気軽に試すことができますが、無線では得られない圧倒的な通信安定度がEthernetにはありますので用途によって使い分けられると良いかと思います。

W5500を使ってESP32でEthernet UDP通信する

f:id:hik0leaf:20190617230354j:plain

WIZnetのW5500を使ってESP32(Arduino core for the ESP32)でUDP通信を行ってみます。

1. 準備

Aliexpress等でW5500を購入します。

1 個 USR ES1 W5500 チップ新 spi 蘭イーサネット変換 TCPIP Mod - Aliexpress.com | Alibaba グループ上の 電子部品&用品 からの 集積回路 の中

f:id:hik0leaf:20190615222738j:plain
とってもコンパクト

ESP32はESP32-DevKitCを使います。秋月電子で購入できます。

akizukidenshi.com

2. 接続

W5500とESP32を以下のように接続します。3.3Vの供給についてはW5500の消費電流が大きいためESP32から行わずに外部から加えるようにしています。

W5500 | WIZnet Co., Ltd.

(2019.06.23追記)
ESP32-DevKitCであれば3.3Vのレギュレーターに1A以上出力可能なNCP1117を使用しているため、W5500と直接つなげても駆動できるかと思います。

NCP1117データシート(PDF)

f:id:hik0leaf:20190620224653j:plain
接続図

ESP32 W5500
IO33 CS
IO23 (VS_MOSI) MOSI
IO19 (VSMISO) MISO
IO18 (VS_SCK) SCLK
3V3 VCC
GND GND

データシートを見ると100M Trasmittingのときに132mA消費すると書いてあるので、外部電源は200mA以上供給できるものを用意すると良いでしょう。

products:w5500:datasheet [Document Wiki]

f:id:hik0leaf:20190615134207j:plain
W5500の消費電流 (データシートから抜粋)

f:id:hik0leaf:20190615134554j:plain
W5500モジュールのピン配置

3. ライブラリ修正

配線が終わったらコードの書き込みに進みたいところですが、Arduino core for the ESP32 Ver 1.0.2にはEthernetを使用するためのライブラリに不備があり、コンパイルすることができません。

コンパイルできるようにするためにはArduino core for the ESP32のClient.hを修正する必要があります。 Client.hはボードマネージャを使用してインストールしてあれば以下のパスにがあります。(Windows)

C:\Users\<アカウント名>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.2\cores\esp32\Client.h

テキストエディタでClient.hを開いて以下の行をコメントアウトします。

// virtual int connect(IPAddress ip, uint16_t port, int timeout) =0;
// virtual int connect(const char *host, uint16_t port, int timeout) =0;

このあたりの事情は以下のやり取りで伺い知ることができます。

また、この事象によって報告されている不具合

要約するとArduino core for the ESP32 Ver 1.0.2ではArduinoEthernet.hとの互換性を意識していない実装になっているためコンパイルエラーが発生しているようです。

ここではJuraj Andrássy氏の意図を汲んで、Arduino側のライブラリを修正するのではなくArduino core for the ESP32側のライブラリを修正して進めます。

4. コード

ライブラリを修正したら、以下のコードをESP32に書き込みます。

#include <Ethernet.h>
#include <EthernetUdp.h>

byte mac[] = { 0x70, 0x69, 0x69, 0x2D, 0x30, 0x31 };

unsigned int localPort = 3001;

char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
char ReplyBuffer[] = "acknowledged";

EthernetUDP Udp;

void setup() {
  Serial.begin(115200);
  Serial.println("starting...");
  
  Ethernet.init(33);// CS pin 33
  Ethernet.begin(mac);// use DHCP

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());
  
  Udp.begin(localPort);
}

void loop() {
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    IPAddress remote = Udp.remoteIP();
    for (int i=0; i < 4; i++) {
      Serial.print(remote[i], DEC);
      if (i < 3) {
        Serial.print(".");
      }
    }
    Serial.print(", port ");
    Serial.println(Udp.remotePort());

    // read the packet into packetBufffer
    Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Serial.print("Contents: ");
    Serial.println(packetBuffer);

    // send a reply to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(ReplyBuffer);
    Udp.endPacket();
  }
  delay(10);
}

MACアドレスは環境に合わせて変更してください。IPアドレスの設定はDHCPを使っていますが、Ethernet.begin()の引数にIPアドレスgatewayを指定することで固定IPを設定することもできます。

固定IPで設定する際のdnsgateway・subnetはデフォルト値が設定されているため、MACアドレスIPアドレスだけで動作する場合がありますが、指定しないと動作しなかったり通信が不安定だったりするため、面倒でも全て指定した方が良いでしょう。Ethernet.begin()の詳細は以下の公式リファレンスから確認できます。

Arduino - EthernetBegin

5. 実行

シリアルモニタでログを確認します。通信相手はNode.jsを使うと簡単に用意することができます。正常に動作していれば、ログに受信したメッセージと相手先にReplyBufferで設定した文字列が表示されます。

f:id:hik0leaf:20190619233945j:plain
シリアルモニタの動作ログ (例:カウントアップデータを受信している様子)

6. 参考リンク

W5500を使ってArduinoでMQTT通信する

f:id:hik0leaf:20190616104524j:plain

WIZnetのW5500を使ってArduinoでMQTT通信を行ってみます。

1. 準備

Aliexpress等でW5500を購入します。

1 個 USR ES1 W5500 チップ新 spi 蘭イーサネット変換 TCPIP Mod - Aliexpress.com | Alibaba グループ上の 電子部品&用品 からの 集積回路 の中

f:id:hik0leaf:20190615222738j:plain
とってもコンパクト

ArduinoはUNO R3を使います。

2. 接続

ArduinoとはSPIで通信を行うため、W5500を以下のように接続します。IOレベルは3.3Vですが5VトレラントでもあるのでArduinoへ直結することができます。その他の注意点としてはW5500の消費電流が大きいため3.3Vの供給をArduinoから行わずに外部から加えるようにしています。

W5500 | WIZnet Co., Ltd.

f:id:hik0leaf:20190616105511j:plain
接続図

Arduino W5500
D10 (SS) CS
D11 (MOSI) MOSI
D12 (MISO) MISO
D13 (SCK) SCLK
GND GND

データシートを見ると100M Trasmittingのときに132mA消費すると書いてあるので、外部電源は200mA以上供給できるものを用意すると良いでしょう。

products:w5500:datasheet [Document Wiki]

f:id:hik0leaf:20190615134207j:plain
W5500の消費電流 (データシートから抜粋)

f:id:hik0leaf:20190615134554j:plain
W5500モジュールのピン配置

【注意】ブレッドボードで接続する場合はワイヤーをなるべく短くしてください。ワイヤーが長いと通信が不安定になります。

3. ライブラリのインストール

W5500を使用する場合はENC28J60とは異なり、標準のEthernetライブラリでモジュールを制御することができるため、MQTT部分を処理するPubSubClientのみをインストールします。

github.com

ライブラリはArduino IDEのライブラリマネージャから名前で検索してインストールすることができます。

f:id:hik0leaf:20190615212349j:plain

4. コード

以下のコードをArduinoに書き込みます。

#include <Ethernet.h>
#include <PubSubClient.h>

byte mac[] = { 0x70, 0x69, 0x69, 0x2D, 0x30, 0x31 };
byte server[] = { 10, 0, 1, 5 };

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String msg = String((char*) payload);
  Serial.println(msg);
}


EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);


void reconnect() {
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
    
    if (client.connect("myClient")) {
      Serial.println("MQTT PubSub Ready");
      client.publish("output", "ready");
      client.subscribe("input");
      Serial.println("subscribe input");
    } else {
      Serial.println("MQTT PubSub failer");
      Serial.println("try again in 2 seconds");
      delay(2000);
    }
  }  
}


void setup() {
  Serial.begin(9600);
  Serial.println("starting...");
  
  Ethernet.init(10);// CS pin 10
  Ethernet.begin(mac);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());
  
  delay(1500);
}


void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

PubSubClientにはAPIドキュメントも用意されています。

Arduino Client for MQTT

MACアドレスやserverアドレスは環境に合わせて変更してください。 serverアドレスはMQTT Brokerのアドレスで、動作確認するためにはMQTT Brokerが必要になります。MQTT BrokerはRaspberry Pimosqittoをインストールして構築するのが一番簡単でおすすめです。

5. 実行

MQTT Brokerが動作していることが確認できたら、Arduinoに繋がっているシリアルモニタでログを確認します。正常に動作していれば以下のようになります。

f:id:hik0leaf:20190616110611j:plain

mosquittoをインストールすると、Brokerの他にPubSubクライアントもインストールされるのでPublish(Pub)用とSubscribe(Sub)用のターミナルを立ち上げて動作を確認してみます。

f:id:hik0leaf:20190615214945j:plain
Subscribe画面

トピックはoutputをSubscribeしておきます。Arduinoが立ち上がるとreadyが送られてくるのを確認できます。見逃してしまったらArduinoをリセットするともう一度送られてきます。

f:id:hik0leaf:20190615214907j:plain
Publish画面

inputトピックに対してメッセージ(payload)を送るとArduinoのシリアルモニタで受信を確認することができます。

6. まとめ

W5500やENC28J60を使うことで非力なマイコンでもTCPUDP通信できることをこのブログで紹介してきました。最近ではWi-Fiを搭載したESP8266やESP32の登場により、単体で気軽にIoT機器を作ることができるようになりましたが、人が多い場所や電波干渉が顕著な環境では有線による安定動作が望まれる場面もあるかと思います。

マイコンで通信を行うときはコストや用途、使用環境などを考慮して有線や無線、プロトコルを選択できるようになると良いでしょう。

ENC28J60を使ってArduinoでMQTT通信する

f:id:hik0leaf:20190615210326j:plain

MICROCHIPのENC28J60を使ってArduinoでMQTT通信を行ってみます。

1. 準備

ENC28J60を入手します。aitendoやAmazonで1,000円程度で購入できます。入手に時間がかかってもよければAliexpressを使うと$2~$3で購入できます。

f:id:hik0leaf:20190615181042j:plain f:id:hik0leaf:20190615181059j:plain

ArduinoはUNO R3を使います。

2. 接続

ArduinoとはSPIで通信を行うため、ENC28J60を以下のように接続します。IOレベルは3.3Vですが5VトレラントでもあるのでArduinoへ直結することができます。その他の注意点としてはENC28J60の消費電流が大きいため3.3Vの供給をArduinoから行わずに外部から加えるようにしています。このあたりはWIZnetのW5500と同じですね。

f:id:hik0leaf:20190615181240j:plain
接続図

Arduino ENC28J60
D10 (SS) CS
D11 (MOSI) SI
D12 (MISO) SO
D13 (SCK) SCK
GND GND

ENC28J60のモジュールによってはピン配置が異なるため、実物の仕様やシルク等を確認して割り当てられたピンと適切に接続してください。今回購入したENC28J60のモジュールでは以下のようになっていました。

f:id:hik0leaf:20190615181903j:plain
ENC28J60モジュールのピン配置

また、ENC28J60のデータシートを見ると動作時の最大消費電流が180mAとあるので、少なくとも200mA以上の供給能力がある外部電源を用意してください。

ENC28J60 - Ethernet Controllers

f:id:hik0leaf:20190615181350j:plain
ENC28J60の消費電流 (データシートから抜粋)

(2019.06.15 追記)
ブレッドボードで接続する場合はワイヤーをなるべく短くしてください。ワイヤーが長いと通信が不安定になります。

3. ライブラリのインストール

以下の2つのライブラリを使用します。

github.com

github.com

UIPEthernetはENC28J60でEthernet通信するためのライブラリで、PubSubClientはMQTT通信するためのライブラリです。PubSubClientについてはENC28J60をサポートしていないようですが、動作確認することができたのでこのまま進めます。

ライブラリは2つともArduino IDEのライブラリマネージャから名前で検索してインストールすることができます。

f:id:hik0leaf:20190615212408j:plain f:id:hik0leaf:20190615212349j:plain

4. コード

以下のコードをArduinoに書き込みます。

#include <UIPEthernet.h>
#include <PubSubClient.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte server[] = { 10, 0, 1, 9 };

EthernetClient ethClient;
void callback(char* topic, byte* palyload, unsigned int length);
PubSubClient client(server, 1883, callback, ethClient);

void setup() {
  Serial.begin(9600);
  Serial.println("starting...");
  
  Ethernet.begin(mac);
  Serial.print("IP Address: ");
  Serial.print(Ethernet.localIP());
  Serial.print("/");
  Serial.println(Ethernet.subnetMask());

  while (!client.connected()) {
    if (client.connect("myClient")) {
      Serial.println("MQTT PubSub Ready");
      client.publish("output", "ready");
      client.subscribe("input");
      Serial.println("subscribe input");
    } else {
      Serial.println("MQTT PubSub failer");
      Serial.println("try again in 2 seconds");
      delay(2000);
    }
  }
}

void loop() {
  client.loop();
}

void callback(char* topic, byte* payload, unsigned int length) {
  payload[length] = '\0';
  String msg = String((char*) payload);

  Serial.println(msg);
}

PubSubClientにはAPIドキュメントも用意されています。

Arduino Client for MQTT

MACアドレスやserverアドレスは環境に合わせて変更してください。 serverアドレスはMQTT Brokerのアドレスで、動作確認するためにはMQTT Brokerが必要になります。MQTT BrokerはRaspberry Pimosqittoをインストールして構築するのが一番簡単でおすすめです。

5. 実行

MQTT Brokerが動作していることが確認できたら、Arduinoに繋がっているシリアルモニタでログを確認します。正常に動作していれば以下のようになります。

f:id:hik0leaf:20190615214540j:plain

mosquittoをインストールすると、Brokerの他にPubSubクライアントもインストールされるのでPublish(Pub)用とSubscribe(Sub)用のターミナルを立ち上げて動作を確認してみます。

f:id:hik0leaf:20190615214945j:plain
Subscribe画面

トピックはoutputをSubscribeしておきます。Arduinoが立ち上がるとreadyが送られてくるのを確認できます。見逃してしまったらArduinoをリセットするともう一度送られてきます。

f:id:hik0leaf:20190615214907j:plain
Publish画面

inputトピックに対してメッセージ(payload)を送るとArduinoのシリアルモニタで受信を確認することができます。

6. まとめ

ENC28J60を使ってArduinoでMQTT通信できることが確認できました。しかしながら、PubSubClientがENC28J60のサポートを正式に謳っていないことや、スケッチを書き込んでみると分かりますがArduino UNOを使うとフラッシュメモリを70%以上消費してしまうため、使用にはやや不安が残ります。

これらの点に注意しながら使うか、別のイーサネットモジュール(W5500など)や別のライブラリの使用を検討するのも良いかと思います。

7. 参考リンク

ワンコインでIot入門 第二章

ENC28J60を使ってArduinoでUDP通信する

f:id:hik0leaf:20190615175934j:plain

MICROCHIPのENC28J60を使ってArduinoUDP通信を行ってみます。

1. 準備

ENC28J60を入手します。aitendoやAmazonで1,000円程度で購入できます。入手に時間がかかってもよければAliexpressを使うと$2~$3で購入できます。

f:id:hik0leaf:20190615181042j:plain f:id:hik0leaf:20190615181059j:plain

ArduinoはUNO R3を使います。

2. 接続

ArduinoとはSPIで通信を行うため、ENC28J60を以下のように接続します。IOレベルは3.3Vですが5VトレラントでもあるのでArduinoへ直結することができます。その他の注意点としてはENC28J60の消費電流が大きいため3.3Vの供給をArduinoから行わずに外部から加えるようにしています。このあたりはWIZnetのW5500と同じですね。

f:id:hik0leaf:20190615181240j:plain
接続図

Arduino ENC28J60
D10 (SS) CS
D11 (MOSI) SI
D12 (MISO) SO
D13 (SCK) SCK
GND GND

ENC28J60のモジュールによってはピン配置が異なるため、実物の仕様やシルク等を確認して割り当てられたピンと適切に接続してください。今回購入したENC28J60のモジュールでは以下のようになっていました。

f:id:hik0leaf:20190615181903j:plain
ENC28J60モジュールのピン配置

また、ENC28J60のデータシートを見ると動作時の最大消費電流が180mAとあるので、少なくとも200mA以上の供給能力がある外部電源を用意してください。

https://www.microchip.com/wwwproducts/en/en022889

f:id:hik0leaf:20190615181350j:plain
ENC28J60の消費電流 (データシートから抜粋)

(2019.06.15 追記)
ブレッドボードで接続する場合はワイヤーをなるべく短くしてください。ワイヤーが長いと通信が不安定になります。

3. コード

W5500であれば標準ライブラリのEthernet.hが使用できますが、ENC28J60は対応していないようなので、今回は別ライブラリのEtherCardを使用します。Arduino Library Managerに対応しているので、Arduino IDEのライブラリマネージャからインストールすることができます。(「ethercard」で検索すると出てきます)

github.com

他にもENC28J60に対応しているライブラリとしてはUIPEthernetもあります。

以下のコードをArduinoに書き込みます。

#include <EtherCard.h>

const byte ip[] = { 10, 0, 1, 10 };
const byte gw[] = { 10, 0, 1, 1 };
const byte dns[] = { 10, 0, 1, 1 };
const byte mac[] = { 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F };
byte Ethernet::buffer[700];


void udpSerialPrint(uint16_t dest_port, uint8_t src_ip[IP_LEN], uint16_t src_port, const char *data, uint16_t len){
  Serial.print("dest_port: ");
  Serial.println(dest_port);
  
  Serial.print("src_port: ");
  Serial.println(src_port);
  
  Serial.print("src_ip: ");
  ether.printIp(src_ip);
  
  Serial.print("\ndata: ");
  Serial.println(data);

  ether.sendUdp(data, len, dest_port, src_ip, src_port);// send echo back
}


void setup () {
  Serial.begin(9600);
  Serial.println("starting...");
  
  if (ether.begin(sizeof Ethernet::buffer, mac, SS) == 0) {
    Serial.println( "Failed to access Ethernet controller");
  }
  
  ether.staticSetup(ip, gw, dns);
  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip);
  ether.printIp("DNS: ", ether.dnsip);

  ether.udpServerListenOnPort(&udpSerialPrint, 1337);
}


void loop () {
  ether.packetLoop(ether.packetReceive());
}

MACアドレスIPアドレスは環境に合わせて変更してください。 EtherCardの詳細なリファレンスも用意されています。

EtherCard: EtherCard

4. 実行

シリアルモニタでログを確認します。通信相手はNode.jsを使うと簡単に用意することができます。正常に動作していれば、ログに受信したメッセージと相手先に同じメッセージをエコーバックしていることが確認できます。

f:id:hik0leaf:20190615184136j:plain
シリアルモニタの動作ログ (例:カウントアップデータをエコーバック)

5. 参考リンク