Урок 93
Часть 1
LAN. W5500. HTTP Server. Сокеты
Сегодня мы для нашего модуля W5500 попробуем организовать сокеты.
Сокеты — это такие виртуальные соединения, которые используются для организации нескольких соединений TCP, работающих одновременно.
То есть если клиент с сервером хотят создать ещё одно соединение, не закрывая уже созданное, то для этого будет использоваться сокет. Другими словами, если мы хотим, чтобы между клиентом и сервером могло существовать одновременно до 8 неразорванных соединений, то мы должны организовать 8 сокетов. Нужно это для того, что порой многие программы создают для более быстрой передачи несколько одновременных соединений. А, например протокол FTP вообще использует обязательно 2 открытых соединения. Одно — для команд и управление, а другое — для передачи данных.
Поэтому нам нужно как-то это обеспечить.
А для этого наша микросхема нам предлагает готовый аппаратный подход. Когда мы знакомились с ней в уроке 91, мы видели, что в ней существует два типа регистров. а также памяти. Предназначенные для общего пользования, то есть независимые от какого-либо соединения, а также регистры и память для сокетов, которые и отвечают каждый за своё соединение. Мы инициализировали, а впоследствии и пользовались только одним из них. А сегодня попробуем задействовать и другие.
Чтобы как-то со всем этим разобраться, давайте создадим проект из проекта урока 91 W5500_HTTPS и назовём его W5500_HTTPS_SOCKETS.
Запустим наш проект в Cube MX.
Перейдём в Configuration и в параметрах FATFS изменим количество одновременно открываемых файлов на «8»
Также внесём некоторые коррективы в настройки периферии SDIO. Мы установим там делитель 2, чтобы контроллер не подвисал при копировании через данную шину больших объёмов информации
Если встать на строчку с делителем, то внизу мы увидим, как рассчитывается частота работы шины SDIO. То есть если мы делитель оставим по умолчанию в 0, то частоту мы получим соответственно формуле. То есть если у нас тактирование шины 48 мегагерц (больше нельзя), то реальная частота работы шины будет 24, если делитель установим в 1, то получим частоту 18 мегагерц. Мы установили делитель 2, следовательно частота работы шины будет 12 мегагерц.
В настройках на всякий случай увеличим размер стека и кучи вчетверо, так как соединений будет много, структур и переменных также получится много, поэтому может стандартного и не хватить. Хотя я тестировал — всё хватало. Но мало ли какие попадутся страницы в будущем, да и не открывалось у меня как-то сразу 8 соединений
Сохраним изменения, сгенерируем проект, откроем его в 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);
Соберём код и проверим работоспособность инициализации
Всё работает.
В функцию приёма пакета добавим входной аргумент номера сокета
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
Смотреть ВИДЕОУРОК (нажмите на картинку)