Современные информационные
технологии/Компьютерная инженерия
Мясищев А.А.
Хмельницкий национальный
университет, Украина
Способ размещения данных в 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