STM Урок 91. LAN. W5500. HTTP Server. Часть 5



 

Урок 91

 

Часть 5

 

LAN. W5500. HTTP Server

 

В предыдущей части урока мы продолжили функцию ответа на запрос HTTP, а также написали функцию передачи клиенту документа, умещающегося вместе с заголовком в одно окно.

 

Следом за этой функцией создадим функцию передачи первого буфера многооконного (многобуферного) документа

 

//-----------------------------------------------

void tcp_send_http_first(void)

{

  uint8_t prt;

  uint16_t i=0;

  uint16_t data_len=0;

  uint16_t header_len=0;

  uint16_t end_point;

  uint8_t num_sect=0;

  uint16_t len_sect;

  uint16_t last_part, last_part_size;

}

//-----------------------------------------------

 

Вызовем её в надлежащем месте в функции http_request

 

  else if(httpsockprop[tcpprop.cur_sock].data_stat==DATA_FIRST)

  {

    tcp_send_http_first();

  }

}

 

Причём, в отличие от вызова первой функции, мы здесь уже не закрываем соединение, так как это только первая часть нашего ответа HTTP.

Продолжим писать функцию отправки первой части нашего ответа.

Как и в предыдущей функции, мы по состоянию типа документа привяжем буфер заголовка к определённому массиву, определим его длину и также занесём в переменную длину данных, которая будет у нас равна размеру окна

 

uint16_t last_part, last_part_size;

//На EXISTING проверять не будем, так как будем считать, что error404_htm у нас всегда будет умещаться в один буфер

switch(httpsockprop[tcpprop.cur_sock].http_doc)

{

  case EXISTING_HTML:

    header = (void*)http_header;

    break;

  case EXISTING_ICO:

    header = (void*)icon_header;

    break;

  case EXISTING_JPG:

    header = (void*)jpg_header;

    break;

}

header_len = strlen(header);

data_len = tcp_size_wnd-header_len;

 

Как и указано в комментарии, мы будем считать, что если документ не существует, то страницу с ошибкой и соответствущим заголовком мы уже отправили в предыдущей функции, так как такой ответ у нас спокойно умещается в одном буфере.

Определим и передвинем указатель в буфере отправки

 

data_len = tcp_size_wnd-header_len;

end_point = GetWritePointer(tcpprop.cur_sock);

end_point+=header_len+data_len;

SetWritePointer(tcpprop.cur_sock, end_point);

end_point = GetWritePointer(tcpprop.cur_sock);

 

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

 

end_point = GetWritePointer(tcpprop.cur_sock);

//сохраним некоторые параметры, а то почему-то они теряются

last_part = httpsockprop[tcpprop.cur_sock].cnt_rem_data_part;

last_part_size = httpsockprop[tcpprop.cur_sock].last_data_part_size;

prt = httpsockprop[tcpprop.cur_sock].prt_tp;

 

Конечно, вместо структуры мы в будущем данные параметры передавать где-то в функциях, а там уже заносить в статические переменные, говорят, что они меньше так теряются. Но пока временно решим именно таким образом данную проблему. Такое решение также имеет право на жизнь.

Также как и в предыдущей функции, заполним буфер данными заголовка, запишем его в буфер отправки и передвинем указатель

 

prt = httpsockprop[tcpprop.cur_sock].prt_tp;

//Заполним данными буфер для отправки пакета

memcpy(sect+3,header,header_len);

w5500_writeSockBuf(tcpprop.cur_sock, end_point, (uint8_t*)sect, header_len);

end_point+=header_len;

num_sect = data_len / 512;

 

Аналогично предыдущей функции, мы  мы посчитаем количество секторов по 512 байт, добавим цикл, в котором прочитаем их с карты SD и запишем в буфер для передачи

 

num_sect = data_len / 512;

for(i=0;i<=num_sect;i++)

{

  //не последний сектор

  if(i<(num_sect-1)) len_sect=512;

  else len_sect=data_len;

  result=f_lseek(&MyFile,i*512); //Установим курсор чтения в файле

  sprintf(str1,"f_lseek: %drn",result);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  result=f_read(&MyFile,sect+3,len_sect,(UINT *)&bytesread);

  w5500_writeSockBuf(tcpprop.cur_sock, end_point, (uint8_t*)sect, len_sect);

  end_point+=len_sect;

  data_len -= len_sect;

}

 

Теперь мы вернём значения полей структуры, которые мы сохранили выше

 

  data_len -= len_sect;

}

//вернем параметры

httpsockprop[tcpprop.cur_sock].cnt_rem_data_part = last_part;

httpsockprop[tcpprop.cur_sock].last_data_part_size = last_part_size;

httpsockprop[tcpprop.cur_sock].prt_tp = prt;

 

Отправим данные клиенту

 

httpsockprop[tcpprop.cur_sock].prt_tp = prt;

//отправим данные

RecvSocket(tcpprop.cur_sock);

SendSocket(tcpprop.cur_sock);

 

 

Так как мы одну часть данных передали, то уменьшим количество оставшихся частей для передачи

 

SendSocket(tcpprop.cur_sock);

//будем считать, что одну часть отправили, поэтому количество оставшихся частей декрементируем

httpsockprop[tcpprop.cur_sock].cnt_rem_data_part--;

 

Далее мы, в зависимости от количества оставшихся частей определим, какая будет следующая часть — средняя или последняя

 

httpsockprop[tcpprop.cur_sock].cnt_rem_data_part--;

if(httpsockprop[tcpprop.cur_sock].cnt_rem_data_part>1)

{

  httpsockprop[tcpprop.cur_sock].data_stat=DATA_MIDDLE;

}

else

{

  httpsockprop[tcpprop.cur_sock].data_stat=DATA_LAST;

}

 

И по окончании функции мы посчитаем общее количество переданных байтов. Это нужно будет для следующих функций, чтобы там устанавливать указатель в читаемый файл в правильное место

 

    httpsockprop[tcpprop.cur_sock].data_stat=DATA_LAST;

  }

  //Количество переданных байтов

  httpsockprop[tcpprop.cur_sock].total_count_bytes = tcp_size_wnd - header_len;

}

 

Соберём код, прошьём контроллер и запросим в браузере страницу побольше, правда, скорее всего он нам её не покажет, но у нас есть WireShark, в котором все ходы, как говорится, записаны. И там мы и посмотрим, ушли ли от нас пакеты

 

image45

 

Данные переданы, подтверждение получено. Отлично!

Добавим сразу заготовки для функций передачи средней и последней части после только что написанной нами функции

 

//-----------------------------------------------

void tcp_send_http_middle(void)

{

  uint16_t i=0;

  uint16_t data_len=0;

  uint16_t end_point;

  uint8_t num_sect=0;

  uint8_t prt;

  uint16_t len_sect;

  uint16_t last_part, last_part_size;

  uint32_t count_bytes;

}

//-----------------------------------------------

void tcp_send_http_last(void)

{

  uint16_t i=0;

  uint16_t data_len=0;

  uint16_t end_point;

  uint8_t num_sect=0;

  uint16_t len_sect;

}

//-----------------------------------------------

 

Только вызывать мы данные функции будем уже не из функции ответа на запрос, а из функции принятия и обработки пакета TCP w5500_packetReceive. Если вы помните, мы специально там подготвили для этого условия. Только чтобы там вызвать наши функции, необходимо, чтобы они там были видны. Для этого добавим на них прототипы и затем перейдём в функцию w5500_packetReceive, найдя ещё в файле w5500.c, и вызовем наши функции

 

    else if(httpsockprop[tcpprop.cur_sock].data_stat==DATA_MIDDLE)

    {

      if(httpsockprop[tcpprop.cur_sock].prt_tp == PRT_TCP_HTTP)

      {

        tcp_send_http_middle();

      }

    }

    else if(httpsockprop[tcpprop.cur_sock].data_stat==DATA_LAST)

    {

      if(httpsockprop[tcpprop.cur_sock].prt_tp == PRT_TCP_HTTP)

      {

        tcp_send_http_last();

        DisconnectSocket(tcpprop.cur_sock); //Разъединяемся

        SocketClosedWait(tcpprop.cur_sock);

        OpenSocket(tcpprop.cur_sock,Mode_TCP);

        //Ждём инициализации сокета (статус SOCK_INIT)

        SocketInitWait(tcpprop.cur_sock);

        //Продолжаем слушать сокет

        ListenSocket(tcpprop.cur_sock);

        SocketListenWait(tcpprop.cur_sock);

      }

    }

  }

}

 

Ну и, понятное дело, после передачи последней части документа мы разъединяемся с клиентом и продолжаем слушать сокет.

 

 

Возвращаемся в файл httpd.c и сначала начнём писать тело функции передачи последней части документа. Так как мы сначала будем пробовать передавать документ, который уместится в две части, а затем уже более объёмные.

Занесём в переменную длину данных, опеределим адрес передачи данных и передвинем его

 

uint16_t len_sect;

data_len = httpsockprop[tcpprop.cur_sock].last_data_part_size;

end_point = GetWritePointer(tcpprop.cur_sock);

end_point+=data_len;

SetWritePointer(tcpprop.cur_sock, end_point);

end_point = GetWritePointer(tcpprop.cur_sock);

 

Посчитаем количество секторов по 512 байт, добавим цикл, в котором прочитаем их с карты SD и запишем в буфер для передачи

 

end_point = GetWritePointer(tcpprop.cur_sock);

//Заполним данными буфер для отправки пакета

num_sect = data_len / 512;

//борьба с неправильным расчётом, когда размер данных делится на размер сектора без остатка

if(data_len%512==0) num_sect--;

for(i=0;i<=num_sect;i++)

{

  //не последний сектор

  if(i<(num_sect-1)) len_sect=512;

  else len_sect=data_len;

  result=f_lseek(&MyFile, (DWORD)(i*512) + httpsockprop[tcpprop.cur_sock].total_count_bytes); //Установим курсор чтения в файле

  sprintf(str1,"f_lseek: %drn",result);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  result=f_read(&MyFile,sect+3,len_sect,(UINT *)&bytesread);

  w5500_writeSockBuf(tcpprop.cur_sock, end_point, (uint8_t*)sect, len_sect);

  end_point+=len_sect;

  data_len -= len_sect;

}

 

Как видно из текста комментария, здесь может случиться ситуация, когда у нас размер разделится на количество секторов без остатка. Может получиться неправильный рассчёт. И это мы здесь исправили.

Отправим наши данные и установим соответствующий статус

 

  data_len -= len_sect;

}

//отправим данные

RecvSocket(tcpprop.cur_sock);

SendSocket(tcpprop.cur_sock);

httpsockprop[tcpprop.cur_sock].data_stat = DATA_COMPLETED;

 

Соберём код, прошьём контроллер и посмотрим результат в браузере

 

image46

 

Страница полностью передалась. В этом случае у нас второй буфер умещается в один пакет.

Также попробуем передать страницу, в которой вторая часть уже не влезет в один буфер. Скриншот браузера я уже показывать не буду, так как данных становится всё больше и больше, так что можете мне поверить на словао, я покажу скриншот анализатора трафика

 

image47

 

Всё нормально передаётся и подтверждается. Соединение открывается и закрывается также корректно.

Теперь мы начнём писать тело функции передачи средней части документа.

Здесь немного попроще. Мы уже не передаём заголовок, так как мы его уже передали в последней части.

Пока опеределим адрес передачи данных и передвинем его

 

uint32_t count_bytes;

data_len = tcp_size_wnd;

end_point = GetWritePointer(tcpprop.cur_sock);

end_point+=data_len;

SetWritePointer(tcpprop.cur_sock, end_point);

end_point = GetWritePointer(tcpprop.cur_sock);

 

Сохраним опять некоторые параметры структуры

 

end_point = GetWritePointer(tcpprop.cur_sock);

//сохраним некоторые параметры, а то почему-то они теряются

last_part = httpsockprop[tcpprop.cur_sock].cnt_rem_data_part;

last_part_size = httpsockprop[tcpprop.cur_sock].last_data_part_size;

count_bytes = httpsockprop[tcpprop.cur_sock].total_count_bytes;

prt = httpsockprop[tcpprop.cur_sock].prt_tp;

 

Посчитаем количество секторов и запишем их в буфер передачи, предварительно считав с носителя

 

prt = httpsockprop[tcpprop.cur_sock].prt_tp;

//Заполним данными буфер для отправки пакета

num_sect = data_len / 512;

//борьба с неправильным расчётом, когда размер данных делится на размер сектора без остатка

if(data_len%512==0) num_sect--;

for(i=0;i<=num_sect;i++)

{

  //не последний сектор

  if(i<(num_sect-1)) len_sect=512;

  else len_sect=data_len;

  result=f_lseek(&MyFile,(DWORD)(i*512) + count_bytes); //Установим курсор чтения в файле

  result=f_read(&MyFile,sect+3,len_sect,(UINT *)&bytesread);

  w5500_writeSockBuf(tcpprop.cur_sock, end_point, (uint8_t*)sect, len_sect);

  end_point+=len_sect;

  data_len -= len_sect;

}

 

Вернём сохранённые ранее параметры в поля структуры

 

  data_len -= len_sect;

}

//вернем параметры

httpsockprop[tcpprop.cur_sock].cnt_rem_data_part = last_part;

httpsockprop[tcpprop.cur_sock].last_data_part_size = last_part_size;

httpsockprop[tcpprop.cur_sock].total_count_bytes = count_bytes;

httpsockprop[tcpprop.cur_sock].prt_tp = prt;

 

Передадим буфер клиенту

 

httpsockprop[tcpprop.cur_sock].prt_tp = prt;

RecvSocket(tcpprop.cur_sock);

SendSocket(tcpprop.cur_sock);

 

Уменьшим количество оставшихся частей для передачи и далее, в зависимости от количества оставшихся частей определим, какая будет следующая часть — средняя или последняя

 

  SendSocket(tcpprop.cur_sock);

//будем считать, что одну часть отправили, поэтому количество оставшихся частей декрементируем

httpsockprop[tcpprop.cur_sock].cnt_rem_data_part--;

if(httpsockprop[tcpprop.cur_sock].cnt_rem_data_part>1)

{

  httpsockprop[tcpprop.cur_sock].data_stat=DATA_MIDDLE;

}

else

{

  httpsockprop[tcpprop.cur_sock].data_stat=DATA_LAST;

}

 

И по окончании функции мы добавим количество переданных байтов документа

 

    httpsockprop[tcpprop.cur_sock].data_stat=DATA_LAST;

  }

  //Количество переданных байтов

  httpsockprop[tcpprop.cur_sock].total_count_bytes += (uint32_t) tcp_size_wnd;

}

 

Ну вроде и всё.

Соберём код, прошьём контроллер и попробуем передать различные документы разного размера, в том числе включающие и картинки

 

image48

 

Итак, сегодня мы с вами освоили ещё одну интересную микросхему, обеспечивающую передачу данных по сети LAN — W5500, которая позволяет работать со стеком TCP/IP на аппаратном уровне, что позволит сэкономить в очень значительной степени наши аппаратные ресурся, которых, как мы значем, очень нехватает зачастую.

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату можно приобрести здесь Nucleo STM32F401RE

и здесь Nucleo STM32F401RE

Ethernet LAN Сетевой Модуль можно купить здесь W5500 Ethernet LAN

Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN. W5500. HTTP Server

11 комментариев на “STM Урок 91. LAN. W5500. HTTP Server. Часть 5
  1. Дмитрий:

    Огромная работа проделана. Спасибо! Но было бы неплохо добавить 6-ю часть где разобрать удаленое управление GPIO с WEB страницы. Собственно ради этого и затевается вся история с LAN в микроконтроллерах. Практический смысл в просмотре файлов в браузере — отсутствует. Я года 2 назад пытался к этому подступиться (отпрос модулей по RS485 (USART) и отображение параметров в WEB + управление), но стабильности не достиг., постоянно сервер отваливался через какое то время. Использовал STM32F103RET6, CooCox и стандартные библиотеки от WizNet.

  2. Вадим:

    Очень благодарен за ваши уроки, хочу описать несколько граблей с которыми столкнулся.
    1 и в режиме ТСР и в UDP читать из буфера надо в два приема. То есть если прочитать по схеме
    rLen = GetSizeRX(soc_tcp); // пусть длинна блока == 56 байт
    if(!rLen) return;
    point = GetReadPointer(soc_tcp);
    w5500_readSockBuf(soc_tcp, point, (uint8_t*)tmpbuf, rLen);
    SetReadPointer(soc_tcp,point+dLen);
    без ошибок читается только 30 байт, дальше идет сбойный блок, то есть теряем 16 байт. В чем причина, и повторяется ли этот эффект я не знаю. у меня так. Решаю эту проблему так,
    point = GetReadPointer(soc_tcp);
    SetReadPointer(soc_tcp,point); // !!!! обновляю указатель
    w5500_readSockBuf(soc_tcp, point, (uint8_t*)tmpbuf, rLen);
    SetReadPointer(soc_tcp,point+dLen);
    это помогает. В UDP режиме этот эффект тоже есть, и там к блоку принимаемых данных добавляется служебный заголовок в 8 байт. Кроме того там бывают ситуации когда два и более пакетов склеиваются, то есть источник быстро отправляет два или более пакетов, а мы принимаем один большой.
    вот там приходится читать в два приема, читаем заголовок, извлекаем длину, читаем данные. Пример

    hLen =8;
    tLen = GetSizeRX(socProp.now_udp);
    if(!tLen) return;
    point = GetReadPointer(socProp.now_udp);
    do
    {
    w5500_readSockBuf(socProp.now_udp, point, (uint8_t*)tmpbuf, hLen);
    memcpy(udp_head,tmpbuf,hLen);
    dLen = (udp_head[6] << 8)|udp_head[7];
    rLen += hLen;
    point += hLen;
    SetReadPointer(soc_udp,point);
    w5500_readSockBuf(socProp.now_udp, point, (uint8_t*)tmpbuf, dLen);
    memcpy(DATA,tmpbuf,dLen);
    rLen += dLen; // Len all read Head + DATA
    point += dLen;
    SetReadPointer(socProp.now_udp,point);
    PrintHead((uint8_t*)udp_head);
    PrintData((uint8_t*)DATA,dLen);
    }
    while(rLen < tLen );
    SetRECV(soc_udp);

  3. Вадим:

    Что касается отправки ТСР пакетов то у вас в четвертой части говорилось,
    //Заполним данными буфер для отправки пакета
    SetWritePointer(tcpprop.cur_sock, end_point);
    end_point = GetWritePointer(tcpprop.cur_sock);
    казалось бы странная операция, зачем считывать известный указатель? Как оказалось что если так не делать, то после переоткрытия сокета, первый пакет передается с искажениями, а последующие нормально,
    но у вас в HTTP сервере сокет переоткрывется постоянно.

  4. Kvazibog:

    Добрый день, какой скорости удалось достичь в итоге? В моем проекте реальная скорость не превышает 150 кбит/с, что очень мало. Не подскажите в чем может быть проблема, пробовал разные микросхема из разных партий, эффект всегда одинаковый: 150 кбит/с на одном сокете, и по 75 на каждый если на 2 сокета.

  5. Sergey:

    Здравствуйте уважаемый автор. Часто в уроках вы говорите что нужно идти от простого к сложному. А с w5500 получилось как то так. От сложного к более сложному. Очень хотелось бы получить урок организации приема — передачи данных по UDP протоколу как это было с enc28j60. Причем с применением старой доброй stdperiph.

  6. Сергей:

    Давно пытаюсь подружить W5500 и STM32. Самому с нуля написать проект не получается, нет ещё достаточных знаний. Эти уроки очень ценны, но не получилось собрать проект из приложенного архива. После сборки через CybeMX(по совету автора) из проекта удалились многие файлы(w5500.c и h, net.c и т.д.) Скомпилировалось все без ошибок , но и толку нет. Попытался без CubeMX обойтись, подбросил в проект HAL библиотеки, но куча ошибок в которых не могу разобраться. Может есть у кого готовый проект для изучения Сервер/клиент на w5500/stm32? Был бы очень признателен за помощь.

  7. Владимир:

    Так, как я вашего е-мейла не нашел, напишу здесь. Исправьте 3 опечатки в конце этой страницы и удалите мое сообщение.

    «что позволит сэкономить в очень значительной степени наши аппаратные ресурся, которых, как мы значем, очень нехватает зачастую.»

    (ресурся — ресурсы, значем — знаем, нехватает — не хватает)

  8. Виктор:

    На этих двух сайтах даны примеры с W5500 с применением библиотек SPL.
    Но на вариант HAL эти примеры перекладываются неплохо.
    (Ссылки на внешние источники удалил. Обмен внешними ссылками производите в личных сообщениях)

  9. Ильяс:

    Пытаюсь освоить программирование по вашим урокам, огромная вам благодарность.
    Не все у меня проходит гладко, возможно связано с обновленной версией CubeMX.
    Может кому-то из начинающих помогут мои исправления, без которых проект не работал.

    1) В файле httpd.c в функциях отправки документа:

    void tcp_send_http_one(uint8_t sn),
    void tcp_send_http_first(uint8_t sn),
    void tcp_send_http_middle(uint8_t sn),
    void tcp_send_http_last(uint8_t sn)

    пришлось исправить:
    //не последний сектор
    //if(i<(num_sect-1)) len_sect=512;
    if(i<num_sect) len_sect=512;

    2)видимо при копировании текста у автора выпадает обратный слэш "\". Поэтому комбинация "\r\n" превращается в "rn".
    Обязательно учесть эту особенность при написании хидеров файла httpd.c

    const char http_header[] = { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"};
    const char jpg_header[] = {"HTTP/1.0 200 OK\r\nServer: nginxrnContent-Type: image/jpeg\r\nConnection: close\r\n\r\n"};
    const char icon_header[] = { "HTTP/1.1 200 OK\r\nContent-Type: image/x-icon\r\n\r\n"};
    const char error_header[] = {"HTTP/1.0 404 File not found\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"};

    При этом учесть, что каждый хидер заканчивается двойной комбинацией "\r\n\r\n", а одинарная комбинация "\r\n" встречается и в середине хидера.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*