- Web - сервер на STM32F4Discovery для удаленного управления
Web – сервер на платах STM32F4Discovery и STM32F4DIS-BB для удаленного управления по TCP/IP сети
Практика для студентов. Мясищев А.А.
Во 2-й статье рассматривался сервер, который позволял отображать лишь статические web - странички. Рассмотрим создание сервера на основе демонстрационного примера, который выполняет четырёхканальное управление удаленными устройствами (4-я светодиодами).
Добавим к предыдущей web – странички 4-е кнопки, которые позволяют включать и выключать независимые устройства (4-е светодиода, установленные на платеSTM32F4Discovery). Причем на web – страничке должна появляться информация о включенных и выключенных каналах (светодиодах).
Но перед этим модернизируем demo программу web – сервера таким образом, чтобы можно было компилировать ее на IAR Embedded Workbench for ARM, но с лицензией Kickstart, которая ограничивает размер кода программ 32килобайтами. Это можно сделать, так как в примере задействовано использование LCD, протоколDHCP.
Удаляем LCD. Для его выключения заходим в main.h и закомментируем USE_LCD:
Удаляем файл stm32f4_discovery_lcd.c
В файле stm32f4x7_eth_bsp.c закомментируем 76, 77, 78 строки:
Аналогично удаляем файл dhcp.c, а в файле etharp.c закомментируем 770 строку.
После этих корректировок размер кода будет равен 30918 Байт. Максимальный размер кода программы не должен превышать 32768 Байт. Таким образом, остается 32768-30918 = 1850 Байт свободного кода для модернизации программы.
Сформируем на начальной страничке index.html 4-е кнопки с использованием тега FORM и метода передачи GET:
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" />
<TITLE>Пример web - сервера на STM32F4Discovery</TITLE>
</HEAD>
<BODY>
<H2>Ниже показано фото платы STM32F4Discovery</H2>
<img src="foto_stm32.jpg">
<br><br>
<table>
<tr>
<td>
<B><font color="green">LED1 ON</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led1" value="1" size=2>
<INPUT type="submit" value="LED1 ON"> </FORM>
</td>
<td>
<B><font color="red">LED2 ON</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led2" value="1" size=2>
<INPUT type="submit" value="LED2 ON"> </FORM>
</td>
<td>
<B><font color="blue">LED3 ON</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led3" value="1" size=2>
<INPUT type="submit" value="LED3 ON"> </FORM>
</td>
<td>
<B><font color="broun">LED4 ON</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led4" value="1" size=2>
<INPUT type="submit" value="LED4 ON"> </FORM>
</td>
</tr>
<tr>
<td>
<B><font color="green">LED1 OFF</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led10" value="0" size=2>
<INPUT type="submit" value="LED1 OFF"> </FORM>
</td>
<td>
<B><font color="red">LED2 OFF</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led20" value="0" size=2>
<INPUT type="submit" value="LED2 OFF"> </FORM>
</td>
<td>
<B><font color="blue">LED3 OFF</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led30" value="0" size=2>
<INPUT type="submit" value="LED3 OFF"> </FORM>
</td>
<td>
<B><font color="broun">LED4 OFF</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led40" value="0" size=2>
<INPUT type="submit" value="LED4 OFF"> </FORM>
</td>
</tr>
</table>
</BODY>
</HTML>
Как и в предыдущем случае поместим этот файл под именем index.html в каталог fs, где находится рисунок foto_stm32.jpg. Запустим утилиту makefsdata.exe, которая сформирует файл fsdata.c Скопируем этот файл в каталог D:\STM32F4DIS-BB Discover More Software Examples\STM32F4DIS-BB Discover More Software Examples\STM32F4xx_Ethernet_Example\Project\Standalone\httpserver\src Перед компиляцией программы отредактируем файл httpd_cgi_ssi.c. Меняем всю выделенную функцию:
на представленную ниже:
const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
uint32_t i=0;
STM_EVAL_LEDInit(LED3);
STM_EVAL_LEDInit(LED4);
STM_EVAL_LEDInit(LED5);
STM_EVAL_LEDInit(LED6);
/* We have only one SSI handler iIndex = 0 */
if (iIndex==0) {
/* Check cgi parameter : example GET /leds.cgi?led=2&led=4 */
// Проверяем параметры CGI
for (i=0; i<iNumParams; i++) {
/* check parameter "led" */
if (strcmp(pcParam[i] , "led1")==0) {
STM_EVAL_LEDOn(LED4); j0=1;
}
else if (strcmp(pcParam[i] , "led2")==0) {
STM_EVAL_LEDOn(LED5); j1=1;
}
else if (strcmp(pcParam[i] , "led3")==0) {
STM_EVAL_LEDOn(LED6); j2=1;
}
else if (strcmp(pcParam[i] , "led4")==0) {
STM_EVAL_LEDOn(LED3); j3=1;
}
else if (strcmp(pcParam[i] , "led10")==0) {
STM_EVAL_LEDOff(LED4); j0=2;
}
else if (strcmp(pcParam[i] , "led20")==0) {
STM_EVAL_LEDOff(LED5);j1=2;
}
else if (strcmp(pcParam[i] , "led30")==0) {
STM_EVAL_LEDOff(LED6); j2=2;
}
else if (strcmp(pcParam[i] , "led40")==0) {
STM_EVAL_LEDOff(LED3); j3=2;
}
}
}
/* uri to send after cgi call*/
return "/index.html";
}
И добавляем описание переменных в начале файла httpd_cgi_ssi.c:
#include <string.h>
#include <stdlib.h>
tSSIHandler ADC_Page_SSI_Handler;
uint32_t ADC_not_configured=1;
uint32_t j0=0,j1=0,j2=0,j3=0; // Переменные для кнопок
Далее компилируем программу и загружаем ее в микроконтроллер. При подключении по адресу http://192.168.1.10:8080 в браузере появится следующее изображение:
Недостатком сервера с удаленным управлением является отсутствие информации о том, какое устройство находится во включенном и выключенном состоянии. Необходимо изменить программу так, чтобы под кнопками высвечивалась надпись, в каком состоянии находятся светодиоды (включен или выключен). Для этого можно использовать технологию SSI (Server Side Include), которую поддерживает demo сервер. По этой технологии сервер работает следующим образом. Если в htmlдокументе существует строка вида:
<!--#t-->
которую называют tag, то вместо нее будет подставлена строка, которая записана в буфере *pcInsert.
Выбор символа для tag прописан в файле httpd_cgi_ssi.c:
/* we will use character "t" as tag for CGI */
char const* TAGCHAR="t";
char const** TAGS=&TAGCHAR;
Максимальный размер этого буфера (192байта) задается в файле httpd.h:
/* The maximum length of string that can be returned to replace any given tag */
#ifndef LWIP_HTTPD_MAX_TAG_INSERT_LEN
#define LWIP_HTTPD_MAX_TAG_INSERT_LEN 192
Для того чтобы SSI работал для выбранной странички, ее имя должно иметь расширение .shtml. Поэтому имя нашей модернизированной странички будет index.shtml. Ниже представлен ее фрагмент, полученный из index.html:
<td>
<B><font color="broun">LED4 OFF</font></B>
<FORM method="get" action="/leds.cgi" >
<INPUT type="HIDDEN" name="led40" value="0" size=2>
<INPUT type="submit" value="LED4 OFF"> </FORM>
</td>
</tr>
</table>
<!--#t-->
</BODY>
</HTML>
Изменим текст файла httpd_cgi_ssi.c. Заменим в нем полностью функцию
u16_t ADC_Handler(int iIndex, char *pcInsert, int iInsertLen)
{
/* We have only one SSI handler iIndex = 0 */
if (iIndex ==0) {
char Digit1=0, Digit2=0, Digit3=0, Digit4=0;
uint32_t ADCVal = 0;
…
}
на:
u16_t ADC_Handler(int iIndex, char *pcInsert, int iInsertLen)
{
//Установка начальных состояний надписи для светодиодов
char Ins[]={'L','E','D','1','_','O','F','F',';',' ','L','E','D','2','_','O','F','F',';',
' ','L','E','D','3','_','O','F','F',';',' ','L','E','D','4','_','O','F','F',';',' '};
for(char ii=0; ii<=39;ii++)
*(pcInsert+ii)=Ins[ii];
/* We have only one SSI handler iIndex = 0 */
if (iIndex ==0) {
/* prepare data to be inserted in html */
// Подготавливаем данные для вывода с использованием SSI
if (j0==1){
*(pcInsert + 0) ='L';
*(pcInsert + 1) = 'E';
*(pcInsert + 2) = 'D';
*(pcInsert + 3) = '1';
*(pcInsert + 4) ='_';
*(pcInsert + 5) = '_';
*(pcInsert + 6) = 'O';
*(pcInsert + 7) = 'N';
*(pcInsert + 8) = ';';
*(pcInsert + 9) = ' ';
}
if (j0==2){
*(pcInsert + 0) ='L';
*(pcInsert + 1) = 'E';
*(pcInsert + 2) = 'D';
*(pcInsert + 3) = '1';
*(pcInsert + 4) ='_';
*(pcInsert + 5) = 'O';
*(pcInsert + 6) = 'F';
*(pcInsert + 7) = 'F';
*(pcInsert + 8) = ';';
*(pcInsert + 9) = ' ';
}
if (j1==1){
*(pcInsert + 10) ='L';
*(pcInsert + 11) = 'E';
*(pcInsert + 12) = 'D';
*(pcInsert + 13) = '2';
*(pcInsert + 14) ='_';
*(pcInsert + 15) = '_';
*(pcInsert + 16) = 'O';
*(pcInsert + 17) = 'N';
*(pcInsert + 18) = ';';
*(pcInsert + 19) = ' ';
}
if (j1==2){
*(pcInsert + 10) ='L';
*(pcInsert + 11) = 'E';
*(pcInsert + 12) = 'D';
*(pcInsert + 13) = '2';
*(pcInsert + 14) ='_';
*(pcInsert + 15) = 'O';
*(pcInsert + 16) = 'F';
*(pcInsert + 17) = 'F';
*(pcInsert + 18) = ';';
*(pcInsert + 19) = ' ';
}
if (j2==1){
*(pcInsert + 20) ='L';
*(pcInsert + 21) = 'E';
*(pcInsert + 22) = 'D';
*(pcInsert + 23) = '3';
*(pcInsert + 24) ='_';
*(pcInsert + 25) = '_';
*(pcInsert + 26) = 'O';
*(pcInsert + 27) = 'N';
*(pcInsert + 28) = ';';
*(pcInsert + 29) = ' ';
}
if (j2==2){
*(pcInsert + 20) ='L';
*(pcInsert + 21) = 'E';
*(pcInsert + 22) = 'D';
*(pcInsert + 23) = '3';
*(pcInsert + 24) ='_';
*(pcInsert + 25) = 'O';
*(pcInsert + 26) = 'F';
*(pcInsert + 27) = 'F';
*(pcInsert + 28) = ';';
*(pcInsert + 29) = ' ';
}
if (j3==1){
*(pcInsert + 30) ='L';
*(pcInsert + 31) = 'E';
*(pcInsert + 32) = 'D';
*(pcInsert + 33) = '4';
*(pcInsert + 34) ='_';
*(pcInsert + 35) = '_';
*(pcInsert + 36) = 'O';
*(pcInsert + 37) = 'N';
*(pcInsert + 38) = ';';
*(pcInsert + 39) = ' ';
}
if (j3==2){
*(pcInsert + 30) ='L';
*(pcInsert + 31) = 'E';
*(pcInsert + 32) = 'D';
*(pcInsert + 33) = '4';
*(pcInsert + 34) ='_';
*(pcInsert + 35) = 'O';
*(pcInsert + 36) = 'F';
*(pcInsert + 37) = 'F';
*(pcInsert + 38) = ';';
*(pcInsert + 39) = ' ';
}
/* 40 символов будет помещено вместо t*/
return 40;
}
return 0;
}
А в функции const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) вместо
return "/index.html";
следует поставить
return "/index.shtml";
После компиляции, пересылки файла в микроконтроллер и подключению по адресу http://192.168.1.10:8080 в браузере клиента появиться следующее изображение:
Выводы:
1. Отсутствуют зависания web – сервера на STM32F4Discovery и STM32F4DIS-BB.
2. По сравнению с web – сервером на Arduino + Ethernet Shield W5100 этот сервер работает значительно быстрее.
3. За счет большой флешь, оперативной памяти у STM32F407VGT6 сервер можно сделать с большим количеством каналов управления, датчиков и более информативным по сравнению с Arduino.
4. Стек протоколов TCP/IP для STM32F407VGT6 работает более надежно, чем для Arduino + Ethernet Shield W5100. Например, при относительно высокой загрузке сети для сервера на Arduino не всегда все данные доходили до клиента, а для сервера с протоколом LwIP таких проблем не замечалось.
5. Однако следует отметить большую сложность создания систем управления по TCP/IP сети с микроконтроллерами STM32 по сравнению с AVR (Arduino).