Современные информационные технологии/Компьютерная инженерия

Мясищев А.А.

Хмельницкий национальный университет, Украина

О возможности построения универсального Web-сервера на Arduino для отображения информации и управления по TCP/IP сети

         Рассмотрим построение web – сервера на микроконтроллере AVR (ATmega328) и Ethernet контроллере w5100 производителя WIZNET. На устройстве должна быть установлена SD карта памяти, на которой располагаются HTML файлы, файлы JPG и PDF. К серверу также должен быть подключен температурный датчик DS18B20 и мобильный телефон. При обращении к серверу по Интернет браузер должен отображать HTML документы,  температуру в помещении, выполнять дозвон с подключенного к нему мобильного телефона на заданный мобильный телефон. Сервер также должен работать с концевым выключателем с целью возможности оповещения клиента через мобильный телефон при замыкании выключателя (например, при открытии входной двери помещения). Для изучения возможности решения такой задачи воспользуемся платформой Arduino[1].

         Arduino - аппаратная вычислительная платформа, основными компонентам которой являются простая плата ввода/вывода и среда разработки на языке Processing/Wiring. Плата Arduino состоит из микроконтроллера Atmel AVR (например, ATmega328 и ATmega1280) и элементной обвязки для программирования и интеграции с другими схемами. На каждой плате обязательно присутствуют линейный стабилизатор напряжения 5 В и 16 МГц кварцевый генератор. В микроконтроллер предварительно прошит загрузчик, поэтому внешний программатор не используется и  прошивка выполняется с USB порта компьютера. Платы Arduino позволяют использовать большую часть I/O выводов микроконтроллера во внешних схемах. Например,  в плате Arduino UNO (рис.1) доступно 14 цифровых вводов/выводов (уровни "LOW" -0В и "HIGH" -5В), 6 из которых могут выдавать ШИМ сигнал, и 6 аналоговых входов(0-5В). На рынке доступны несколько внешних плат расширения, известных как "shields".

http://192.168.1.100/aUNO.jpg

Рис.1. Arduino UNO

         Интегрированная среда разработки Arduino (рис.2) - это кроссплатформенное приложение на Java, включающее в себя редактор кода, компилятор и модуль передачи прошивки в плату. Среда разработки основана на языке программирования Processing, а язык программирования микроконтроллеров аналогичен языку, используемому в проекте Wiring. Здесь программы обрабатываются с помощью препроцессора, а затем компилируется с помощью AVR-GCC. Программное обеспечение включает много библиотек для работы с внешними устройствами.

http://192.168.1.100/aprog.jpg

Рис.2. Интегрированная среда разработки Arduino

         Для функционирования web – сервера необходим контроллер Ethernet Shield W5100 (рис.3), который устанавливается с помощью разъемов на плате Arduino. Контроллер основан на Ethernet –микросхеме Wiznet W5100, которая также поддерживает стеки TCP/IP и UDP в IP-сети. Для создания программ, которые подключают Arduino к сети при помощи данного контроллера,  используется библиотека Ethernet. Плата Ethernet Shield W5100 имеет стандартный разъём RJ-45 для подключения к сети.

http://192.168.1.100/a5100.jpg

Рис.4. Arduino Ethernet Shield W5100

 

         Контроллер также имеет разъём для карт памяти типа micro-SD, которая может использоваться для хранения файлов. Разъем micro-SD доступен при помощи библиотеки SD Library.

Arduino осуществляет связь с W5100 и картой SD посредством шины SPI (через разъём ICSP, расположенный справа на рис.1). При  использовании библиотек Ethernet  и SD вывод № 10 платы Arduino(формирует сигнал SS шины SPI) используется для выбора W5100, а ввод № 4 - для карты SD. Эти выводы не могут быть использованы для другого ввода-вывода. Необходимо учитывать, что на плате Arduino Mega, аппаратный вывод SS № 53, не используется для выбора ни W5100, ни карты SD, но он должен быть сконфигурирован как вывод, иначе интерфейс SPI не будет работать.

         Микросхема W5100 и карта SD разделяют шину SPI, поэтому одновременно они работать  не могут.  Если используются оба этих периферийных устройства в программе, следует использовать соответствующие им библиотеки. Однако если не используется ни одно из этих периферийных устройств, следует явно отключить их. Чтобы это сделать, необходимо сконфигурировать  вывод платы № 4 для SD как выход и записать в него "1". Для W5100 необходимо сделать то же самое но для вывода № 10.

         В работе представлена устойчиво работающая  программа универсального web - сервера на Arduino UNO (ATmega328)[2] , которая отображает web - страницы с иллюстрациями и аппаратно с помощью реле сбрасывает сама себя примерно каждые две минуты, выполняет дозвон и считывает показания температурного датчика. Сброс необходим для выхода из "зависаний", которые наблюдаются при интенсивном обращении со стороны клиента. Такие  "зависания" наблюдались на платах Arduino UNO  и Arduino Mega. Анализ показал, что зависания возникают при передаче значительных объёмов данных при активном обращении к серверу как со стороны одного клиента, так и со стороны нескольких. Если web – страничка имеет размер в пределах одного Ethernet пакета, "зависаний" не наблюдалось. Анализ  показал, что зависает контроллер Ethernet Shield W5100. Для обеспечения устойчивой работы сервера была использована библиотека VEduino[3]  предназначенная для программирования счетчиков – таймеров. Согласно программе через определенный интервал времени выполнялось прерывание от таймера – счетчика 1. Управление передавалось функции обработки прерывания ISR(TIMER1_COMPA_vect). Каждое обращение к этой функции приводило к увеличению на единицу переменной s. Как только ее значение превышало 500, на 7-м выводе платы Arduino устанавливался высокий уровень, сигнал поступал на базу транзистора, который с помощью реле замыкал RESET микроконтроллера на “землю” (рис.4). В этом случае происходил аппаратный сброс всего устройства и если контроллер до этого  "зависал", то после RESET работа всего устройства возобновлялась.  

         Сервер также с помощью датчика DS18B20 определяет температуру в помещении и передает ее браузеру после нажатия на ссылку "информация".  При замыкании концевого выключателя, например при открывании входной двери, на выводе 5 платы Arduino дважды устанавливается высокий уровень, поступающий на вход транзистора, замыкающего реле два раза (рис. 4). Контакты реле подключены к кнопке мобильного телефона “поднять трубку”, после чего телефон дозванивается по последнему номеру, по которому он ранее дозванивался.  Программа сервера также дает возможность принудительно выполнить дозвон при условии подключения к нему по адресу:  http://192.168.1.100/tele.

Рис.4. Схема подключения к Arduino

         Представленная ниже программа web - сервера имеет подробные комментарии,  поэтому нет необходимости ее детального описания.

#include <SPI.h>

#include <SD.h>

#include <Ethernet.h>

#include <OneWire.h>

#include <ve_avr.h> // Библиотека VEduino для программирования

//счетчиков - таймеров

unsigned int potValue;  volatile int s=0; OneWire ds(6);

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

IPAddress ip(192,168,1,100); IPAddress gateway(192,168,1,1);

IPAddress subnet(255,255,255,0);  

EthernetServer server(80); // Создаем сервер, слушающий 80 порт

File myFile;

 float temp()  // Функция определения температуры с датчика DS18B20

 {

  byte i; byte data[10];  byte addr[8]; float celsius; 

  ds.search(addr); ds.reset(); ds.select(addr);

  ds.write(0x44,1); // start conversion, with parasite power on at the end

  delay(1000);    ds.reset();  ds.select(addr);   

  ds.write(0xBE);   // Read Scratchpad

  for ( i = 0; i < 9; i++) {   // we need 9 bytes

    data[i] = ds.read();}   

  int raw = (data[1] << 8) | data[0];

    unsigned char t_mask[4] = {0x7, 0x3, 0x1, 0x0};

    byte cfg = (data[4] & 0x60) >> 5;  raw &= ~t_mask[cfg];

  celsius = (float)raw / 16; return celsius;

 }

void setup() {

  pinMode(2,INPUT); // Сигнализация

  digitalWrite(2, HIGH); // Сигнализация

  pinMode(5,OUTPUT);   // Telephone

  digitalWrite(5, LOW); // Telephone

   pinMode(7,OUTPUT);   // Reset

  digitalWrite(7, LOW); // Reset

  Serial.begin(9600);

  SD.begin(4); // Инициализация SD модуля и выборка SD 4-м выводом

  Serial.print("Free RAM: ");  // Распечатка свободной памяти SRAM

  Serial.println(FreeRam()); 

  pinMode(10, OUTPUT);                    // Установить SS вывод как выходящий

  digitalWrite(10, HIGH);                    // Выключить чип w5100

 // Запускаем сервер. Шлюз выбран 192.168.1.1, маска 255.255.255.0

  Ethernet.begin(mac, ip, gateway, subnet);

  server.begin(); // Ожидаем соединение на 80-м порту

  // Настройка прерывания от таймера - счетчика 1

    potValue = 65535;

    DEV_TIMER1.setClockSelect(TimerW::Prescaler_64);// Тактовая

//частота - 16МГц / 64 = 250 кГц. Таймер-счётчик 1 будет

// увеличивать значение регистра TCNT1 на единицу

// каждые 4 микросекунды.

    DEV_TIMER1.setWaveGenMode(TimerW::FastPWM_OCRA);// Таймер-

//счётчик 1 будет сравнивать значение регистров

// TCNT1 и OCR1A и  когда они будут равны,   

    DEV_TICTRL1.outCompIntEnableA();// будет вызываться функция-

//обработчик прерывания TIMER1_COM

    interrupts();  // Включить прерывания.

}

// Выбираем размер буфера 100 символов, где находится имя файла.

#define BUF 100

void loop()

{

  if(digitalRead(2)==LOW) //Проверяем, сработал ли концевой выключатель

// на 2-м выводе

  {

    delay(500); // Даем задержку

    if(digitalRead(2)==LOW) // И еще раз проверяем срабатывание

//концевого выключателя

    {  digitalWrite(5, HIGH);  delay(100); digitalWrite(5, LOW);

          delay(1000);digitalWrite(5, HIGH);delay(100);

          digitalWrite(5, LOW);  delay(60000);   }

  }

  char clientline[BUF]; char *filename=0; int index = 0;

// В программе выполняется чтение только первой строки

//заголовка от браузера. После ее чтения

// выделяется имя файла для чтения его с SD карты памяти,

//считывается этот файл, пересылается

// браузеру и после этого соединение закрывается.

//Весь запрос не анализируется.

  EthernetClient client = server.available();

    if (client) { // Если client=1, то существует соединение с сервером

    // reset the input buffer

 index = 0; 

 while (client.connected())//Если есть несчитанные данные то client.connected()=1

{

if (client.available()) //В client.available() хранится количество полученных

// символов от клиента

{

  char c = client.read();// Посимвольное чтение данных с клиента

 // Сбросить соединение, если пришел непонятный символ от клиента.

// Например, наблюдались зависания от браузера

 // Safary (IPad 2), который посылал "непонятные" символы

        if ( c==0x0A || c==0x0D ) goto aa;      

        if ( c<0x20 || c>0x7E ) break;

   aa:

      // Если символ от клиента правильный, записываем его в буфер

      // Если идет чтение не новой строки, то продолжаем ее символы

      //записывать в буфер.

        if (c != '\n' && c != '\r') {

          clientline[index] = c;

          index++;         

 // Идем на продолжение считывать новый символ.

          continue;

        }        

 // Заканчиваем строку символом 0, если следующая

 // строка новая ( получили  \n или \r )

        clientline[index] = 0;

 // Распечатываем прочитанную строку.

        Serial.println(clientline);

// Если запрос http://192.168.1.100/temp распечатываем на браузере температуру

          if (strstr(clientline, "GET /temp") != 0) {client.println("HTTP/1.1 200 OK");

          client.println("Content-Type: text"); client.println();

          client.print("Temp=");  client.print(temp());

 // Выходим с цикла while

          break;

          }   

        // Если запрос http://192.168.1.100/tele то звоним    

          if (strstr(clientline, "GET /tele") != 0) {client.println("HTTP/1.1 200 OK");

          client.println("Content-Type: text");  client.println();   client.print("Ring...");

          digitalWrite(5, HIGH);  delay(100); digitalWrite(5, LOW);

          delay(1000);digitalWrite(5, HIGH);delay(100); digitalWrite(5, LOW);

       // Выходим с цикла while

          break;

          }   

        // Если первая строка запроса клиента подобна этой GET / HTTP/1.1 то

        // имя файла для чтения с SD карты - index.htm

        if (strstr(clientline, "GET / ") != 0) { filename = "index.htm"; }               

        if (strstr(clientline, "GET /") != 0) {

          // Если пробела после "/" нет, то определяем имя файла        

          if (!filename) filename = clientline + 5;   Serial.println(filename);

          // Просматриваем строку после  "GET /" (5 символов),

          //присваиваем filename

          // все символы строки clientline, которые идут за 5-й позицией.

          // Ищем " HTTP" в полученной подстроке и там где пробел,

          //ставим "0". Строка,

          // записанная в filename отрезается (т.е. удаляется HTTP/1.1).

          char *fil;

          fil= strstr(filename, " HTTP");

          fil[0]=0;         

          // Окончательно распечатываем имя файла, который

          // считывается с SD карты памяти

          Serial.println(filename);

          // Открываем файл

         myFile = SD.open(filename);

          if (!myFile ) {  client.println("HTTP/1.1 404 Not Found");

            client.println("Content-Type: text/html");  client.println();

            client.println("<h2>File Not Found!</h2>");

            break; } 

         // и передаем его содержимое браузеру вместе с заголовком,

         //который формируется в зависимости

         // от расширения файла

        Serial.println("Opened!");        

          client.println("HTTP/1.1 200 OK");

          if (strstr(filename, ".htm") != 0)

client.println("Content-Type: text/html; charset=Windows-1251");

else if (strstr(filename, ".jpg") != 0) client.println("Content-Type: image/jpeg");

else if (strstr(filename, ".pdf") != 0)  client.println("Content-Type: application/pdf");

         else

             client.println("Content-Type: text");

          client.println();

  // Для ускорения чтения данных с  SD карты чтение и передачу

  // выполняем 64-байтными блоками      

  byte cB[64];   int cC=0;

    while (myFile.available())

       {

         cB[cC]=myFile.read();  cC++;

       if(cC > 63)

        { client.write(cB,64);  cC=0; }

       }

      if(cC > 0) client.write(cB,cC);  

       myFile.close();

        } else {

          // everything else is a 404

          client.println("HTTP/1.1 404 Not Found");

          client.println("Content-Type: text/html");

          client.println();   client.println("<h2>File Not Found!</h2>");

           }

       // Выходим из цикла while

        break;

      }

    }

    // Даем браузеру время для получения данных и закрываем соединение

    delay(1);   client.stop();

  }

}

ISR(TIMER1_COMPA_vect)              // Функция обработки прерывания

{

    DEV_TIMER1.setOutputCompareA(potValue); // Установить значение

// следующего прерывания от переменной Val

   s++;                   // После каждого прерывания увеличиваем s  

   if (s>500) {  s=0; digitalWrite(7, HIGH);}   // Примерно через 2 минуты

// идет обнуление Reset (с помощью релле)

// (аппаратный сброс)

}

Выводы.

1. Показана возможность на базе микроконтроллеров AVR построение универсальных web – серверов, позволяющих не только отображать достаточно большие объёмы информации, но и выполнять управление устройствами по TCP/IP сети, что характерно для микроконтроллеров.

2. Использование платформы Arduino значительно упрощает и ускоряет создание подобных достаточно сложных проектов из-за наличия увеличивающегося набора библиотек и достаточно дешёвых плат Arduino с минимальным набором электронных компонентов.

3. Обнаруженным существенным недостатком является нестабильная работа представленного здесь web – сервера, который периодически “виснет” при интенсивном к нему обращении. Анализ показал нестабильность работы именно контроллера w5100 совместно с его набором библиотек.

4. Недостатком также является довольно медленная совместная работа по шине SPI памяти SD и контроллера w5100. Несмотря на использовании в программе блочного способа чтения данных с SD, скорость передачи данных по сети не превышала 17.5Кбайт/с.

 

Литература.

1. Arduino. Официальный сайт.  [Electronic resource]. -  Mode of access: http://arduino.cc/ , 2013.

2. Мясищев А.А. Web – сервер на Arduino UNO и Ethernet Shield W5100. [Electronic resource]. -  Mode of access: http://host56.no-ip.biz, 2013.

3. Библиотека VE_AVR. [Electronic resource]. -  Mode of access: https://sites.google.com/site/vanyambauseslinux/biblioteka-ve_avr, 2013.