Артур Филип Дент (arthurphdent) wrote,
Артур Филип Дент
arthurphdent

Category:

Веб-сервер своими руками. Обзор Ethernet Shield для Arduino.

Сегодня я  рассмотрю популярную плату расширения для Arduino - Ethernet Shield на базе чипа Wiznet W5100. Она отличается модулей на базе чипа ENC28J60 большей стабильностью, скоростью работы и расширенным функционалом, хотя и стоит немного дороже. Для работы с платой не требуется дополнительных библиотек - только <Ethernet.h>, входящий в базовый пакет Arduino. Так же, в Arduino IDE вы найдете готовые примеры работы с модулем Ethernet, однако многие при заливке даже базовых скетчей столкнутся с рядом трудностей, которые я постараюсь максимально описать в данной статье.

Итак, нам понадобятся:

Получение данных с сервера

Для чего вообще может понадобится веб-сервер на Arduino? Основной его плюс - это конечно же малая стоимость, полноценный сервер обойдется в сотни долларов, когда как сервер на Arduino всего в несколько сотен рублей. Он обязателен для проектов "Умный дом", и вообще для проектов, требующих выхода в Интернет. Получение показаний с датчиков и изображений с камер, удаленное управление модулями, это только малая часть того, для чего он может понадобиться. Больше не нужно беспокоиться, не оставили ли вы включенным утюг или свет в коридоре, уходя на работу, если их можно выключить откуда угодно!)

Приступим к сборке, всего 2 простых шага:


  • Присоедините Ethernet Shield к Arduino

  • Один конец патч-корда вставьте в разъем на шилде, второй подключите к роутеру


Рассмотрим немного измененный пример из библиотеки Ethernet - WebServer (переведены комментарии, чтобы был понятен каждый пункт).


#include <SPI.h>
#include <Ethernet.h>
// MAC адрес можно выбрать любой - на результат это не повлияет
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
/* IP адрес нужно выбирать исходя из IP адреса основного шлюза - выполнив команду ipconfig в командной строке, получим IPv4 и IPv6 адреса, а так же IP основного шлюза и маску подсети. Если основной шлюз имеет вид 192.168.0.1, то выберем например 192.168.0.177 
Основной шлюз можно узнать так же в настройках подключения.*/
IPAddress ip(192, 168, 0, 177);
// Выбираем порт, к которому будем подключаться. По умолчанию при HTTP запросе идет подключение в 80 порту, но можно выбрать любое значение
EthernetServer server(80);
void setup() {
  Serial.begin(9600); // открываем монитор COM-порта
  while (!Serial) {
    ; // ждем подключения COM-порта
  }
  // начинаем интернет-соединение
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("ip сервера:");
  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) {
          // отсылаем старндартные http заголовки
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // соединение будет закрыто после получения ответа
          client.println("Refresh: 5");  // автоматическая перезагрузка страницы каждые 5 секунд
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          // выводим данные с каждого аналогового выхода
          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 disconnected");
  }
}


Введя в адресной строке браузера ip адрес нашей платы, получим что то вроде этого:


Отлично! Теперь мы получили рабочий веб-сервер. Изменим пример, возьмем любой датчик. В моем случае это будет аналоговый датчик температуры LM35DZ. Подадим питание 5В, землю, а управляющий пин подключим к A0.



Наш новый пример:


#include 
[Error: Irreparable invalid markup ('<spi.h>') in entry. Owner must fix manually. Raw contents below.]

<p><span style="text-align: justify; line-height: 1.4;">Сегодня я &nbsp;рассмотрю популярную плату расширения для Arduino - Ethernet Shield на базе чипа Wiznet W5100. Она отличается модулей на базе чипа ENC28J60 большей стабильностью, скоростью работы и расширенным функционалом, хотя и стоит немного дороже. Для работы с платой не требуется дополнительных библиотек - только &lt;Ethernet.h&gt;, входящий в базовый пакет Arduino. Так же, в Arduino IDE вы найдете готовые примеры работы с модулем Ethernet, однако многие при заливке даже базовых скетчей столкнутся с рядом трудностей, которые я постараюсь максимально описать в данной статье.</span></p><p>Итак, нам понадобятся:</p><ul>
<li><a href="http://roboshop.spb.ru/xDuino/xDuino-Uno" target="_blank">Arduino Uno R3</a> (<a href="http://roboshop.spb.ru/arduino/arduino-leonardo" target="_blank">Leonardo</a> или <a href="http://roboshop.spb.ru/arduino/arduino-mega" target="_blank">Mega</a>)</li>
<li><a href="http://roboshop.spb.ru/modules/ethernet-shield-w5100" target="_blank">Ethernet Shield W5100</a></li>
<li>Коммутационный кабель (патч-корд)</li>
</ul><h2></h2><lj-cut><h2>Получение данных с сервера</h2><p>Для чего вообще может понадобится веб-сервер на Arduino? Основной его плюс - это конечно же малая стоимость, полноценный сервер обойдется в сотни долларов, когда как сервер на Arduino всего в несколько сотен рублей. Он обязателен для проектов &quot;Умный дом&quot;, и вообще для проектов, требующих выхода в Интернет. Получение показаний с датчиков и изображений с камер, удаленное управление модулями, это только малая часть того, для чего он может понадобиться. Больше не нужно беспокоиться, не оставили ли вы включенным утюг или свет в коридоре, уходя на работу, если их можно выключить откуда угодно!) </p><p>Приступим к сборке, всего 2 простых шага:</p><ul>
<li>Присоедините Ethernet Shield к Arduino</li>
<li>Один конец патч-корда вставьте в разъем на шилде, второй подключите к роутеру</li>
</ul><p>
Рассмотрим немного измененный пример из библиотеки Ethernet - WebServer (переведены комментарии, чтобы был понятен каждый пункт).</p>
<pre>
#include &lt;SPI.h&gt;
#include &lt;Ethernet.h&gt;
// MAC адрес можно выбрать любой - на результат это не повлияет
byte mac[] = {
&nbsp; 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
/* IP адрес нужно выбирать исходя из IP адреса основного шлюза - выполнив команду ipconfig в командной строке, получим IPv4 и IPv6 адреса, а так же IP основного шлюза и маску подсети. Если основной шлюз имеет вид 192.168.0.1, то выберем например 192.168.0.177
Основной шлюз можно узнать так же в настройках подключения.*/
IPAddress ip(192, 168, 0, 177);
// Выбираем порт, к которому будем подключаться. По умолчанию при HTTP запросе идет подключение в 80 порту, но можно выбрать любое значение
EthernetServer server(80);
void setup() {
&nbsp; Serial.begin(9600); // открываем монитор COM-порта
&nbsp; while (!Serial) {
&nbsp; &nbsp; ; // ждем подключения COM-порта
&nbsp; }
&nbsp; // начинаем интернет-соединение
&nbsp; Ethernet.begin(mac, ip);
&nbsp; server.begin();
&nbsp; Serial.print(&quot;ip сервера:&quot;);
&nbsp; Serial.println(Ethernet.localIP());
}
void loop() {
&nbsp; // ждем появления клиентов
&nbsp; EthernetClient client = server.available();
&nbsp; if (client) {
&nbsp; &nbsp; Serial.println(&quot;new client&quot;);
&nbsp; &nbsp; // переменная флаг новой строки
&nbsp; &nbsp; boolean currentLineIsBlank = true;
&nbsp; &nbsp; while (client.connected()) {
&nbsp; &nbsp; &nbsp; if (client.available()) {
&nbsp; &nbsp; &nbsp; &nbsp; char c = client.read();
&nbsp; &nbsp; &nbsp; &nbsp; Serial.write(c);
&nbsp; &nbsp; &nbsp; &nbsp; // если мы получили символ новой строки
&nbsp; &nbsp; &nbsp; &nbsp; // и текущая строка пустая, значит запрос окончен
&nbsp; &nbsp; &nbsp; &nbsp; // и можно отослать ответ
&nbsp; &nbsp; &nbsp; &nbsp; if (c == &#39;\n&#39; &amp;&amp; currentLineIsBlank) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // отсылаем старндартные http заголовки
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;HTTP/1.1 200 OK&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;Content-Type: text/html&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;Connection: close&quot;); &nbsp;// соединение будет закрыто после получения ответа
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;Refresh: 5&quot;); &nbsp;// автоматическая перезагрузка страницы каждые 5 секунд
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;&lt;!DOCTYPE HTML&gt;&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;&lt;html&gt;&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // выводим данные с каждого аналогового выхода
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (int analogChannel = 0; analogChannel &lt; 6; analogChannel++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int sensorReading = analogRead(analogChannel);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.print(&quot;analog input &quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.print(analogChannel);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.print(&quot; is &quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.print(sensorReading);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;&lt;br /&gt;&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; client.println(&quot;&lt;/html&gt;&quot;);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; if (c == &#39;\n&#39;) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // у нас новая строка
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; currentLineIsBlank = true;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; else if (c != &#39;\r&#39;) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // у нас символ на текущей строке
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; currentLineIsBlank = false;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp; // даем браузеру время получить данные
&nbsp; &nbsp; delay(1);
&nbsp; &nbsp; // закрываем соединение
&nbsp; &nbsp; client.stop();
&nbsp; &nbsp; Serial.println(&quot;client disconnected&quot;);
&nbsp; }
}</pre>

<p><span style="line-height: 1.4;">Введя в адресной строке браузера ip адрес нашей платы, получим что то вроде этого:</span></p>
<p style="text-align: center; "><img src="http://roboshop.spb.ru/image/catalog/demo/lessons/web-server-1.jpg" /></p><p style="text-align: left;">Отлично! Теперь мы получили рабочий веб-сервер. Изменим пример, возьмем любой датчик. В моем случае это будет аналоговый датчик температуры LM35DZ. Подадим питание 5В, землю, а управляющий пин подключим к A0.</p>
<p style="text-align: center;"><img src="http://roboshop.spb.ru/image/catalog/demo/lessons/web-server-4.jpg" /></p>
<p style="text-align: left;"><span style="line-height: 1.4;">Наш новый пример:</span></p>
<pre style="text-align: left;">
#include <spi.h>
#include <ethernet.h>

byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 0, 177);
EthernetServer server(80);

void setup() {
Serial.begin(9600);
while (!Serial) {
;
}
Ethernet.begin(mac, ip);
server.begin();
Serial.print(&quot;server is at &quot;);
Serial.println(Ethernet.localIP());
}


void loop() {
EthernetClient client = server.available();
if (client) {
Serial.println(&quot;new client&quot;);
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
if (c == &#39;\n&#39; &amp;&amp; currentLineIsBlank) {
client.println(&quot;HTTP/1.1 200 OK&quot;);
client.println(&quot;Content-Type: text/html&quot;);
client.println(&quot;Connection: close&quot;);
client.println(&quot;Refresh: 5&quot;);
client.println();
client.println(&quot;&quot;);
client.println(&quot;&quot;);
int sensorReading = analogRead(0);// подключаем нулевой аналоговый выход
client.print(&quot;
temp is: &quot;);
client.print(sensorReading*0.48828125); // преобразуем показания в градусы С
client.println(&quot;
&quot;);
client.println(&quot;&quot;);
break;
}
if (c == &#39;\n&#39;) {
currentLineIsBlank = true;
}
else if (c != &#39;\r&#39;) {
currentLineIsBlank = false;
}
}
}
delay(1);
client.stop();
Serial.println(&quot;client disconnected&quot;);
}
}</ethernet.h></spi.h></pre>

<p style="text-align: left;">В итоге получаем:</p>
<p style="text-align: center;"><img src="http://roboshop.spb.ru/image/catalog/demo/lessons/web-server-2.jpg" /></p><p style="text-align: left;">Датчики можно подключать какие угодно, важно лишь помнить, что 4 пин используется при чтении SD карты, а так же 10 пин для самого Ethernet Shield, так что при подключении туда датчиков данные будут искажены.</p>
<h2 style="text-align: left;">Отправка запроса на сервер и управление платой</h2><p style="">Как передавать данные мы разобрались. А что насчет удаленного управления платой? Рассмотрим управление RGB светодиодом.</p>
<p style="text-align: center; "><img src="http://roboshop.spb.ru/image/catalog/demo/lessons/web-server-5.jpg" /></p><p></p>
<pre style="text-align: left;">
#include <spi.h>
#include <ethernet.h>

byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 0, 177);
EthernetServer server(80);
int numPins = 3;
int pins[] = { 2, 3, 5 }; // Пины для RGB светодиода
int pinState[] = {0, 0, 0}; // Состояние пинов

void setup() {
Serial.begin(9600);
for (int i = 0; i &lt; numPins; i++)
{
pinMode(pins[i], OUTPUT);
digitalWrite(pins[i], 0);
}
Ethernet.begin(mac, ip);
server.begin();
Serial.print(&quot;server is at &quot;);
Serial.println(Ethernet.localIP());
}


void loop() {
EthernetClient client = server.available();
if (client)
{
// Проверяем подключен ли клиент
while (client.connected())
{
// Проверяем идет ли запрос
int dataCount = client.available();
if (dataCount &gt; 0)
{
// Считываем данные
for (int i = 0; i &lt; dataCount; i++)
{
char dat = client.read();
// Если данные передаются, то они будут переданы POST запросом, который начинается с символа &#39;P&#39;
if (i == 0 &amp;&amp; dat != &#39;P&#39;)
break;
if (dat == &#39;\n&#39; &amp;&amp; i &lt; dataCount - 1)
{
// Находим строку, в которой содержатся передаваемые данные
char chNext = client.read();
// Формат строки r2=on&amp;r3=on&amp;r4=on
if (chNext == &#39;r&#39;)
{
// Выключаем светодиод
pinState[0] = 0;
pinState[1] = 0;
pinState[2] = 0;

char ledNum = client.read();
pinState[ledNum-&#39;0&#39;] = 1;
Serial.write(ledNum);
ledNum = client.read();
ledNum = client.read();
ledNum = client.read();
ledNum = client.read();

while (ledNum != -1)
{
ledNum = client.read();
ledNum = client.read();
pinState[ledNum-&#39;0&#39;] = 1;
Serial.write(ledNum);
ledNum = client.read();
ledNum = client.read();
ledNum = client.read();
ledNum = client.read();
}
} else
{
// Если не было передано данных, то выключаем светодиод
pinState[0] = 0;
pinState[1] = 0;
pinState[2] = 0;
}
}
}
}
Serial.println(&quot; &quot;);
// Включаем светодиод
for (int i = 0; i &lt; 3; i++)
{
digitalWrite(pins[i], pinState[i]);
}
// Выводим HTML страницу
client.println(&quot;HTTP/1.1 200 OK&quot;);
client.println(&quot;Content-Type: text/html&quot;);
client.println();
client.println(&quot;&quot;);
client.println(&quot;&quot;);
client.println(&quot;<title></title>&quot;);
client.println(&quot;&quot;);
client.println(&quot;&quot;);
client.println(&quot;<h3>RGB control</h3>&quot;);
client.println(&quot;<form method="post">&quot;);
client.print(&quot;<div>RED <input br="" div="" if="" name="r0" type="checkbox" />&quot;);
client.print(&quot;<div>BLUE <input br="" div="" if="" name="r1" type="checkbox" />&quot;);
client.print(&quot;<div>GREEN <input br="" div="" if="" name="r2" type="checkbox" />&quot;);
client.println(&quot;<input type="submit" value="Refresh" />&quot;);
client.println(&quot;</div></div></div></form>&quot;);
client.println(&quot;&quot;);
client.println(&quot;&quot;);
client.stop();
}
}
}
</ethernet.h></spi.h></pre>

<div>Теперь по запросу получаем такую страницу:</div>
<div style="text-align: center; "><img src="http://roboshop.spb.ru/image/catalog/demo/lessons/web-server-3.jpg" /></div>
<div>Выбрав необходимые пункты, мы можем получить любой цвет, который может выдать RGB светодиод.</div>
<div>Этот пример универсален - на управляющих пинах может быть что угодно - датчики, модули, реле. Главное, если вдруг захотите подвести всю технику в доме к платам через реле - это соблюдать ТБ и перед операцией отлючить электричество) </div>
<h2>
Подлючение сервера к Интернету</h2><div>Теперь, осталось обсудить вопросы подключения вашего сервера к интернету. </div>
<div>Обязательное условие - наличие &quot;белого&quot; IP адреса. Так как количество адресов IPv4 ограничено, многие провайдеры выдают абонентам один и тот же IP на всех. В таком случае организовать сервер не удастся. </div>
<div>Варианты решения проблемы:</div>
<div><ul>
<li>У оператор обычно есть услуга &quot;белого&quot; IP, естественно платная</li>
<li>Сменить оператора на того, у которого белый IP идет по умолчанию</li>
<li><a href="http://rubukkit.org/threads/ipv6-shag-v-buduschee-ili-obxodim-seryj-ip-bez-hamachi.35342/?attempt=1" target="_blank">Взять себе IPv6 адрес</a></li>
</ul></div>
<div><span style="line-height: 1.4;">Последний вариант обсуждать не будем, по ссылке все указано в подробностях. Допустим, что у вас белый IP адрес, что же делать дальше?</span></div>
<div>Самое важное - нужно &quot;пробросить&quot; порт.</div>
<div>Зайдите в настройки вашего роутера. Проброс портов различается в зависимости от моделей роутера, но общие принципы одинаковы:</div>
<div><ul>
<li>Найдите в настройках пункт &quot;Виртуальный сервер&quot;</li>
<li>Найдите там таблицу соответствия порт - ip. Вам необходимо указать пробрасываемый порт, локальный IP адрес, на который будет идти запрос, и протокол запроса. IP адрес и порт в данном случае - это тот IP и тот порт которые вы указали в скетче.</li>
<li>Укажите IP адрес сервера в настройках DMZ </li>
</ul>
Все, теперь введя в строке браузера ваш внешний IP адрес, и указав через двоеточие порт, вы увидите страницу, сгенерированную вашим сервером. </div></lj-cut>

<lj-like buttons="repost,facebook,twitter,google,vkontakte,odnoklassniki,tumblr,livejournal" />
Tags: arduino, ethernet
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments