STM Урок 93. LAN. W5500. HTTP Server. Сокеты. Часть 1



 

Урок 93

 

Часть 1

 

LAN. W5500. HTTP Server. Сокеты

 

Сегодня мы для нашего модуля W5500 попробуем организовать сокеты.

Сокеты — это такие виртуальные соединения, которые используются для организации нескольких соединений TCP, работающих одновременно.

То есть если клиент с сервером хотят создать ещё одно соединение, не закрывая уже созданное, то для этого будет использоваться сокет. Другими словами, если мы хотим, чтобы между клиентом и сервером могло существовать одновременно до 8 неразорванных соединений, то мы должны организовать 8 сокетов. Нужно это для того, что порой многие программы создают для более быстрой передачи несколько одновременных соединений. А, например протокол FTP вообще использует обязательно 2 открытых соединения. Одно — для команд и управление, а другое — для передачи данных.

 

Поэтому нам нужно как-то это обеспечить.

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

Чтобы как-то со всем этим разобраться, давайте создадим проект из проекта урока 91 W5500_HTTPS и назовём его W5500_HTTPS_SOCKETS.

Запустим наш проект в Cube MX.

Перейдём в Configuration и в параметрах FATFS изменим количество одновременно открываемых файлов на «8»

 

image07

 

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

 

Image06

 

Если встать на строчку с делителем, то внизу мы увидим, как рассчитывается частота работы шины SDIO. То есть если мы делитель оставим по умолчанию в 0, то частоту мы получим соответственно формуле. То есть если у нас тактирование шины 48 мегагерц (больше нельзя), то реальная частота работы шины будет 24, если делитель установим в 1, то получим частоту 18 мегагерц. Мы установили делитель 2, следовательно частота работы шины будет 12 мегагерц.

В настройках на всякий случай увеличим размер стека и кучи вчетверо, так как соединений будет много, структур и переменных также получится много, поэтому может стандартного и не хватить. Хотя я тестировал — всё хватало. Но мало ли какие попадутся страницы в будущем, да и не открывалось у меня как-то сразу 8 соединений

 

Image00

 

Сохраним изменения, сгенерируем проект, откроем его в Keil, подключим наши библиотеки net.c, httpd.c и w5500.c, настроим программатор на авторезет, установим уровень оптимизации 1 и попробуем собрать.

Если всё нормально собралось, то начнём думать, как нам организовать работу с сокетами.

Добавим в файл httpd.h пртотипы двух функций (почему-то их там не оказалось, но тем не менее всё работало)

 

void http_request(void);

void tcp_send_http_middle(void);

void tcp_send_http_last(void);

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

 

Перейдём в файл  w5500.c и напишем в ней функцию инициализации порта в сокете после функции w5500_readSockBuf

 

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

void SetSockPort(uint8_t sock_num, uint16_t port)

{

  uint8_t opcode;

  opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;

  w5500_writeReg(opcode, Sn_PORT0,port>>8);

  w5500_writeReg(opcode, Sn_PORT1,port);

}

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

 

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

 

 

Перейдём в функцию инициализации микросхемы в файл w5500.c и удалим из неё данный участок кода

 

//Настраиваем сокет 0

opcode = (BSB_S0<<3)|OM_FDM1;

w5500_writeReg(opcode, Sn_PORT0,local_port>>8);

w5500_writeReg(opcode, Sn_PORT1,local_port);

//инициализируем активный сокет

tcpprop.cur_sock = 0;

 

А вместо всего этого вставим следующий код

 

//Настраиваем сокеты

SetSockPort(0, local_port);

 

Пока мы также работаем только с нулевым сокетом, но уже более универсально. Нам надо добиться работоспособности пока одного сокета но уже с упором на оперативную его смену в любой момент.

Удалим также из инициализации вот эту строку

 

//Посмотрим статусы

opcode = (BSB_S0<<3)|OM_FDM1;

 

А дальше немного исправим код, вызвав более высокоуровневую функцию

 

//Посмотрим статусы

dtt = GetSocketStatus(0);

sprintf(str1,"First Status Sn%d: 0x%02X\r\n",0,dtt);

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

 

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

 

Image01

 

Всё работает.

В функцию приёма пакета добавим входной аргумент номера сокета

 

void w5500_packetReceive(uint8_t sn)

 

Не забываем также про прототип.

Также передадим параметр и в вызове данной функции в файле net.c

 

w5500_packetReceive(0);

 

Вернёмся в файл w5500 и в функции w5500_packetReceive заменим строку tcpprop.cur_sock  на sn. Так как эта строка встречается только в этой функции, то можно воспользоваться процедурой замены, вызвав её так же, как и процедуру поиска комбинацией клавиш Ctrl+F.

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

 

if(httpsockprop[sn].data_stat == DATA_COMPLETED)

{

  len = GetSizeRX(sn);

  //Если пришел пустой пакет, то уходим из функции

  if(!len) return;

  //Отобразим размер принятых данных

  sprintf(str1,"S%d len buf:0x%04X\r\n",sn,len);

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

  //здесь обмениваемся информацией: на запрос документа от клиента отправляем ему запрошенный документ

 

 

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

 

sprintf(str1,"Sn_RX_RD:0x%04X\r\n",point);

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

 

Также ближе к концу этой функции отобразим в терминале номер закрытого сокета

 

if(httpsockprop[sn].prt_tp == PRT_TCP_HTTP)

{

  tcp_send_http_last(sn);

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

  SocketClosedWait(sn);

  sprintf(str1,"S%d closed\r\n",sn);

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

 

Перейдём в файл httpd.c и также аналогичный входной параметр вставим в функцию void http_request, а также в её протоип в заголовочном файле

 

void http_request(uint8_t sn)

 

Вернёмся в файл w5500.c и добавим параметр в вызове этой функции в функции приёма пакетов

 

http_request(sn);

 

Опять перейдём в файл 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)

 

Также во всех вызовах данных функций в файлах httpd.c и w5500.c также добавим аргумент

 

tcp_send_http_one(sn);

tcp_send_http_first(sn);

tcp_send_http_middle(sn);

tcp_send_http_last(sn);

 

В файле  httpd.c теперь также произведём замену вхождения tcpprop.cur_sock  на sn.

У меня получилось 109 замен.

В функциях tcp_send_http_first и tcp_send_http_last уберём вывод в терминальную программу факта установки указателя в файле

 

sprintf(str1,"f_lseek: %d\r\n",result);

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

 

И вот это тоже в файле w5500.c

 

HAL_UART_Transmit(&huart2,(uint8_t*)"HTTP\r\n",6,0x1000);

 

Структуру tcp_prop мы можем теперь удалить из файла w5500.h, а также удалить объявления всех переменных её типа.

По идее мы теперь уже не привязаны к нулевому сокету так сильно, как это было ранее.

Проверим сначала работоспособность на нулевом сокете, собрав код, прошив контроллер и запросив какой-либо документ в браузере.

Если всё работает, то попробуем поменять нулевой сокет на третий.

Для этого нам достаточно в файлах net.c и w5500.c изменить вызываемый параметр в следующих строках

 

net:c:

w5500_packetReceive(3);

 

w5500.c:

//Настраиваем сокеты

SetSockPort(3, local_port);

//Открываем сокет

OpenSocket(3,Mode_TCP);

SocketInitWait(3);

//Начинаем слушать сокет

ListenSocket(3);

SocketListenWait(3);

HAL_Delay(500);

//Посмотрим статусы

dtt = GetSocketStatus(3);

sprintf(str1,"First Status Sn%d: 0x%02X\r\n",3,dtt);

 

Также проверим работу после установки в качестве рабочего сокета 3.

Если всё хорошо работает, то осталось нам задействовать сразу несколько сокетов. Это также будет очень легко.

Для этого создадим локальную переменную в функции инициализации в файле w5500.c

 

void w5500_ini(void)

{

  uint8_t i;

  uint8_t dtt=0;

 

И перестроим немного код в конце функции

 

  w5500_writeReg(opcode, SIPR3,ipaddr[3]);

  //Настраиваем сокеты

  for(i=3;i<8;i++)

  {

    SetSockPort(i, local_port);

    //Открываем сокет

    OpenSocket(i,Mode_TCP);

    SocketInitWait(i);

    //Начинаем слушать сокет

    ListenSocket(i);

    SocketListenWait(i);

  }

  HAL_Delay(500);

  //Посмотрим статусы

  for(i=3;i<8;i++)

  {

    dtt = GetSocketStatus(i);

    sprintf(str1,"First Status Sn%d: 0x%02X\r\n",i,dtt);

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

  }

}

 

То есть мы инициализировали 6 сокетов под номерами 3-7.

Осталось также в файле net.c также подправить вызов функции приёма и обработки пакетов, внеся её также в цикл

 

void packet_receive(void)

{

  uint8_t i;

  for(i=3;i<8;i++)

  {

    w5500_packetReceive(i);

  }

}

 

Также в файле httpd.c исправим в глобальном массиве количество элементов

 

http_sock_prop_ptr httpsockprop[8];

 

То же самое проделаем и  в файле w5500.c

 

extern http_sock_prop_ptr httpsockprop[8];

 

Всё это очень хорошо, но есть ещё одна интересная засада. Я про неё часто забываю.

Мы передаём файлы, сокеты работают по очереди, а переменная для файла у нас одна, а должна быть для каждого файла своя. То есть каждый сокет работает со своим файлом, причём происходит это одновременно с тем, когда другие сокеты работают с другими файлами.

Поэтому переменную для файла в файле httpd.c превратим в массив

 

static FIL MyFile[8];

 

Также в этом файле вхождение строки MyFile заменим на строку MyFile[sn].

 

В следующей части урока мы подправим код функции приёма и обработки пакетов HTTP и проверим наш код на практике.

 

 

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

 

 

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

и здесь Nucleo STM32F401RE

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

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

 

 

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

 

STM LAN. W5500. HTTP Server. Сокеты