STM Lesson 91. LAN. W5500. HTTP Server. Part 3
STM Lesson 91. LAN. W5500. HTTP Server. Part 3

Part 3
LAN. W5500. HTTP server
B previous part lesson we wrote the function of initializing the chip, and also began to write the function of receiving and processing network packages.
Since we are writing an HTTP server, let's put all the work with this protocol into a separate module for which we will create files httpd.h and httpd.c as follows
httpd.h
#ifndef
HTTPD_H_
#define
HTTPD_H_
//--------------------------------------------------
#include
"stm32f4xx_hal.h"
#include
<string.h>
#include
<stdlib.h>
#include
<stdint.h>
#include
"fatfs.h"
#include
"w5500.h"
//--------------------------------------------------
#endif
/* HTTPD_H_ */
httpd.c
#include
"httpd.h"
//-----------------------------------------------
extern
UART_HandleTypeDef
huart2;
//-----------------------------------------------
extern
char
str1[60];
//-----------------------------------------------
Also connect this library in the file w5500.h in bottom itself file
void
w5500_packetReceive(
void
);
//--------------------------------------------------
#include
"httpd.h"
//--------------------------------------------------
In file httpd.h add a structure for the properties of the HTTP package, as well as macros for document types
#include
"w5500.h"
//--------------------------------------------------
typedef
struct
http_sock_prop
{
volatile
uint8_t
data_stat
;
//статус передачи данных
volatile
uint32_t
data_size
;
//размер данных для передачи
volatile
uint16_t
last_data_part_size
;
//размер последней части данных для передачи
volatile
uint16_t
cnt_data_part
;
//общее количество частей данных для передачи
volatile
uint16_t
cnt_rem_data_part
;
//количество оставшихся частей данных для передачи
volatile
uint32_t
total_count_bytes
;
//количество переданных байтов документа
volatile
uint8_t
http_doc
;
//вариант документа для передачи
volatile
uint8_t
prt_tp
;
//вариант документа для передачи
char
fname
[20];
//имя файла (документа)
}
http_sock_prop_ptr
;
//--------------------------------------------------
//Варианты документов HTTP
#define
EXISTING_HTML 0
#define
E404_HTML 1
#define
EXISTING_JPG 2
#define
EXISTING_ICO 3
//--------------------------------------------------
In file httpd.c add a variable of our structure, as well as connect the structure of the properties of TCP
extern
char
str1[60];
//-----------------------------------------------
http_sock_prop_ptr httpsockprop[2];
extern
tcp_prop_ptr
tcpprop;
In the w5500.c file, also connect the HTTP property structure variable
tcp_prop_ptr
tcpprop;
extern
http_sock_prop_ptr httpsockprop[2];
In file w5500.h add macro substitutions for data transfer states
} tcp_prop_ptr;
//--------------------------------------------------
//Статусы передачи данных
#define
DATA_COMPLETED 0
//передача данных закончена
#define
DATA_ONE 1
//передаём единственный пакет
#define
DATA_FIRST 2
//передаём первый пакет
#define
DATA_MIDDLE 3
//передаём средний пакет
#define
DATA_LAST 4
//передаём последний пакет
#define
DATA_END 5
//закрываем соединение после передачи данных
//--------------------------------------------------
In file w5500.c in the function of receiving a package in the body of our condition of the socket, add another condition of the state of data transfer
if
(GetSocketStatus(tcpprop.cur_sock)==SOCK_ESTABLISHED)
{
if
(httpsockprop[tcpprop.cur_sock].data_stat == DATA_COMPLETED)
{
}
}
In the body of this condition, we will accept the HTTP request, provided that the data from us is all transferred. And, since the macro of this state matters to us 0, then we do not need to initialize the state at the start of the program and we will immediately enter the body of this condition.
Above the initialization function, add a new function for determining the size of the accepted data in the TCP package, using a special socket register for this
//-----------------------------------------------
uint16_t
GetSizeRX(
uint8_t
sock_num)
{
uint16_t
len;
uint8_t
opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
len = (w5500_readReg(opcode,Sn_RX_RSR0)<<8|w5500_readReg(opcode,Sn_RX_RSR1));
return
len;
}
//-----------------------------------------------
Further in the body of an already new condition, we will find out the size of the accepted data, display it in the terminal program, and if it is zero, then there is no data in the TCP package, then we exit the function
if
(httpsockprop[tcpprop.cur_sock].data_stat == DATA_COMPLETED)
{
//Отобразим размер принятых данных
len = GetSizeRX(0);
sprintf(str1,
"len_rx_buf:0x%04Xrn"
,len);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
//Если пришел пустой пакет, то уходим из функции
if
(!len)
return
;
}
We collect the code, send the controller and try to request any page. Although, of course, our server will not answer in any way, but at least we will see that a package of a certain length has arrived
In the terminal program, we see endlessly incoming data with the size of the data length, that is, until we process the data, we will fall into this condition. and this is independent, whether or not our connection will close
We also see in WareShark that there was only one request from the client
And to stop this "endless cycle" is only possible by restarting the controller.
But at least we see that the request has come to us.
Also above the initialization function we write a function that will return the data address in the receiving buffer, also using the corresponding register of socket
//-----------------------------------------------
uint16_t
GetReadPointer(
uint8_t
sock_num)
{
uint16_t
point;
uint8_t
opcode;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
point = (w5500_readReg(opcode,Sn_RX_RD0)<<8|w5500_readReg(opcode,Sn_RX_RD1));
return
point;
}
//-----------------------------------------------
Add a prototype for this function. It will come in handy for us in another module.
Let's also find out the data address in the buffer of their reception in the body of the same conditions as the reading and processing of network packages, also displaying it in the terminal program
if
(!len)
return
;
//здесь обмениваемся информацией: на запрос документа от клиента отправляем ему запрошенный документ
//указатель на начало чтения приёмного буфера
point = GetReadPointer(tcpprop.
cur_sock
);
sprintf(str1,
"Sn_RX_RD:0x%04Xrn"
,point);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
We collect the code, send the controller and once again request the document in the browser. And here is the result
Our address is still zero. Well, it is understandable. This is the very first request and it has not been processed. Therefore, the address will not change until it is.
Add global array for temporary buffer
extern
char
str1[60];
char
tmpbuf[30];
In file w5500.h add a structure for the transfer buffer
} tcp_prop_ptr;
//--------------------------------------------------
typedef
struct
data_sect
{
volatile
uint16_t
addr
;
volatile
uint8_t
opcode
;
uint8_t
data
[];
}
data_sect_ptr
;
//--------------------------------------------------
We will also add macros for TCP protocol options. So far there will be only two — unknown and HTTP
#define
DATA_END 5
//закрываем соединение после передачи данных
//--------------------------------------------------
//Варианты протоколов TCP
#define
PRT_TCP_UNCNOWN 0
#define
PRT_TCP_HTTP 1
//--------------------------------------------------
Let's go back to the file w5500.c and after the register reading function, add a few more read functions
//-----------------------------------------------
void
w5500_readBuf(data_sect_ptr *datasect,
uint16_t
len)
{
SS_SELECT();
HAL_SPI_Transmit(&hspi1, (
uint8_t
*) datasect, 3, 0xFFFFFFFF);
HAL_SPI_Receive(&hspi1, (
uint8_t
*) datasect, len, 0xFFFFFFFF);
SS_DESELECT();
}
//-----------------------------------------------
uint8_t
w5500_readSockBufByte(
uint8_t
sock_num,
uint16_t
point)
{
uint8_t
opcode, bt;
opcode = (((sock_num<<2)|BSB_S0_RX)<<3)|OM_FDM1;
bt = w5500_readReg(opcode, point);
return
bt;
}
//-----------------------------------------------
void
w5500_readSockBuf(
uint8_t
sock_num,
uint16_t
point,
uint8_t
*buf,
uint16_t
len)
{
data_sect_ptr *datasect = (
void
*)buf;
datasect->opcode = (((sock_num<<2)|BSB_S0_RX)<<3)|OM_FDM0;
datasect->addr = be16toword(point);
w5500_readBuf(datasect,len);
}
//-----------------------------------------------
The first of these functions reads from the buffer using the variable length data reading method, the second — reads one byte from a buffer of a particular socket, and the third — several bytes from a particular socket.
On the second function — w5500_readSockBufByte — add the prototype in the title file.
Now, in the function of reading and processing network packages, add more code to our condition, which will determine what this is the HTTP request
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
w5500_readSockBuf(tcpprop.cur_sock, point, (uint8_t*)tmpbuf, 5);
if
(strncmp(tmpbuf,
"GET /"
, 5) == 0)
{
HAL_UART_Transmit(&huart2,(uint8_t*)
"HTTPrn"
,6,0x1000);
httpsockprop[tcpprop.cur_sock].prt_tp = PRT_TCP_HTTP;
}
If the lines match, we will display the abbreviation "HTTP" in the terminal program, and also initialize the structure field with a specific protocol.
In file httpd.c add the response function to the HTTP request, in which we find the address of the occurrence of the character "/"
extern
tcp_prop_ptr
tcpprop;
//-----------------------------------------------
void
http_request(
void
)
{
uint16_t
point;
uint8_t
RXbyte;
uint16_t
i=0;
char
*ss1;
int
ch1=
'.'
;
// ищем первый "/" в HTTP заголовке
point = GetReadPointer(tcpprop.
cur_sock
);
i = 0;
while
(RXbyte != (
uint8_t
)
'/'
)
{
RXbyte = w5500_readSockBufByte(tcpprop.
cur_sock
,point+i);
i++;
}
}
//-----------------------------------------------
We create a prototype for this function and call it the function of reading the package in our condition in the file w5500.c
httpsockprop[tcpprop.cur_sock].prt_tp = PRT_TCP_HTTP;
http_request();
In order not to get confused in the places where the conditions are added, let's leave the bodies of two conditions at once and add two more conditions in advance
http_request();
}
}
else
if
(httpsockprop[tcpprop.cur_sock].data_stat==DATA_MIDDLE)
{
if
(httpsockprop[tcpprop.cur_sock].prt_tp == PRT_TCP_HTTP)
{
}
}
else
if
(httpsockprop[tcpprop.cur_sock].data_stat==DATA_LAST)
{
if
(httpsockprop[tcpprop.cur_sock].prt_tp == PRT_TCP_HTTP)
{
}
}
}
}
These conditions will be triggered if one of the — transfer statuses is set to transfer the average package and or transfer the last data package. Similarly, we worked with packages in lessons using the HTTP protocol with the enc28j60 module.
In file httpd.c connect temporary buffer
extern
char
str1[60];
extern
char
tmpbuf[30];
We continue to write the response function to the HTTP request.
We consider the following character after a slash and, if there is a space — then this is a query of the main page. Set the corresponding type of document in the structure field
i++;
}
point+=i;
RXbyte = w5500_readSockBufByte(tcpprop.
cur_sock
,point);
if
(RXbyte==(uint8_t)
' '
)
{
strcpy(httpsockprop[tcpprop.cur_sock].fname,
"index.htm"
);
httpsockprop[tcpprop.cur_sock].http_doc = EXISTING_HTML;
}
else
{
}
We will write a body of conditions working otherwise
else
{
// ищем следующий пробел (" ") в HTTP заголовке, таким образом считывая имя документа из запроса
i=0;
while
(1)
{
tmpbuf[i] = w5500_readSockBufByte(tcpprop.cur_sock, point+i);
if
(tmpbuf[i] == (uint8_t)
' '
)
break
;
i++;
}
tmpbuf[i] = 0;
//закончим строку
strcpy(httpsockprop[tcpprop.cur_sock].fname,tmpbuf);
}
In this body we will get all the bytes until the next space. This will be the name of the document.
We continue the function further. Display the file name that the client requested in the terminal program
strcpy(httpsockprop[tcpprop.cur_sock].fname,tmpbuf);
}
HAL_UART_Transmit(&huart2,(uint8_t*)httpsockprop[tcpprop.cur_sock].fname,strlen(httpsockprop[tcpprop.cur_sock].fname),0x1000);
HAL_UART_Transmit(&huart2,(uint8_t*)
"rn"
,2,0x1000);
We collect the code, sew the controller and try to request first the main page, and then some other, then we'll see the result in the terminal
Everything is read normally.
Now in the file main.c add some global variables to work with the file system, and connect the string array
/* Private variables ---------------------------------------------------------*/
extern
char
SDPath[4];
/* logical drive path */
FATFS
SDFatFs;
//указатель на объект
extern
char
str1[60];
/* USER CODE END PV */
In function main ( ) also add a local variable for the result
/* USER CODE BEGIN 1 */
FRESULT
result;
//результат выполнения
/* USER CODE END 1 */
In the same function, mount our media on the SD map and display the result in the terminal program
/* USER CODE BEGIN 2 */
result=f_mount(&SDFatFs,(
TCHAR
const
*)SDPath,0);
sprintf(str1,
"f_mount: %drn"
,result);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
net_ini();
We collect the code, sew the controller and see the result in the terminal
Everything was finely mounted.
Let's go back to the file httpd.c and add the necessary global variables for working with the file system
extern
tcp_prop_ptr
tcpprop;
FIL
MyFile;
FRESULT
result;
//результат выполнения
uint32_t
bytesread;
//-----------------------------------------------
Continue our HTTP request response feature.
We open the required file from the card and find out its size. We will also show this in the terminal program
HAL_UART_Transmit(&huart2,(uint8_t*)
"rn"
,2,0x1000);
f_close(&MyFile);
result=f_open(&MyFile,httpsockprop[tcpprop.
cur_sock
].fname,FA_READ);
//Попытка открыть файл
sprintf(str1,
"f_open: %drn"
,result);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
sprintf(str1,
"f_size: %lurn"
,MyFile.fsize);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
Before opening the file, we close the previous one. You can do this in other places, but it works best for me.
We’ll collect the project again, we’ll send a controller and try to request some existing document
The size is determined normally.
I created several html pages of various sizes to test our project with the request of documents consisting of different numbers of packages.
Add a few types of HTTP headers for response in the form of global arrays, as well as an array with a page transmitted in case of absence of the requested document
uint32_t
bytesread;
//-----------------------------------------------
const
char
http_header[] = {
"HTTP/1.1 200 OKrnContent-Type: text/htmlrnrn"
};
const
char
jpg_header[] = {
"HTTP/1.0 200 OKrnServer: nginxrnContent-Type: image/jpegrnConnection: closernrn"
};
const
char
icon_header[] = {
"HTTP/1.1 200 OKrnContent-Type: image/x-iconrnrn"
};
const
char
error_header[] = {
"HTTP/1.0 404 File not foundrnServer: nginxrnContent-Type: text/htmlrnConnection: closernrn"
};
char
*header;
//-----------------------------------------------
const
uint8_t
e404_htm[] = {
0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0a,0x20,0x20,0x3c,0x68,0x65,0x61,0x64,0x3e,0x0a,
0x20,0x20,0x20,0x20,0x3c,0x74,0x69,0x74,0x6c,0x65,0x3e,0x34,0x30,0x34,0x20,0x4e,
0x6f,0x74,0x20,0x46,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,
0x0a,0x20,0x20,0x3c,0x2f,0x68,0x65,0x61,0x64,0x3e,0x0a,0x3c,0x62,0x6f,0x64,0x79,
0x3e,0x0a,0x3c,0x68,0x31,0x20,0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,
0x74,0x2d,0x61,0x6c,0x69,0x67,0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,
0x22,0x3e,0x34,0x30,0x34,0x20,0x45,0x72,0x72,0x6f,0x72,0x20,0x46,0x69,0x6c,0x65,
0x20,0x4e,0x6f,0x74,0x20,0x46,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x68,0x31,0x3e,0x0a,
0x3c,0x68,0x32,0x20,0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,0x74,0x2d,
0x61,0x6c,0x69,0x67,0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,0x22,0x3e,
0x20,0x54,0x68,0x65,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,0x20,0x61,0x72,
0x65,0x20,0x6c,0x6f,0x6f,0x6b,0x69,0x6e,0x67,0x20,0x66,0x6f,0x72,0x20,0x6d,0x69,
0x67,0x68,0x74,0x20,0x68,0x61,0x76,0x65,0x20,0x62,0x65,0x65,0x6e,0x20,0x72,0x65,
0x6d,0x6f,0x76,0x65,0x64,0x2c,0x20,0x3c,0x62,0x72,0x20,0x2f,0x3e,0x68,0x61,0x64,
0x20,0x69,0x74,0x73,0x20,0x6e,0x61,0x6d,0x65,0x20,0x63,0x68,0x61,0x6e,0x67,0x65,
0x64,0x2c,0x20,0x6f,0x72,0x20,0x69,0x73,0x20,0x74,0x65,0x6d,0x70,0x6f,0x72,0x61,
0x72,0x69,0x6c,0x79,0x20,0x75,0x6e,0x61,0x76,0x61,0x69,0x6c,0x61,0x62,0x6c,0x65,
0x2e,0x3c,0x2f,0x68,0x32,0x3e,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x2f,
0x68,0x74,0x6d,0x6c,0x3e};
//-----------------------------------------------
We continue to write our response function to the HTTP request. If the document has opened, then we will study its extension and set the necessary status, and if not, we will establish the status of the error of the existence of the document on the server. We also calculate in advance the length of the HTTP response in order to determine later what many packages and windows it will fit
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if
(result==FR_OK)
{
//изучим расширение файла
ss1 = strchr(httpsockprop[tcpprop.cur_sock].fname,ch1);
ss1++;
if
(strncmp(ss1,
"jpg"
, 3) == 0)
{
httpsockprop[tcpprop.cur_sock].http_doc = EXISTING_JPG;
//сначала включаем в размер размер заголовка
httpsockprop[tcpprop.cur_sock].data_size = strlen(jpg_header);
}
if
(strncmp(ss1,
"ico"
, 3) == 0)
{
httpsockprop[tcpprop.cur_sock].http_doc = EXISTING_ICO;
//сначала включаем в размер размер заголовка
httpsockprop[tcpprop.cur_sock].data_size = strlen(icon_header);
}
else
{
httpsockprop[tcpprop.cur_sock].http_doc = EXISTING_HTML;
//сначала включаем в размер размер заголовка
httpsockprop[tcpprop.cur_sock].data_size = strlen(http_header);
}
//затем размер самого документа
httpsockprop[tcpprop.cur_sock].data_size += MyFile.fsize;
}
else
{
httpsockprop[tcpprop.cur_sock].http_doc = E404_HTML;
//сначала включаем в размер размер заголовка
httpsockprop[tcpprop.cur_sock].data_size = strlen(error_header);
//затем размер самого документа
httpsockprop[tcpprop.cur_sock].data_size +=
sizeof
(e404_htm);
}
B next part lesson we will continue the response function to the HTTP request, as well as write the function of transferring to the client a document that fits with the heading in one window.