Nano V3 Ethernet Shield — Базовый пример — Веб сервер - RobotDynOfficial/Documentation GitHub Wiki

Базовый пример веб-сервера на Nano V3 Ethernet Shield W5500

В этой статье мы разберём создание и работу простого веб-сервера на шилде Nano V3 Ethernet Shield W5500. Этот пример позволит вам понять принцип работы веб-серверов на микроконтроллерах и познакомиться со схемотехникой и новыми платами компании RobotDyn.

Оборудование

В этом эксперименте нам понадобится следующее оборудование:

Сборка стенда очень простая: достаточно соединить вместе «бутербродом» платы Nano V3 Ethernet Shield W5500 и RobotDyn Nano V3. Опционально, эту сборку можно установить на макетную плату — это придаст устойчивости всей конструкции и позволит в дальнейшем легко подключать различную периферию для проведения экспериментов.

Предупреждение: обратите внимание на цоколёвку и правильность соединения шилда и платы микроконтроллера, иначе плата и шилд могут выйти из строя.

Проводные подключения

Для завершения сборки стенда его нужно подключить к вашему компьютеру и локальной сети.

Подключение к компьютеру осуществляется при помощи кабеля c Micro USB разъёмом. Стандартного тока USB порта в 500 мА вполне хватает для питания всей конструкции, поэтому дополнительное питание не требуется.

Ethernet кабелем вы можете подключить ваш будущий сервер к соответствующей розетке в вашем помещении или напрямую к роутеру или коммутатору, в зависимости от конфигурации вашей локальной сети.

Что даёт сетевая плата

Без сетевого шилда ваш микроконтроллер лишён возможности взаимодействия с внешним миром, он может только локально управлять некоторым оборудованием. Добавление сетевой платы к вашему проекту открывает широкие возможности: ваш контроллер сможет обмениваться данными с другими микроконтроллерами, посылать данные в интернет, получать различные управляющие команды по сети и т. д.

Кроме этого, при помощи сетевой платы вы сможете создать интерактивный веб-интерфейс для своего устройства и управлять им так же легко, как вы делаете это с обычными сайтами.

Цель эксперимента

На этом уроке мы создадим простой веб-сервер и подробно разберём как он работает. Взаимодействие будет проходить следующим образом: в адресной строке браузера вы вводите IP адрес вашего Arduino-сервера, нажимаете Enter и на веб-странице видите ответ вашего сервера.

В качестве ответа Arduino-сервера можно вывести различную информацию, в нашем примере мы будем выводить надпись «Analog port 0 is» и далее текущее значение нулевого аналогового порта контроллера (A0). Надпись будет выглядеть примерно так:

Несмотря на то, что это только одна строка, но поняв принцип её вывода, вы в дальнейшем сможете создавать любые, сколь угодно сложные веб-страницы и даже целые веб-сайты.

Скетч

Итак, тестовый стенд мы собрали, подключили его к компьютеру и локальной сети, теперь настало время заняться созданием скетча веб-сервера. Сначала приведём полный текст скетча, а затем подробно разберём его работу.

/*
Web Server example
(based on standard example by David A. Mellis & Tom Igoe)
(c)2019 RobotDyn
License: GNU GPL 2.1

A simple web server that shows the value of the analog input pin.

Circuit:
* [Nano V3 Ethernet Shield W5500](https://robotdyn.com/nano-v3-ethernet-shield-w5500-v2.html) (use D10, D11, D12, D13 pins)
* [RobotDyn Nano V3](https://robotdyn.com/nano-v3-atmega-328-usb-ttl-ch340g-micro-usb.html)
* Analog input attached to pin A0
*/

// SPI library
#include <SPI.h>

// Ethernet2 library (support WIZnet W5500 chip)
#include <Ethernet2.h>

// MAC address
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

// IP address
IPAddress ip(192, 168, 1, 177);

// Ethernet server on 80 port (default for HTTP)
EthernetServer server(80);

const byte analogPort = 0;

// Setup

void setup() {
// Initialize Serial port
Serial.begin(115200);

// Print start message
Serial.println("Start WEB Server example...");

// Start Ethernet connection
Ethernet.begin(mac, ip);

// Start HTTP server
server.begin();

// Print IP address
Serial.print("Server at: ");
Serial.println(Ethernet.localIP());
} // setup()

// Functions

void responseHeader(EthernetClient cl) {
// send a standard http response header
cl.println("HTTP/1.1 200 OK");
cl.println("Content-Type: text/html");
cl.println("Connection: close"); // connection close after response
cl.println();
}

void sendPage(EthernetClient cl) {
// Get value of analog input pin
int value = analogRead(analogPort);

// Send Web-page
cl.println("<!DOCTYPE HTML>");

cl.println("<html>");

cl.print("Analog port ");
cl.print(analogPort);
cl.print(" is ");
cl.println(value);

cl.println("</html>");
}

// Server Works

void serverWorks() {
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
Serial.println();
Serial.println("New client");

// an http request ends with a blank line
boolean currentLineIsBlank = true;

while (client.connected()) {
if (client.available()) {

// read character from client
char c = client.read();

// and print to Serial
Serial.write(c);

// if you've gotten to the end of the line (received a newline character)
// and the line is blank,
// the http request has ended, so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// Response (send Web-page)
responseHeader(client);
sendPage(client);
break;
}

if(c == '\n') {currentLineIsBlank = true;}// starting a new line
else if (c != '\r') {currentLineIsBlank = false;} // gotten a character on the current line
} // client.available()
} // client.connected()

delay(1); // give browser time to receive data
client.stop(); // close connection
Serial.println("Client disconnected");
} // if (client)
} // serverWorks()

// Loop

void loop() {
serverWorks();
}

Несмотря на то, что на первый взгляд скетч кажется большим и сложным, но, после нашего разбора его работы, он станет для вас простым и понятным.

Arduino IDE

В этом мини-проекте мы будем использовать среду разработки Arduino IDE версии 1.8.5, как стабильную и хорошо себя зарекомендовавшую. Экосистема Arduino постоянно развивается и часто выходят новые версии — вы можете использовать и более поздние версии среды Arduino.

Обратите внимание: в этой статье предполагается, что вы достаточно опытный пользователь Arduino, знакомы с основными принципами и приёмами работы с микроконтроллерами и средой Arduino IDE. Если вы испытываете какие-либо проблемы с пониманием этой статьи, то обратитесь к документации и примерам на сайте разработчика системы Arduino.

Подключение библиотек

Для большинства Arduino-проектов требуется подключение сторонних библиотек. Библиотеки — это определённым образом оформленные сборники функций, ориентированные на решение тех или иных задач. Для вас, как пользователей этих библиотек, важно знать только как подключать библиотеки в скетче и уметь пользоваться их функциями (которые обычно описаны в соответствующей документации).

В нашем случае нам понадобится подключить две библиотеки: SPI и Ethernet2.

// SPI library
#include <SPI.h>

// Ethernet2 library (support WIZnet W5500 chip)
#include <Ethernet2.h>

Библиотека SPI присутствует в стандартной поставке Arduino IDE, поэтому вам достаточно только подключить её в скетче одной строкой.

#include <SPI.h>

Библиотеку Ethernet2 можно скачать с сайта GitHab по этой ссылке и установить стандартным образом. Описание процедуры установки библиотек в Arduino IDE выходит за рамки этой статьи, если вы с ней незнакомы, то о ней можно прочитать на сайте разработчика Arduino.

После установки библиотеки Ethernet2, её можно подключить в скетче одной строкой.

#include <Ethernet2.h>

На этом предварительные действия закончены, мы можем приступать к созданию самого скетча веб-сервера.

MAC-адрес

Каждое сетевое устройство должно иметь уникальный 6-байтовый идентификатор, именуемый MAC (Media Access Control) адресом. Когда вы покупаете сетевое оборудование этот идентификатор уже содержится в каждом устройстве. В нашем случае мы можем задать этот адрес в скетче.

Какие конкретно значения байтов нужно выбирать для MAC адреса? Хорошей идеей будет воспользоваться адресом уже заданным в скетче примера. В случае, если у вас в сети находятся несколько Arduino-устройств — можно изменить в этом адресе последний байт для обеспечения его уникальности для каждого сетевого устройства.

// MAC address
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

Если у вас в сети несколько Arduino-устройств — меняем последний байт MAC-адреса произвольным образом, например, вместо значения 0xED вводим значение 0xEE.

// MAC address
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE};

IP-адрес

Если MAC-адрес идентифицирует сетевое устройство на «железном» уровне, то для работы на программном уровне сетевого TCP/IP стека требуется ещё один 4-байтовый идентификатор, называемый IP (Internet Protocol) адресом.

Есть определённые правила задания таких адресов и специально оговоренные их диапазоны, но в нашем случае вам достаточно знать, что из всего IP-адреса

// IP address
IPAddress ip(192, 168, 1, 177);

для нас является важным только последний байт 177. Это и есть «локальный адрес» который вы можете изменять в диапазоне от 1 до 254 по своему желанию. Тут важно только помнить, что как MAC-адреса, так и IP-адреса должны быть уникальными, то есть не повторяться у разных устройств в одной сети.

Примечание: если в настройках вашей локальной сети первые три байта отличаются от указанных в скетче, то вам нужно соответствующим образом изменить эти байты и в скетче.

Объект сервера

Создаём объект веб-сервера (server) на 80-м порту, стандартном для HTTP протокола. Благодаря тому, что вся сложность реализации веб-сервера скрыта в библиотеке Ethernet, мы можем одной строкой создать такой сервер.

// Ethernet server on 80 port (default for HTTP)
EthernetServer server(80);

Аналоговый порт

По условию задачи, мы будем выводить на веб-страницу значение аналогового порта A0 контроллера Arduino. Для этого создаём константу analogPort и присвоим ей значение 0 (порт A0).

const byte analogPort = 0;

В дальнейшем мы будем использовать эту константу для указания функции analogRead() из какого порта нужно брать текущее значение напряжения. В случае, если вы захотите выводить на страницу данные из другого порта, например, A1, то вам нужно будет просто изменить 0 на 1, например, так:

const byte analogPort = 1;

Разбиение скетча на функции

Теперь перейдём непосредственно к написанию кода и функций скетча. В Arduino существуют две функции, которые обязательно должны присутствовать в каждом скетче setup() и loop().

Функция setup() выполняется один раз при старте системы и предназначена для проведения каких-то настроек, которые нужно произвести только один раз в начале работы.

Функция loop() – это бесконечный цикл, внутри которого располагается весь код скетча, который выполняет какие-то полезные действия.

Но этими двумя функциями программирование Arduino не ограничивается и мы можем создавать сколько угодно своих функций для решения наших задач. По этому принципу и построен скетч нашего веб-сервера — весь его функционал разбит на несколько функций, с работой и назначением которых мы познакомимся далее.

Функция setup()

Разберём подробнее работу функции setup(). Сначала полный её код:

void setup() {
// Initialize Serial port
Serial.begin(115200);

// Print start message
Serial.println("Start example...");

// Start Ethernet connection
Ethernet.begin(mac, ip);

// Start HTTP server
server.begin();

// Print IP address
Serial.print("Server at: ");
Serial.println(Ethernet.localIP());
} // setup()

Первая строка инициализирует Serial порт микроконтроллера на скорости 115200 бод. В некоторых скетчах вы можете видеть другие скорости работы порта, например 9600, но в нашем случае мы будем использовать более высокую скорость.

// Initialize Serial port
Serial.begin(115200);

Примечание: если мы задали скорость работы последовательного интерфейса 115200, то такую же скорость нужно выставить и в настройках окна Serial Monitor.

И далее выводим в последовательный порт приветственную в надпись, сообщающую о старте нашего веб-сервера.

// Print start message
Serial.println("Start WEB Server example...");

Затем инициализируем Ethernet с ранее заданными нами MAC и IP адресами и стартуем наш веб-сервер. С этого момента он начинает работу и будет отвечать на приходящие по сети запросы.

// Start Ethernet connection
Ethernet.begin(mac, ip);

// Start HTTP server
server.begin();

И последнее, что мы делаем в функции setup() — это выводим в Serial информационное сообщение о том, на каком порту работает наш сервер, используя библиотечную функцию Ethernet.localIP().

// Print IP address
Serial.print("Server at: ");
Serial.println(Ethernet.localIP());

Ниже представлен результат работы функции setup() и её вывод в Serial Monitor.

На этом работа функции setup() окончена, переходим к описанию и разбору работы функции loop().

Функция loop()

Как вы помните, функция loop() — это бесконечный цикл и всё, что в нем находится, просто повторяется до тех пор, пока микроконтроллер не будет выключен.

void loop() {
serverWorks();
}

В нашем случае loop() содержит только одну функцию serverWorks(), которая и реализуем весь функционал веб сервера. Можно было бы не использовать функцию serverWorks(), а весь её код поместить непосредственно в тело функции loop(), но так делать не стоит — это ухудшит структурированность и читаемость кода проекта.

Веб-запрос

Теперь, для общего понимания происходящих процессов, разберём что происходит, когда вы посылаете из браузера запрос веб серверу. В адресной строке браузера вы набираете IP-адрес Arduino-сервера:

В момент нажатия вами клавиши Enter браузер посылает запрос серверу по указанному IP-адресу. Этот запрос стандартизирован и отвечает заранее установленным требованиям. Выглядит этот запрос примерно так (детали могут отличаться в зависимости от вашего программного обеспечения).

Запрос содержит в себе много различной информации, но для нас в данном случае важны два момента, это:

GET /

означающий, что браузер запрашивает главную «индексную» страницу сервера, и пустая строка после

Upgrade-Insecure-Requests: 1

говорящая серверу, что запрос окончен и браузер начинает ожидание ответа от сервера. Вот, собственно, и вся магия GET запросов от браузера к серверу. Теперь переходим к рассмотрению работы функции serverWorks(), реализующий функционал нашего веб-сервера.

Функция serverWorks()

И вот, наконец, мы добрались непосредственно до кода самого веб-сервера. Этот код представляет собой стандартный пример из поставки библиотеки Ethernet2 и для наших целей даже необязателен для вашего понимания, вам достаточно знать, что он содержит вызовы двух наших функций responseHeader() и sendPage(), о которых мы поговорим чуть ниже.

void serverWorks() {
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
Serial.println();
Serial.println("New client");

// an http request ends with a blank line
boolean currentLineIsBlank = true;

while (client.connected()) {
if (client.available()) {

// read character from client
char c = client.read();

// and print to Serial
Serial.write(c);

// if you've gotten to the end of the line (received a newline character)
// and the line is blank,
// the http request has ended, so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// Response (send Web-page)
responseHeader(client);
sendPage(client);
break;
}

if(c == '\n') {currentLineIsBlank = true;}// starting a new line
else if (c != '\r') {currentLineIsBlank = false;} // gotten a character on the current line
} // client.available()
} // client.connected()

delay(1); // give browser time to receive data
client.stop(); // close connection
Serial.println("Client disconnected");
} // if (client)
} // serverWorks()

Функция serverWorks() постоянно ожидает подключения клиента и, как только клиент подключится к серверу, анализирует его запрос и в ответ выдаёт веб-страницу. Выдача веб-страницы осуществляется функциями responseHeader() и sendPage().

Итак, весь процесс ответа сервера можно разбить на несколько шагов:

1 К серверу подключается клиент (браузер пользователя) 2 Браузер клиента посылает стандартизированный GET запрос 3 Функция serverWorks() принимает и анализирует GET запрос браузера 4 В случае корректности запроса, функция serverWorks() передаёт управление функциям responseHeader() и sendPage() 5 Функции responseHeader() и sendPage() формируют стандартный ответ и посылают веб-страницу браузеру 6 Пользователь видит в окне своего браузера присланную веб- страницу Arduino-сервера

Ниже представлен вывод функции serverWorks() в Serial Monitor среды разработки Arduino IDE при приёме GET запроса от браузера:

Функция отработала, послала веб-страницу и отключила клиента. Теперь наш веб-сервер готов для обслуживания следующего запроса.

Функции responseHeader() и sendPage()

По сути, это одна функция формирования ответа веб-сервера, разделённая на две для удобства понимания и лучшего структурирования кода. Ответ сервера на запрос состоит из двух частей:

1 Посылка заголовка с мета-информацией (функция responseHeader()) 2 Посылка собственно тела веб-страницы (функция sendPage())

Рассмотрим эти функции по-подробнее.

Функция responseHeader()

Функция responseHeader() занимается посылкой заголовка ответа с мета-информацией. Она формирует данные о протоколе и его версии, коде ответа и его результате, типе посылаемого контента и состоянии соединения — вся эта информация используется браузером для правильного отображения посылаемой ему веб-страницы.

void responseHeader(EthernetClient cl) {
// send a standard http response header
cl.println("HTTP/1.1 200 OK");
cl.println("Content-Type: text/html");
cl.println("Connection: close"); // connection close after response
cl.println();
}

Как видно из представленного фрагмента кода сервера, посылка мета-информации заканчивается посылкой пустой строки.

Функция sendPage()

Функция sendPage() это и есть «главный» фрагмент кода нашего сервера — именно в ней формируется та страница, которую мы потом видим в окне браузера. Поняв принцип формирования этой страницы, вы легко сможете создавать любые веб-страницы для ваших проектов.

void sendPage(EthernetClient cl) {
// Get value of analog input pin
int value = analogRead(analogPort);

// Send Web-page
cl.println("<!DOCTYPE HTML>");

cl.println("<html>");

cl.print("Analog port ");
cl.print(analogPort);
cl.print(" is ");
cl.println(value);

cl.println("</html>");
}

Первая строка функции sendPage() получает текущее значение аналогового порта контроллера analogPort и присваивает его переменной value.

// Get value of analog input pin
int value = analogRead(analogPort);

Далее идёт формирование отсылаемой веб-страницы с использованием стандартных HTML тегов.

cl.println("<!DOCTYPE HTML>");

cl.println("<html>");

cl.print("Analog port ");
cl.print(analogPort);
cl.print(" is ");
cl.println(value);

cl.println("</html>");

И вот что вы видите как результат этих действий в окне своего браузера:

А вот как выглядит HTML код присланной сервером веб-страницы:

Вот, собственно, и весь принцип работы веб-сервера и формирования им веб-страниц на микроконтроллерах Arduino. Возможно сразу это покажется вам несколько сложным, но, внимательно прочитав это руководство и поэкспериментировав с вашей платой Nano V3 Ethernet Shield W5500, вы, несомненно, станете мастером сетевого взаимодействия контроллеров Arduino.

Заключение

Вооружившись платой Nano V3 Ethernet Shield W5500 компании RobotDyn и информацией из этой статьи, вы сможете создавать свои интересные сетевые проекты или улучшать сторонние Open Source проекты — ведь теперь вы понимаете как работают веб-сервера на Arduino.

Загрузить скетч

Загрузить скетч примера веб-сервера на Nano V3 Ethernet Shield W5500.

Скетч примера веб-сервера на Nano V3 Ethernet Shield W5500 Size: 3 KB

Ссылки по теме

Overview Nano V3 Ethernet Shield W5500 Спецификации и подключение Nano V3 Ethernet Shield W5500 Купить Nano V3 Ethernet Shield W5500 Купить RobotDyn Nano V3

⚠️ **GitHub.com Fallback** ⚠️