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

 

Мясищев А.А.

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

Способ размещения данных в flash – памяти ATmega1280 используемых для работы Web-сервера

В настоящее время широко используется удаленное управление устройствами по TCP/IP сети (через Интернет). Обычно такие устройства создаются на базе микроконтроллеров AVR, PIC, STM и др. Но кроме чисто управленческих команд целесообразно выводить на браузер информацию о самом устройстве, правилах использования команд, схему взаимодействия датчиков и т.д. Поэтому web – сервер на перечисленных микроконтроллерах должен выполнять функции также и информационного сервера, который в состоянии отображать не только радио-кнопки, поля ввода web – форм, но и достаточный объем текстовой информации и изображения. В настоящее время выпускается достаточное количество микроконтроллеров, которые имеют объем flash памяти программ более 128Кбайт. Например, для AVR – это ATmega128, ATmega1280, ATmega2560 и др.  Особенностью перечисленных микроконтроллеров является то, что они являются 8-ми разрядными, поэтому при их программировании возникают сложности адресации к памяти за пределами 64Кбайт. Тем более не все компиляторы поддерживают обращение к памяти за область 64Кбайт. Для микроконтроллеров ATmega32, ATmega644 и др. аналогичных таких проблем не существует, т.к. их flash память не выходит за пределы 64Кбайт. Рассмотрим решение задачи размещения html – страниц во всей flash памяти микроконтроллера ATmega1280, используя контроллер Arduino mega и программную среду Arduino, использующую компилятор WinAVR и язык Wiring[1].

Выполним построение web – сервера, который удовлетворяет  следующим условиям:

1. В flash – памяти микроконтроллера хранится не менее 100Кбайт данных, т.е. html – странички и изображения web – сервера.

2. Сервер должен снимать данные с температурного датчика DS18B20 и отображать их на браузере.

3. Управлять удаленно одним исполнительным механизмом. Например, включать – выключать освещение. Отображать состояние устройства на экране браузера (включено оно или выключено).

         Рассмотрим два случая. Первый – использование библиотеки Flash среды Arduino, заимствованной из источника [2] и второй – непосредственное использование библиотеки  AVR libc[3] компилятора gcc для Atmel AVR микроконтроллеров (в нашем случае WinAVR).

         Библиотека Flash, которая была специально написана для среды Arduino,  позволяет сэкономить ОЗУ микроконтроллера для размещения данных, перенеся их в программную память. Эта библиотека также упрощает программирование на C++ с целью ухода от понятий prog_char, PSTR (), PROGMEM, pgm_read_byte (), и т.д. и сводит работу с flash памятью подобно работе с обычными массивами. Например, предварительно объявляются массивы:

Strings: FLASH_STRING(name, value)

Arrays: FLASH_ARRAY(type, name, list of values…)

Tables: FLASH_TABLE(type, name, columns, values…)

String Arrays: FLASH_STRING_ARRAY(name, values…)

А впоследствии производится обычная работа с ними, как в обычном языке программирования:

FLASH_ARRAY(float, temp, 23.1, 23.1, 23.2, 23.2, 23.4, 23.7, 25.0, 26.0, 26.8, 28.8, 30.2, 31.9, 33.1, 33.1, 33.2);

float cont;

for(int i=0; i<15; j++)  cont=temp/12.0;

Для того чтобы эта библиотека работала, необходимо ее скопировать с сайта http://arduiniana.org/libraries/flash/ и разместить файлы библиотеки в каталоге libraries пакета программ Arduino. В начале программы необходимо записать

#include <Flash.h>

         Рассмотрим  сокращенную часть  программы web – сервера:

#include <Flash.h>

#include <SPI.h>

#include <Ethernet.h>

#include <OneWire.h>          //Подключаем описание библиотеки шины OneWire

#include <DallasTemperature.h> // Библиотека для температуры(DS18B20)

#define FORM "<FORM action=\"\" >"

OneWire oneWire(6);      //Настройка шины для работы с 6-м выводом Ардуино

DallasTemperature sensors(&oneWire); //Подключаем датчик температуры

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

IPAddress ip(192,168,1,10);

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

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

EthernetServer server(8080);

int kk=0;

void writ(EthernetClient client) // Функция передачи 64-х байтных блоков текста

{ FLASH_ARRAY(byte, tex,

0x3c,0x46,0x4f,0x4e,0x54,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3d,0x27,0x23,0x30,

// … Байтовое представление текста html - странички

0x3e,0x0d,0x0a,0x3c,0x2f,0x70,0x72,0x65,0x3e,);

int ii=0,cC=0; byte cB[64];

while ( ii < tex.count() ){

 cB[cC]=tex[ii]; cC++; if(cC > 63) { client.write(cB,64); cC=0; } ii++; }

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

}

void w_pic(EthernetClient client)//Передача 64-х байтных блоков изображения

{ FLASH_ARRAY(byte, pic,

0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48, 0x44,

// … Байтовое представление изображения

0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82);

int ii=0,cC=0; byte cB[64];

while ( ii < pic.count() ){

 cB[cC]=pic[ii]; cC++; if(cC > 63) { client.write(cB,64); cC=0; } ii++; }

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

}

void setup()

{   pinMode(7,OUTPUT);   // Реле включения освещения на 7-м выводе Ардуино

  digitalWrite(7, LOW); // Управляет освещением 

  sensors.begin(); //Инициализация датчика температуры DS18B20

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

  server.begin();

}

void loop() { int ki=0; EthernetClient client = server.available();

 if (client) {  boolean currentLineIsBlank = true; String buffer = "";

 while (client.connected()) { if (client.available()) { char c = client.read();buffer+=c;

 if (c == '\n' && currentLineIsBlank) { client.println("HTTP/1.1 200 OK");

 if(kk==0&&ki==0){client.println("Content-Type:text/html;charset=windows- 1251"); client.println(); client.println("<!DOCTYPE HTML>");       

client.println("<html><HEAD><TITLE>Web - server Arduino Mega Ethernet Shield W5100</TITLE></HEAD><BODY>");  client.print(FORM);

writ(client); // Передаче текста браузеру

 client.println("</BODY></html> "); }

 if(kk==0 && ki==1) { client.println("Content-Type: image/jpeg"); client.println();

  w_pic(client);  // Передача рисунка браузеру       

   }    

  if(kk==0) goto lab;  

 // Формирование формы температура

  client.println("Content-Type: text/html; charset=utf-8"); client.println();  client.print("<b>Температура:</b><BR>"); client.print(FORM);

client.print("<INPUT type=\"HIDDEN\" name=\"t\" value=\"1\" size=2>");

client.print("<INPUT type=\"submit\" value=\"Температура\"> </FORM>");

client.print("Температура= "); // Распечатка температуры

client.print(sensors.getTempCByIndex(0)); client.print(" град.<BR>");         

// Формирование  формы «Управление  освещением»        

client.print("<br><b>Управление освещением:</b><BR>"); client.print(FORM);

client.print("<INPUT type=\"PASSWORD\" name=\"d\" value=\"\" size=9>");

client.print("<INPUT type=\"submit\" value=\"Включить\"> </FORM>");

client.print(FORM);

client.print("<INPUT type=\"PASSWORD\" name=\"d\" value=\"\" size=9>");

client.print("<INPUT type=\"submit\" value=\"Выключить\"> </FORM>");

if (digitalRead(7)){client.print("Освещение включено<br><br>");}

else { client.print("Освещение выключено<br><br>");  }

client.print(FORM);

client.print("<INPUT type=\"HIDDEN\" name=\"h\" value=\"0\" size=2>");

client.print("<INPUT type=\"submit\" value=\"Перейти к главной странице\"> </FORM>");        

 lab:  break; // После передачи данных, выйти из while и закрыть соединение

}

if (c == '\n') { currentLineIsBlank = true; buffer="";   } else if (c == '\r') {

if(buffer.indexOf("GET /1.jpg")>=0)  {ki=1;} 

if(buffer.indexOf("GET / HTTP")>=0)  {kk=0;}

if(buffer.indexOf("GET /?h=1")>=0)  kk=1;

if(buffer.indexOf("GET /?h=0")>=0)  kk=0;

if(buffer.indexOf("GET /?t=1")>=0) {sensors.requestTemperatures();}

if(buffer.indexOf("GET /?d=1361380")>=0) { digitalWrite(7,LOW); }       

if(buffer.indexOf("GET /?d=1361381")>=0) { digitalWrite(7,HIGH);  }  

   }     else { currentLineIsBlank = false; }

   }   } delay(10);

    client.stop(); // Закрыть соединение

  }  }

         Подробное описание, как работает эта программа, можно найти в источнике[4]. Подготовка байтового представления текста и изображения можно выполнить с помощью утилиты makefsdata.exe[5]. Для этого в рабочем каталоге создается подкаталог fs и записывается туда html - документ и рисунок. В рабочем каталоге должна находиться также утилита makefsdata.exe. После её запуска в рабочем каталоге появится файл fsdata.c, из которого и следует скопировать массивы байт, которые отдельно представлены для текста и изображения.

         Работа с библиотекой Flash показала, что как только размер программного кода, загружаемого в Arduino mega, превышал для данной задачи ~88000Байт, сервер полностью зависал. Согласно описанию микроконтроллера ATmega1280 и работе Flash библиотеки память разбивается на две части: 64Кбайт – память кода, 64Кбайт – память данных. Поэтому можно предположить, если данные (текст + рисунок) занимают 64Кбайт, то на код остается ~24Кбайт. Следовательно, код программы можно увеличить примерно ещё ~40Кбайт, но размер данных для сервера с помощью этой библиотеки увеличить не удастся. Особенно это актуально для микроконтроллера ATmega2560, у которого больше половины памяти останется не задействованной.

         Для решения задачи размещения данных во всей доступной flash – памяти рассмотрим второй случай – непосредственное использование библиотеки  AVR libc. Для ее подключения необходимо в начале программы записать

#include <avr/pgmspace.h>

Для размещения массива байт из файла pgmspace.h можно воспользоваться следующими описаниями

#define  pgm_read_byte(address_short)        pgm_read_byte_near(address_short)

#define  pgm_read_byte_near(address_short)   __LPM((uint16_t)(address_short))

- читает байт с flash памяти  коротким адресом в проеделах 64КБайт

#define  pgm_read_byte_far(address_long)   __ELPM((uint32_t)(address_long))

- читает байт с flash памяти  “дальним” адресом за пределами  64КБайт

 

#define        __LPM(addr)   __LPM_classic__(addr)

#define        __ELPM(addr)   __ELPM_classic__(addr)

Здесь __LPM_classic__(addr)  – макрос, который предназначен для чтения байта с памяти программ, используя 16-и битный адрес (т.е. в пределах 64КБайт). Его можно представить следующим образом:

#define __LPM_classic__(addr) 

(__extension__({                \

    uint16_t __addr16 = (uint16_t)(addr); \

    uint8_t __result;           \

    __asm__                     \

    (                           \

        "lpm" "\n\t"            \

        "mov %0, r0" "\n\t"     \

        : "=r" (__result)       \

        : "z" (__addr16)        \

        : "r0"                  \

    );                          \

    __result;                   \

}))

__ELPM_classic__(addr)  – макрос, который предназначен для чтения байта с памяти программ, используя 32-х битный адрес (т.е. за пределами 64КБайт). Его можно представить следующим образом:

#define __ELPM_classic__(addr)

(__extension__({                    \

    uint32_t __addr32 = (uint32_t)(addr); \

    uint8_t __result;               \

    __asm__                         \

    (                               \

        "out %2, %C1" "\n\t"        \

        "mov r31, %B1" "\n\t"       \

        "mov r30, %A1" "\n\t"       \

        "elpm" "\n\t"               \

        "mov %0, r0" "\n\t"         \

        : "=r" (__result)           \

        : "r" (__addr32),           \

          "I" (_SFR_IO_ADDR(RAMPZ)) \

        : "r0", "r30", "r31"        \

    );                              \

    __result;                       \

}))

В файле pgmspace.h, который включен в компилятор WinAVR модификации 2010-01-20, нет макроса для вычисления 32-х битного “дальнего” адреса (за пределами 64КБайт). Это может выполнить следующий макрос[6], который должен быть вставлен в программу сервера:

#define FAR(var)                     \

({ uint_farptr_t tmp;                \

   __asm__ (                         \

       "ldi    %A0, lo8(%1)"  "\n\t" \

       "ldi    %B0, hi8(%1)"  "\n\t" \

       "ldi    %C0, hh8(%1)"         \

       : "=d" (tmp)                  \

       : "i"  (&(var)));             \

   tmp;                              \

})

При компиляции необходимо указать компоновщику, в каких сегментах необходимо разместить массивы данных. Для этого при задании массивов с помощью #define описываются  сегменты в части flash памяти, где расположен код программы  и следующий 64-х килобайтный блок:

byte tex[] __attribute__((section(".my_section")))   =

{0x3c,0x68,0x74,0x6d,0x6c,0x20,0x78,0x6d,0x6c,0x6e,0x73,0x3a,0x6f,…};

byte pic[] __attribute__((section(".far_section")))   =

{0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,…};

Впоследствии при компоновке необходимо воспользоваться опциями:

-Wl,--section-start=.my_section=0x5600 -Wl,--section-start=.far_section=0x10000

Секция  .my_section будет располагать массив tex[] с начального адреса 0x5600, который должен следовать за кодом программы.  Секция  .far_section будет располагать массив pic[] с начального адреса 0x10000 в следующем блоке размером 64Кбайт.

Таким образом, начальная часть программы должна быть видоизменена так:

#include <avr/pgmspace.h>

#include <SPI.h>

#include <Ethernet.h>

#include <OneWire.h>      //Подключаем описание библиотеки шины OneWire

#include <DallasTemperature.h> // Библиотека для температуры (DS18B20)

#define FORM "<FORM action=\"\" >"

#define FAR(var)                     \

({ uint_farptr_t tmp;                \

   __asm__ (                         \

       "ldi    %A0, lo8(%1)"  "\n\t" \

       "ldi    %B0, hi8(%1)"  "\n\t" \

       "ldi    %C0, hh8(%1)"         \

       : "=d" (tmp)                  \

       : "i"  (&(var)));             \

   tmp;                              \

})

OneWire oneWire(6);       //Настройка шины для работы с 6-м выводом Ардуино

DallasTemperature sensors(&oneWire); //Подключаем датчик температуры

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

IPAddress ip(192,168,1,10);

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

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

EthernetServer server(8080);

int kk=0;

byte tex[]  __attribute__((section(".my_section")))   =

{0x3c,0x68,0x74,0x6d,0x6c,0x20,0x78,0x6d,0x6c,0x6e,0x73,0x3a,0x6f,…};

byte pic[]  __attribute__((section(".far_section")))   =

{0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,…};

void writ(EthernetClient client) // Функция передачи 64-х байтных блоков текста

{int ii=0,cC=0; byte cB[64]; while ( ii < sizeof(tex)) {cB[cC]=pgm_read_byte(tex+ii);cC++; if(cC > 63) { client.write(cB,64); cC=0; } ii++; }  if(cC > 0) client.write(cB,cC); }

void w_pic(EthernetClient client) // Функция передачи изображения

{int ii=0,cC=0; byte cB[64]; while ( ii < sizeof(pic)) { cB[cC]=pgm_read_byte_far(FAR(pic)+ii); cC++; if(cC > 63) { client.write(cB,64); cC=0; } ii++; } if(cC > 0) client.write(cB,cC); }

void setup()

… // Далее программа повторяется

Компиляция программы выполняется в программной среде Ардуино стандартным образом. Однако компоновщик для большого объема данных в конце компиляции может выдать примерно такие ошибки:

../avr/bin/ld.exe: webserv.cpp.elf section  .far_section will not fit in region data

../avr/bin/ld.exe: region data overflowed by 14023 bytes

В этом случае компоновку образа и формирование из него файла .hex необходимо выполнить вручную. Для этого на компьютере должен быть отдельно установлен компилятор WinAVR. А в каталог, в который Ардуино записывает файлы после компиляции, необходимо скопировать программатор avrdude.exe и его конфигурационный файл avrdude.conf. В рассматриваемом случае заходим во временный каталог, который был создан Ардуино:

C:\Documents and Settings\alex\Local Settings\Temp\build7500246830097907008.tmp

Выполняем компоновку с формированием файла .hex:

avr-gcc -Os -Wl,--gc-sections -Wl,--section-start=.my_section=0x5600 -Wl,--section-start=.far_section=0x10000 -mmcu=atmega1280 -o ws.cpp.elf ws.cpp.o SPI\SPI.cpp.o Ethernet\Dhcp.cpp.o Ethernet\Dns.cpp.o Ethernet\Ethernet.cpp.o Ethernet\EthernetClient.cpp.o Ethernet\EthernetServer.cpp.o Ethernet\EthernetUdp.cpp.o Ethernet\utility\socket.cpp.o Ethernet\utility\w5100.cpp.o OneWire\OneWire.cpp.o DallasTemperature\DallasTemperature.cpp.o core.a -LC:. -lm

avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 ws.cpp.elf ws.cpp.eep

avr-objcopy -O ihex -R .eeprom  ws.cpp.elf ws.cpp.hex

С помощью программатора прошиваем микроконтроллер:

avrdude -C avrdude.conf -patmega1280 -carduino -PCOM5 -b57600 -D -Uflash:w:ws.cpp.hex:i

 

Выводы

1. Показано, что программная среда Ардуино не позволяет использовать всю память программ микроконтроллера для размещения данных. Особенно это актуально для микроконтроллеров ATmega1280 и ATmega2560, где можно разместить соответственно 128 и 256Кбайт данных и кода.

2. В работе показано как,  используя среду Ардуино и библиотеку AVR libc , возможно размещение во всей программной памяти 8-и разрядных микроконтроллеров AVR массивов данных, которые представляют собой  html странички web – сервера.

3. Несмотря на простоту создания с помощью программной среды Ардуино полностью функционального web – сервера, существенным недостатком является его зависания при удаленной работе по сети Интернет. Замечено, что чем неустойчивее канал связи, тем чаще зависания. Причем зависания  происходят из-за библиотек сетевой поддержки сервера.

 

Литература

1. Arduino. [Electronic resource]. -  Mode of access:    http://robocraft.ru/blog/arduino/14.html, 2010.

2. A Library to Ease Accessing Flash-based (PROGMEM) Data. [Electronic resource]. -  Mode of access:   http://arduiniana.org/libraries/flash/, 2014.

3. AVR libc. [Electronic resource]. -  Mode of access:    http://www.nongnu.org/avr-libc/user-manual/pgmspace_8h.html, 2012.

4. Программа для web-сервера на микроконтроллере ATmega32 и модуле  WIZ812MJ. [Electronic resource]. -  Mode of access: http://webstm32.sytes.net/web_2a.htm, 2013.

5.Мясищев А.А. Web – сервер на платах STM32F4Discovery и STM32F4DIS-BB для удаленного управления по TCP/IP сети. [Electronic resource]. -  Mode of access:  http://alex56ma.zapto.org/stm32_web/stm32_3.html, 2014.

6. AVR-GCC-Tutorial.   [Electronic resource]. -  Mode of access:    http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmspeicher_.28Flash.29