ArduinoのEthernetライブラリでクライアントを実装する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Arduino UNO専用のETHERNET SHIELDを実装するとEthernetライブラリを使用してLAN通信ができます。DHT20モジュールから取得した温湿度の情報をサーバーに送信するWebClientを実装して動作確認を行いました。

DHT20モジュールはGrove-Temperature & Humidity Sensor V2.0(DHT20)(Seeed Studio) 及びETHERNET SHIELDはArduino Ethernet Shield 2を使用しています。

ローカルサーバーを模擬するためにESP32-WROOM-32E開発ボード(秋月電子)を使用してWiFiをサーバーモードにして使用します。下記記事で使用したサーバーを流用します。

ESP32-WROOM-32Eにサーバーを実装してレスポンスする

アクセスポイントにはWZR-HP-G300NH(バッファロー:生産中止)を使用し、ETHERNET SHIELDとLANケーブルで接続します。

ETHERNET SHIELDはアイキャッチ画像のようにArduino UNOに差し込むだけで使用することができます。Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Ethernetライブラリを使用する

Arduino Ethernet Shield Rev2を含む全体構成
Arduino Ethernet Shield Rev2を含む全体構成

ETHERNET SHIELDはイーサネットコントローラーにW5500が使用されておりSPI通信をイーサネットに変換することができます。Arduino環境でイーサネットのプロトコルであるTCP/IPやUDP/IPによる通信を構築することができます。

ローカルサーバーを模擬するためにESP32-WROOM-32Eをサーバーモード(以下サーバーESP32とします。)にして使用します。アクセスポイントとETHERNET SHIELDはLANケーブルで接続します。

ArduinoにWebClientを実装しサーバーESP32に接続を行います。接続が確立するとDHT20モジュールから取得した温湿度のデータを文字列に変換してサーバーに送信します。

EthernetライブラリはArduino UNO用に標準搭載されておりスケッチ例が各種準備されています。本記事ではスケッチ例の「WebClient」を流用して動作確認を行っています。

Ethernetライブラリのインクルードからを初期化及び関数の使用方法を説明します。

スポンサーリンク

ライブラリの準備と初期化

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = {
  0x83, 0x61, 0x0A, 0xAE, 0x73, 0xA8
};

IPAddress ip(192, 168, 11, 2);//ArduinoのIP
IPAddress server(192, 168, 11, 10);//サーバーのIP
EthernetClient client;

void setup() {

  Ethernet.begin(mac, ip);//mac及びipで初期化
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");//イーサーネットシールドの接続が確認できない場合表示
    while (true) {
      delay(1);
    }
  }

  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }
}

EthernetライブラリはSPI通信を使用するためEthernet.hとSPI.hの2つのライブラリをインクルードする必要があります。

イーサーネットコントローラーは固有のMACアドレスが割り振られているため6バイトの値を配列で準備します。ETHERNET SHIELDの裏面にMACアドレスが貼り付けてあります。

IPAddressクラスの変数としてipをインスタンス化し、引数をIPアドレスを指定します。例ではIPアドレスに「192.168.11.2」を指定しています。同様にserverをインスタンス化し、引数にサーバーのIPアドレスを指定します。例ではサーバーESP32のIPアドレスの「192.168.11.10」を指定しています。

EthernetClientクラスの変数としてclientをインスタンス化(clientオブジェクト)します。

Ethernetオブジェクトのbegin()関数でイーサーネットの初期化を行います。第1引数にMACアドレスを指定します。第2引数にIPアドレスを指定します。

hardwareStatus()関数はETHERNET SHIELDの接続を確認します。例では接続が確認できなければETHERNET SHIELDが確認できるまでループさせるようにしています。

linkStatus()関数はイーサーネットに接続状態を確認します。ケーブルの抜き差しでリンクのステータスが切り替わるため特に意識して接続状態を確認する必要はありません。

PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

クライアント接続とデータ送信

if( timConnect == TIME_UP ){
  timConnect = TIM_CON_WAIT;

  if( client.connect(server,10001)){
    //接続が確立
    client.print("Humidity: ");
    client.print(temp_humi[0]);
    client.print(" %\t");
    client.print("Temperature: ");
    client.print(temp_humi[1]);
    client.print(" *C");
    client.println();
    timConnectStop = TIM_CON_STOP;
  }
  else { //接続失敗
    client.stop();//クライアントを停止
    timConnect = TIM_CON_RES;
  }
}

if( timConnectStop == TIME_UP ){
  //クライアントに送信後クライアントを停止
  timConnectStop = TIME_OFF;
  client.stop();
}

clientオブジェクトのconnect()関数でサーバーに要求を出します。第1引数はサーバーのアドレスを指定します。例ではIPAddressクラスでインスタンス化したserverのIPアドレスを指定しています。第2引数は接続するポートを指定します。サーバーESP32のポートを10001にしているので10001を指定しています。

接続が確立するとクライアントからデータを送信することができます。clientオブジェクトのclientオブジェクトのprint()関数で文字列を送信します。引数に送信する文字列を指定します。println()関数を使用すると改行コードが最後に挿入されます。

文字列を送信した後任意の時間(1秒)クライアントを維持した状態にしてからstop()関数でクライアントを停止するようにしています。

文字列の送信後すぐにstop()関数でクライアントを停止するとサーバーにパケットが到達する前にパケットが破棄されてしまいサーバー側でデータが受信できない問題が生じることがあります。

スポンサーリンク

DHT20モジュールからデータを取得

Arduino IDEでDHTライブラリを追加してDHT20モジュールから温湿度のデータを取得します。Arduino IDEのライブラリマネージャの検索欄にgrove dht(ライブラリ名をそのまま入力しても良い)を入力するとライブラリの候補が表示されます。

候補の中からGrove Temperature And Humidity Sensorをインストールします。使用例は以下の通りです。

#include <DHT.h>
#include <Grove_Temperature_And_Humidity_Sensor.h>
#include <Wire.h>

DHT dht(DHT20); //DHT20を指定してインスタンス化
float temp_humi[2];

void setup() {

  Serial.begin(115200);
  Wire.begin();
  dht.begin();
}

void loop() {
  
  dht.readTempAndHumidity(temp_humi);//データを取得
  Serial.print("Humidity: ");
  Serial.print(temp_humi[0]);
  Serial.print(" %\t");
  Serial.print("Temperature: ");
  Serial.print(temp_humi[1]);
  Serial.println(" *C");
  delay(2000);
}

Arduino IDEのスケッチタブの「ライブラリをインクルード」からGrove Temperature And Humidity Sensorを選択します。エディターにDHT.h及びGrove_Temperature_And_Humidity_Sensor.hが追加されますが、DHT.hのみのインクルードでも問題ありません。DHT20はI2C通信を使用するのでWire.hも含めてインクルードします。

DHTクラスの変数としてdhtに引数を指定してインスタンス化(dhtオブジェクト)します。DHT20モジュールを使用するためDHT20を指定します。

DHT.hを選択した状態で「Go to Definition」をクリックするとDHT.hが開けるので対象のDHTモジュールのタイプを定義を確認することができます。

Wire.begin()でI2C通信の初期化を行い、dhtオブジェクトのbegin()関数でDHT20の初期化を行います。

readTempAndHumidity()関数を使用すると温湿度のデータを取得することができます。引数に取得したデータを格納するアドレスを指定します。

使用例のようにデータを取得した場所で関数をコールするだけで簡単に測定値が取得できるため手軽に動作確認できます。

広告
漠然としたキャリア形成の不安を打ち破る!

動作確認

Ethernetライブラリの動作確認の回路図
Ethernetライブラリの動作確認の回路図

ArduinoにETHERNET SHIELDを接続します。LANケーブルをアクセスポイントに接続してイーサネット通信を行います。電源を入れるとDHT20の測定が開始し、サーバー接続を5秒毎に行います。

Arduinoから受信したデータを確認するためサーバーESP32をシリアルモニターで確認します。電源投入後はアクセスポイントが動作開始するまで数秒間待機します。WiFi接続が確立するとサーバーとして動作開始します。

サーバーESP32のシリアルモニターの確認
サーバーESP32のシリアルモニターの確認

Arduinoから接続要求を受け接続を受け付けると「New Client .」を表示します。その後、受け取ったパケットを読み込んで湿度と温度の情報を表示します。Arduinoはサーバーに文字列を送信した後の1秒後にクライアントを停止します。

サーバーESP32はArduinoのクライアントが停止したことを確認するとクライアント情報を破棄して「Client Disconnected.」を表示します。

シリアルモニターの「New Client .」の間隔を確認すると約5秒毎に発生しています。また湿度と温度の文字列を表示した後の約1秒後に「Client Disconnected .」が表示できています。

PR:技術系の通信教育講座ならJTEX

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

Arduinoのソースコード:

#include <MsTimer2.h>
#include <DHT.h>
#include <Grove_Temperature_And_Humidity_Sensor.h>
#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>

#define TIME_UP 0
#define TIME_OFF -1
#define TIM_MEASURE 200
#define TIM_CON_WAIT 500
#define TIM_CON_RES 1000
#define TIM_CON_STOP 100

byte mac[] = {
  0x83, 0x61, 0x0A, 0xAE, 0x73, 0xA8
};//ETHERNET SHIELDのMACを配列で指定する

IPAddress ip(192, 168, 11, 2);//ArduinoのIP
IPAddress server(192, 168, 11, 10);//サーバーのIP
EthernetClient client;

int16_t timMeasure;
int16_t timConnect;
int16_t timConnectStop = TIME_OFF;
DHT dht(DHT20);
float temp_humi[2];

/* Local function prototypes */
void mainTimer(void);
void mainApp(void);

void setup() {

  Serial.begin(115200);
  Wire.begin();
  dht.begin();

  Ethernet.begin(mac, ip);
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1);
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  delay(1000);
  Serial.print("connecting to ");
  Serial.println("...");

  if( client.connect(server,10001)){
    Serial.print("connected to ");
    Serial.println(client.remoteIP());
  }
  else {
    Serial.println("connection failed");
    client.stop();
  }

  timConnect = TIM_CON_WAIT;
  MsTimer2::set(10,mainTimer); //10msごとに関数へ遷移
  MsTimer2::start();
}

void loop() {

  mainApp();
}

/* タイマ管理(timer2がタイムアップするとコールされる) */
void mainTimer(void){

  if( timMeasure > TIME_UP ){
    --timMeasure;
  }

  if( timConnect > TIME_UP ){
    --timConnect;
  }

  if( timConnectStop > TIME_UP ){
    --timConnectStop;
  }
}
/* メイン処理 */
void mainApp(void){

  if( timMeasure == TIME_UP ){
    timMeasure = TIM_MEASURE;

    if (!dht.readTempAndHumidity(temp_humi)) {
      //Serial.print("Humidity: ");
      //Serial.print(temp_humi[0]);
      //Serial.print(" %\t");
      //Serial.print("Temperature: ");
      //Serial.print(temp_humi[1]);
      //Serial.println(" *C");
    } else {
      Serial.println("Failed to get temprature and humidity value.");
    }
  }

  if( timConnect == TIME_UP ){
    timConnect = TIM_CON_WAIT;

    if( client.connect(server,10001)){
      Serial.print("connected to ");
      Serial.println(client.remoteIP());

      client.print("Humidity: ");
      client.print(temp_humi[0]);
      client.print(" %\t");
      client.print("Temperature: ");
      client.print(temp_humi[1]);
      client.print(" *C");
      client.println();
      timConnectStop = TIM_CON_STOP;
    }
    else {
      Serial.println("connection failed");
      client.stop();
      timConnect = TIM_CON_RES;
    }
  }

  if( timConnectStop == TIME_UP ){
    timConnectStop = TIME_OFF;
    client.stop();
  }
}

ESP32-WROOM-32E(サーバーESP32)のソースコード:

#include <WiFi.h>

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_RECONNCT_MAX 1000

const char *ssid = "xxxxxxxxxxxx"; //SSID(使用するアクセスポイントに合わせる)
const char *pass = "yyyyyyyyyyyyy"; //password(使用するアクセスポイントに合わせる)
const IPAddress ip(192,168,11,10); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
  
/* 変数宣言 */
WiFiServer server(10001);
uint32_t beforetimCnt = millis();
int16_t timreconnect = TIME_OFF;
int16_t waitcnt;

/* プロトタイプ宣言 */
void mainApp(void);

void setup() {

  Serial.begin(115200);
  WiFi.begin(ssid,pass);
  WiFi.config(ip,ip,subnet);

  while( WiFi.status() != WL_CONNECTED ){
    delay(1000);
    Serial.print(".");
    if( ++waitcnt >= 60 ){
      esp_restart();
    }
    Serial.print(waitcnt);
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin(); //サーバーの開始
}

void loop() {

  mainApp();
}

/* メイン処理関数 */
void mainApp(void){
    
  WiFiClient client = server.available();

  if (client) { 
    Serial.println("New Client.");

    while (client.connected()) {
      if (client.available()) { 
        char c = client.read();
        Serial.write(c);
      }
    }

    client.stop();
    Serial.println("Client Disconnected.");
  }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました