Продолжаем работу со стеком протоколов LWIP, а также с его интерфейсом SOCKET.
И сегодня мы начнём работать с протоколом уже прикладного уровня — HTTP. Данный протокол находится выше уровнем, чем протокол TCP, а протокол TCP является для протокола HTTP протоколом транспортного уровня. Со всем этим мы уже знакомы, так как с протоколом HTTP мы работаем давно и нет смысла углубляться в его особенности, а также в то, насколько в нынешний век интернета он востребован.
И скорее всего, мы также пойдём последовательным путём, как и в случае использования интерфейса NETCONN, хотя в данный момент всё это не так актуально. Уроки по стеку протоколов LWIP нацелены больше на будущее, так как не все посетители ещё дошли до данной темы. Но у меня уже проект отработан, поэтому я всё-таки помимо урока по упрощённой работе с протоколом HTTP дам пару уроков и по использованию технологий AJAX и Websocket. Что предусматривают данные технологии, можете почитать в уроках по NETCONN.
А сейчас проект, который был сделан из проекта урока 133 с именем LAN8742_TCP_SERVER_SOCKET и имя ему было дано LAN8742_HTTP_SERVER_SOCKET.
Запустим проект в Cube MX и перейдём в настройку LWIP. В разделе HTTPD включим сервер
Сохраним настройки, сгенерируем проект и откроем его в среде System Workbench.
Зайдём в настройки проекта, уровень оптимизации установим в 1 и удалим при наличии отладочные настройки.
Перейдём в файл main.c и сначала уберём так же, как и в уроке 134, массивы на SDRAM.
Удалим здесь
unsigned char *out_buffer;
char *str_usart;
char *str_out;
unsigned char *send_ws_buf;
char* str_buf1;
char* str_buf2;
char* str_buf3;
Вместо этого добавим глобальные массивы в обычной ОЗУ
1 2 3 4 |
#define MAIL_SIZE (uint32_t) 5 char str_usart[512] = {}; char str_out[32] = {}; char str_buf1[30] = {}; |
Также в функции main() удалим инициализацию массивов в памяти SDRAM
//str_usart 512 bytes
str_usart = (char *) 0xC0400000;
//str_out 32 bytes
str_out = (char *) 0xC0400200;
//out_buffer 192 bytes
out_buffer = (uint8_t *) 0xC0400220;
memset(out_buffer,0,192);
//send_ws_buf 1312 bytes
send_ws_buf = (uint8_t *)0xC04002E0;
//str_buf1 256 bytes
str_buf1 = (char *) 0xC0400800;
//str_buf2 256 bytes
str_buf2 = (char *) 0xC0400900;
//str_buf2 1024 bytes
str_buf3 = (char *) 0xC0400A00;
В функции main() исправим первым делом шапку проекта
TFT_DisplayString(0, 10, (uint8_t *)"HTTP Server", CENTER_MODE);
Как мы помним, проект у нас не сможет собраться из-за отсутствия файла fsdata.c, который мы хорошо помним, как надо генерировать, так как с сервером HTTP нам работать не привыкать. Весь состав документального контента мы возьмём тот же, что и в уроке 127. Скопируем папку fs, сохранённую для данного урока, вместе с содержимым, а также файлы makefsdata.exe, makefsdata.cmd и msvcr100d.dll, чтобы мы могли свободно собирать недостающий файл с контентом, в папку «Папка с проектом//Middlewares/Third_Party/LwIP/src/apps/httpd/». Также в папку fs помести файл favicon.ico, который вы сами себе сгенерируйте под себя. Это иконка сайта, которая отображается в закладке с браузером вместе с именем документа. Если этот файл поместить, то серверу не придется постоянно отвечать браузерам об его отсутствии. Для генерации данного файла существует ряд онлайн-сервисов.
Соберём с помощью файла makefsdata.cmd файл fsdata.c и вернёмся в проект.
Сделаем проекту Refresh и отключим от компиляции наш файл fsdata.c.
Попробуем теперь собрать проект, проект должен будет нормально собраться.
Даже если проект соберётся нормально, с виртуальной файловой системой мы работать не сможем.
Для этого надо будет подключить библиотеку
1 2 |
#include "lwip/sockets.h" #include "lwip/apps/fs.h" |
Уберём объявление глобальных структур и переменных их типов, которые нам сегодня не потребуются
typedef struct struct_conn_t {
uint32_t conn;
uint8_t type;
} struct_conn;
struct_conn conn01;
typedef struct struct_client_socket_t {
struct sockaddr_in remotehost;
socklen_t sockaddrsize;
int accept_sock;
uint16_t y_pos;
} struct_client_socket;
struct_client_socket client_socket01;
Удалим также функцию client_socket_thread со всем содержимым.
Мы не будем делать отдельные задачи под соединения, так как в этом случае непременно будут глюки. Я пробовал. Обойдёмся одной задачей на всё, как мы это делали в случае использования API NETCONN.
Также в функции задачи вывода строк на дисплей очистим память очереди
1 2 |
TFT_DisplayString(qstruct->x_pos, qstruct->y_pos, (uint8_t *)str_out, LEFT_MODE); osMailFree(strout_Queue, qstruct); |
В функции задачи по умолчанию StartDefaultTask исправим номер порта
sock01.port = 80;
Также при создании задачи для сокета увеличим стек
sys_thread_new("tcp_thread", tcp_thread, (void*)&sock01, DEFAULT_THREAD_STACKSIZE * 4, osPriorityNormal);
В функции tcp_thread создадим массив структур для передачи в очередь строки в целях последующего вывода её на дисплее.
Мы будем использовать для каждой строки отдельную структуру, иначе наши строки потеряются при удалении элемента из очереди
1 2 |
struct_sock *arg_sock; struct_out *qstruct[4]; |
Добавим ещё одну локальную переменную
int sock, accept_sock, ret;
Добавим буфер и переменную для хранения длины буфера
1 2 3 |
int sock, accept_sock, ret; int buflen = 512; char buf[512] = {}; |
Объявим переменную файловой структуры
1 2 |
char buf[512] = {}; struct fs_file file; |
Немого добавим количество элементов в очередь для прослушивания
listen(sock, 8);
Уберём вывод в терминальную программу
sprintf(str_usart,» socket: %d\r\n», accept_sock);
HAL_UART_Transmit(&huart1,(uint8_t*)str_usart,strlen(str_usart),0x1000);
Удалим тело следующего условия
if(accept_sock >= 0)
{
client_socket01.accept_sock = accept_sock;
client_socket01.remotehost = remotehost;
client_socket01.sockaddrsize = sockaddrsize;
client_socket01.y_pos = arg_sock->y_pos;
sys_thread_new(«client_socket_thread», client_socket_thread, (void*)&client_socket01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );
}
Попытаемся принять пакет от клиента, если в нём есть данные, то заготовим под этот вариант условие с телом, и затем, независимо от выполнения условия, закроем соединение с клиентом
1 2 3 4 5 6 7 |
if(accept_sock >= 0) { ret = recvfrom(accept_sock, buf, buflen, 0, (struct sockaddr *)&remotehost, &sockaddrsize); if(ret > 0) { } close(accept_sock); |
В теле условия добавим ещё одно условие, проверяющее то, что пришел именно запрос HTTP
1 2 3 4 5 |
if(ret > 0) { if ((ret >=5) && (strncmp(buf, "GET /", 5) == 0)) { } |
Если пришёл запрос HTTP, выведем надпись внизу дисплея
1 2 3 4 5 6 7 8 |
if ((ret >=5) && (strncmp(buf, "GET /", 5) == 0)) { qstruct[0] = osMailAlloc(strout_Queue, osWaitForever); qstruct[0]->y_pos = 250; qstruct[0]->x_pos = 10; qstruct[0]->sfont = Font24; sprintf(qstruct[0]->str,"%-20s", "Connect"); osMailPut(strout_Queue, qstruct[0]); |
Ответим на запрос аналогичным образом, как мы делали в уроке по API NETCONN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
osMailPut(strout_Queue, qstruct[0]); if ((strncmp((char const *)buf,"GET / ",6)==0)||(strncmp((char const *)buf,"GET /index.html",15)==0)) { fs_open(&file, "/index.html"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /index1.html",16)==0) { fs_open(&file, "/index1.html"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /favicon.ico",16)==0) { fs_open(&file, "/favicon.ico"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /IMG/img01.jpg",18)==0) { fs_open(&file, "/IMG/img01.jpg"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /IMG/img02.jpg",18)==0) { fs_open(&file, "/IMG/img02.jpg"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /IMG/img03.jpg",18)==0) { fs_open(&file, "/IMG/img03.jpg"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else if (strncmp((char const *)buf,"GET /IMG/img04.jpg",18)==0) { fs_open(&file, "/IMG/img04.jpg"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); } else { fs_open(&file, "/404.html"); write(accept_sock, (const unsigned char*)(file.data), (size_t)file.len); fs_close(&file); qstruct[1] = osMailAlloc(strout_Queue, osWaitForever); qstruct[1]->y_pos = arg_sock->y_pos + 40; qstruct[1]->x_pos = 10; qstruct[1]->sfont = Font24; sprintf(qstruct[1]->str,"%-20s", "file not found"); osMailPut(strout_Queue, qstruct[1]); } |
Если строка запроса больше 20 символов, то ограничим её для вывода на дисплей
1 2 3 4 |
osMailPut(strout_Queue, qstruct[1]); } if ((ret >=20)) buf[20] = 0; else buf[ret] = 0; |
Выведем строку на дисплей, а ниже — минимальный размер кучи
1 2 3 4 5 6 7 8 9 10 11 12 13 |
else buf[ret] = 0; qstruct[2] = osMailAlloc(strout_Queue, osWaitForever); qstruct[2]->y_pos = arg_sock->y_pos; qstruct[2]->x_pos = 10; qstruct[2]->sfont = Font24; sprintf(qstruct[2]->str,"%-20s", buf); osMailPut(strout_Queue, qstruct[2]); qstruct[3] = osMailAlloc(strout_Queue, osWaitForever); qstruct[3]->y_pos = arg_sock->y_pos + 80; qstruct[3]->x_pos = 10; qstruct[3]->sfont = Font24; sprintf(qstruct[3]->str,"%7u",xPortGetMinimumEverFreeHeapSize()); osMailPut(strout_Queue, qstruct[3]); |
Подключим нашу плату STM32F746G-DISCOVERY к ПК посредством кабеля mini-USB, также подключим плату к сети, соберём проект и прошьём контроллер.
Сначала запустим анализатор сетевого трафика Wireshark и отфильтруем его по адресу нашего сервера HTTP.
Запустим WEB-браузер (я использую для этих целей Mozilla Firefox, он мне кажется как-то стабильнее что-ли да и привык я к его отладке) и введём пока IP-адрес нашего сервера без наименования документа.
Должен будет отобразиться документ index.html
Также посмотрим, что у нас всё нормально в Wireshark
Теперь попробуем запросить страниц посложнее, с картинками — index1.html
Также прекрасно выводится весь документ целиком, картинка ни одна не теряется.
На дисплее также всё отображается, что говорит о том, что в очереди в одной функции при выводе разных строк, если ожидается вывод их друг за другом, всё-таки целесообразнее использовать под каждый вывод строки свою отдельную структуру
Попробуем сделать запрос несуществующего документа
Браузер отреагировал правильно, предоставив нам страницу с ошибкой.
На дисплее также появилась соответствующая надпись
Отлично!
Итак, воспользовавшись нашим накопленным опытом, а также удобством интерфейса SOCKET стека протоколов LWIP, мы, можно сказать, не прикладывая каких-то особых усилий, создали несложный, но очень уверенно функционирующий сервер HTTP.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день!
При попытке компиляции возникают ошибки с этими строчками:
__weak void configureTimerForRunTimeStats(void)
{
}
__weak unsigned long getRunTimeCounterValue(void)
{
Если перенести '♯ include' stm32f7xx_hal.h 'из main.c в main.h., то проблема пропадает и все запускается, однако после сервер на STM не подает признаков жизни и ничего не работает.
Можно ли этот как-то починить?
HI,
I have try your code in NUCLEO — F746ZG. there sDRAM not accessible and no TFT display.
and in freeRTOS handle
sys_thread_new(«tcp_thread», tcp_thread, (void*)&sock01, DEFAULT_THREAD_STACKSIZE * 4, osPriorityNormal);
is not working.