Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
Ethernet Shield

概要

Ethernet Shieldを利用したTCP/IP通信の実験です。標準で付属のEthernetライブラリを利用します。スケッチはArduinoソフトウェアについてくるDhcpAddressPrinter、WebClient、WebServerをベースに少し修正しています。

Arduino Uno と Arduino Due の両方で動作することを確認しました。Arduino Dueの場合も、ICSPピンが正しく刺さるように(デジタルピンやアナログピンの番号が同じとなるように)Ethernet Shieldを取り付ければ動作するようです。

目的

Ethernetライブラリの使い方と動作を少しずつ見ていきます。最終的にはTCPの簡単なクライアントプログラムとサーバプログラムを作成します。

  1. 共通編
    1. IPアドレスを設定する。
      1. DHCPサーバからIPアドレスを取得する。
      2. IPアドレスを指定する。
  2. クライアント編
    1. 簡単なWebクライアントを作成する。
  3. サーバ編
    1. 簡単なWebサーバを作成する。

実験

共通編

クライアント・サーバのどちらを利用するにもIPアドレスを設定する必要があります。ここではIPアドレスの設定方法を確認します。

IPアドレスを設定する

EthernetClassクラスのEthernetというオブジェクト(変数)に、MACアドレスやIPアドレスなどを保持します。EthernetというオブジェクトはEthernetClassライブラリの中で事前に定義されています。シリアル通信に利用するSerialというオブジェクトと同様と考えることができます。

IPアドレスは自分で指定することも、DHCPサーバから取得することもできます。

DHCPサーバからIPアドレスを取得する。

Ethernet.begin()という関数を利用してIPアドレスを設定します。この関数はパラメータによって動作が異なります。DHCPサーバからIPアドレスを取得するときは、この関数にMACアドレスだけを渡します。IPアドレスの取得に成功すると1が返り、失敗すると0が返ります。

MACアドレスはEthernet Shieldの裏に記載されています。Ethernet ShieldをArduino本体に装着する前にMACアドレスをメモしておかないと、後で苦労するかもしれません。隙間から見えなくもありませんが。

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

// MACアドレスの定義
byte mac_address[] = {
  0x90, 0xA2, 0xDa, 0x0D, 0xD2, 0xEF
};

void setup() {
 // put your setup code here, to run once:
  Serial.begin(9600);

  // DHCPサーバを用いてIPアドレス、サブネットマスク、ゲートウェイサーバ、DNSサーバを設定する。
  if (Ethernet.begin(mac_address) > 0) {
    Serial.print("IP Address:         ");
    Serial.println(Ethernet.localIP());
    Serial.print("Subnet Mask:        ");
    Serial.println(Ethernet.subnetMask());
    Serial.print("Gateway IP Address: "); 
    Serial.println(Ethernet.gatewayIP());
    Serial.print("DNS Server Address: "); 
    Serial.println(Ethernet.dnsServerIP());
  }
}

void loop() {
  // put your main code here, to run repeatedly: 
}

MACアドレスはbyte型の配列で6バイト分を設定します。Ethernet Shieldに割り当てられているアドレスを設定してください。

Ethernet.localIP()は設定されているIPアドレスを取得する関数、Ethernet.subnetMask()は設定されているサブネットマスクを取得する関数、Ethernet.gatewayIP()は設定されているゲートウェイのIPアドレスを取得する関数、Ethernet.dnsServerIP()は設定されているDNSサーバのIPアドレスを取得する関数です。すべてIPAddressクラスを返却します。Ethernet.localIP()以外は公式のリファレンスには掲載されていませんが利用することができます。

IPアドレスクラスはSerial.print()に直接渡すことができます。ドット付きの10進表記で出力されます。

DHCPサーバからIPアドレスを取得した場合は定期的にアドレスの再取得(再リース)を行う必要があります。これは、Ethernet.maintain()という関数を使います。

IPアドレスを指定する

IPアドレスを自分で設定するときもEthernet.begin()を使います。MACアドレス、IPアドレス、DNSサーバアドレス、ゲートウェイアドレス、サブネットマスクを設定することができます。DNSサーバアドレス、ゲートウェイアドレス、サブネットマスクは省略することもできます。省略した場合は以下の値が設定されます。

項目 デフォルト値 省略組み合わせパターン
MACアドレス なし 必須 必須 必須
DNSサーバアドレス IPアドレスの第4オクテットを1にしたIPアドレス。 省略 設定 設定
ゲートウェイアドレス IPアドレスの第4オクテットを1にしたIPアドレス。 省略
サブネットマスク 255.255.255.0 省略

表の下から順に同時に省略することができます。例えば、サブネットマスクとゲートウェイアドレスを同時に省略することはできますが、DNSサーバを省略してゲートウェイアドレスを指定するということはできません。詳しくはEthernet.begin()のリファレンスを参照してください。

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

// MACアドレスの定義
byte mac_address[] = {
  0x90, 0xA2, 0xDa, 0x0D, 0xD2, 0xEF
};

// IPアドレスの定義
byte IP_address[] = {
  192, 168, 11, 200
};

// DNSサーバアドレスの定義
byte dns_address[] = {
  192, 168, 11, 1
};

// ゲートウェイアドレスの定義
byte gateway_address[] = {
  192, 168, 11, 1
};

// サブネットマスクの定義
byte subnet[] = {
  255, 255, 255, 0
};

void setup() {
 // put your setup code here, to run once:
  Serial.begin(9600);

  // MACアドレス、IPアドレス、DNSサーバ、
  // ゲートウェイサーバ、サブネットマスクを設定する。
  Ethernet.begin(mac_address, IP_address, dns_address, gateway_address, subnet);
  Serial.print("IP Address:         ");
  Serial.println(Ethernet.localIP());
  Serial.print("Subnet Mask:        ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("Gateway IP Address: "); 
  Serial.println(Ethernet.gatewayIP());
  Serial.print("DNS Server Address: "); 
  Serial.println(Ethernet.dnsServerIP());
}

void loop() {
  // put your main code here, to run repeatedly: 
}

IPアドレスやサブネットマスクは、byte型の配列で4バイト分を設定します。各自の環境に合わせて設定してください。IPAddressクラスの変数を使うこともできます。この場合は、例えば、以下のように設定します。

IPAddress IP_address(192, 168, 11, 200);

設定したIPアドレスに対してpingを打つことで、正しく設定されていることが確認できます。

クライアント編

簡単なWebクライアントを作成する

TCPのクライアントは以下の手順で実現することができます。

  1. IPアドレス等設定
  2. クライアント用オブジェクト作成
  3. サーバに接続
  4. データの送受信
    1. 送信
    2. 受信

上記で、EthernetClientクラスのメソッドは実際には、「2」で作成したオブジェクトのメソッドを呼び出します。例えば、「client」というEthernetClient型のオブジェクトを「2」で作成したとすると、EthernetClient::connect()は、スケッチ上では、client.connect()となります。

以下にhttp://www.arduino.cc/にアクセスして、トップページを取得するサンプルプログラムを示します。当然ですが取得できるのはHTML本体です。

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

byte mac_address[] = {
  0x90, 0xA2, 0xDa, 0x0D, 0xD2, 0xEF
};

EthernetClient client;

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

  if (Ethernet.begin(mac_address) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP.");
    for(;;)
      ;
  }

  Serial.print("connecting...");
  if (client.connect("www.arduino.cc", 80)) {
    Serial.println("done.");
    // Make a HTTP request:
    client.println("GET / HTTP/1.1");
    client.println("Host: www.arduino.cc");
    client.println("Connection: close");
    client.println();
  } 
  else {
    Serial.println("failed.");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

EthernetClient型の変数clientを定義します。setup()とloop()の両方で利用するため、グローバル変数として定義しています。

setup()では、前述した方法でEthernet ShieldにIPアドレス等を割り当てています。DHCPサーバ経由でIPアドレスを取得しているので、直接IPアドレスを指定したい場合は、適宜修正してください。

EthernetClient::connect()を使ってサーバに接続します。引数は接続先のホスト名とポートです。ホスト名ではなくIPアドレスで指定する場合は、IPAddress型の変数を指定します。

次に、EthernetClient::print()を用いてサーバに文字列を送信します。今回は、HTTP/1.1のGETメソッドと必要なほぼ最小限のヘッダ情報を送信しています。

その後、loop()内で、EthernetClient::available()で受信データがあるかを確認し、データがある場合は、EthernetClient::read()でデータを受信します。最後に、EthernetClient::connected()でコネクションの有無を確認しています。

サーバ編

TCPのサーバは以下の手順で実現することができます。

  1. IPアドレス等設定
  2. サーバ用オブジェクト作成
  3. クライアントからの接続準備(listen相当)
  4. クライアントからの接続待機(accept相当)
  5. データの受送信
    1. 送信
    2. 受信

以下に、Webサーバとしてアナログ入力の値をWebブラウザに表示するためのスケッチを示します。このスケッチは、チュートリアルにあるスケッチです。

EthernetServer型の変数serverを定義します。setup()とloop()の両方で利用するため、グローバル変数として定義しています。

setup()では、前述した方法でEthernet ShieldにIPアドレス等を割り当てています。IPアドレスを直接指定しているため、DHCPサーバ経由でIPアドレスを取得したい場合は、適宜修正してください。ただし、サーバ用途では毎回IPアドレスが変更されるのは不便なので、IPアドレスを直接指定するのがいいと思います。

EthernetServer::begin()を使ってクライアントからの接続準備を行います。引数はありません。

次に、EthernetServer::available()を使ってクライアントからの接続を待機します。接続がある場合は否ゼロの値が返却されます。返却されるのはEthernetClient型のオブジェクトです。

EthernetServer::available()を呼び出した際に返却されたEthernetClient型のオブジェクトを利用して、クライアントとのデータの受送信を行います。

EthernetServerクラスにもEthernetServer::write()というメソッドがありますが、これは、現在接続されているすべてのクライアントに対してデータを送信するためのメソッドです。

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

// MACアドレスの定義
byte mac_address[] = {0x90, 0xA2, 0xDA, 0x0D, 0xD2, 0xEF};

// IPアドレスの定義
byte ip_address[] = {192, 168, 11, 200};

// サーバ用オブジェクト作成
EthernetServer server(80);

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

  // MACアドレス、IPアドレスを設定する。
  Ethernet.begin(mac_address, ip_address);
  // クライアントからの接続準備
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}

void loop() {
  // クライアントからの接続待機
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    boolean currentLineIsBlank = true;
    // データの受送信
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<meta http-equiv=\"refresh\" content=\"5\">");
          for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
            int sensorReading = analogRead(analogChannel);
            client.print("analog input ");
            client.print(analogChannel);
            client.print(" is ");
            client.print(sensorReading);
            client.println("<br />");       
          }
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        } 
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    Serial.println("client disonnected");
  }
}

まとめ

Ethernetクラス、EthernetClientクラス、EthernetServerクラスの関係は以下の通りです。

クラス 用途
Ethernet IPアドレス等の設定
EthernetClient サーバへの接続要求を取り扱う
クライアント・サーバ間のデータ送受信
EthernetServer クライアントからの接続要求を取り扱う

以下に簡単な関連図を示します。

バージョン

Arduino 1.0.4/Arduino Uno、Arduino 1.5.2/Arduino Due



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2013 garretlab all rights reserved.
inserted by FC2 system