FREERTOS plus TCP - STM32H7

Hello.
I am creating an application for a board based on MCU STM32H743, ethernet phy Microchip lan8742A in Keil (compiler version 6) + CubeMX (initialization of peripherals) using FREERTOS and FREERTOS plus TCP libraries. The board and PC are connected via a switch.

I was able to successfully build the application. It allows you to ping the board, creates a TCP echo server on the board, and also sends packets to the client by timer and works correctly in normal mode. However, there are several questions and problems.
I do not know if I should create separate topics or I can ask all the questions in this one. Correct me if I am wrong.

  1. If you do not connect the network cable to the port, then after initialization of the application and the network stack in the callback vApplicationIPNetworkEventHook is called with the eNetworkDown parameter approximately every 3 seconds, as it should be. When connecting the network cable, the eNetworkUp message comes and everything works as it should (ping, tcp server). But if you now disconnect the network cable, then eNetworkDown never comes.
    Also, if you start the board with the network cable connected, then after initializing the application and the network stack, eNetworkUp comes, but when disconnecting the network cable, eNetworkDown never comes. What could be the reason?

  2. Question about clearing the TCP connection. When the TCP server is working correctly, disconnect occurs as described in the manual: FreeRTOS_recv and FreeRTOS_send return a value less than 0 and clearing occurs.

Size = FreeRTOS_recv( xConnectedSocket, IN_Array, sizeof( IN_Array ), 0 );

/* If data was received, echo it back. */
if( Size > 0 )
{			
   vTaskResume(RTOS_Handle_Write);
}
else
{
   if(Size==0)
   {		
		
   }
   else			
   {				
      /* Socket closed? */
      break;
   }
}
FreeRTOS_shutdown( xConnectedSocket, FREERTOS_SHUT_RDWR );

/* Wait for the shutdown to take effect, indicated by FreeRTOS_recv()
returning an error. */
xTimeOnShutdown = xTaskGetTickCount();
do
{
if( FreeRTOS_recv( xConnectedSocket, cReceivedString, ipconfigTCP_MSS, 0 ) < 0 )
{
break;
}
} while( ( xTaskGetTickCount() - xTimeOnShutdown ) < tcpechoSHUTDOWN_DELAY );

/* Finished with the socket and the task. */
FreeRTOS_closesocket( xConnectedSocket );
vTaskDelete( NULL );

If during operation the cable is disconnected from the board or from the PC, then after about 15 seconds FreeRTOS_recv returns with a value less than 0. However, FreeRTOS_send returns with positive values ​​all this time, as if the transfer is taking place.
What determines the time after which an obviously lost connection will lead to the return of FreeRTOS_recv with a negative value and why FreeRTOS_send does not return an error, although there is clearly no packet transmission.

  1. Is it possible to clear the connection if there is certainty that the connection is lost, without waiting for about 15 seconds for the stack to detect this itself (see point 2)?
    My attempts to do this either do not lead to anything or disrupt the stack and lead to its malfunction.

Thanks

Have you enabled ipconfigSUPPORT_NETWORK_DOWN_EVENT in the FreeRTOSIPConfig.h?
If that is enabled and is still not working, please share what version of FreeRTOS+TCP you are using. If its V4.3.0 or above, then there are 2 possible network interface implementations that support STM32H7 legacy and the unified, please state which one.

However, FreeRTOS_send returns with positive values ​​all this time, as if the transfer is taking place.

FreeRTOS_send called from the application tasks only adds the data to the TCP sockets stream buffer (uxStreamBufferAdd) it doesn’t really send the data from through the network interface, it’s the IP task that does that. So as long as you call FreeRTOS_send with a valid socket and valid data, it keeps adding data to the stream buffer.

  1. Is it possible to clear the connection if there is certainty that the connection is lost, without waiting for about 15 seconds for the stack to detect this itself (see point 2)?
    My attempts to do this either do not lead to anything or disrupt the stack and lead to its malfunction.

You can take a look at the ipconfigPHY_LS_HIGH_CHECK_TIME_MS configuration. It defines the time interval (in milliseconds) for checking if the Physical Layer (PHY) link status remains high (i.e., a connection is established) after a period of inactivity, i.e., no packet reception. This mechanism is to prevent frequent checking of PHY registers, which could impact performance.

The default value is 15000 ms (15 seconds); you can try lowering it.

Hi @Rone4ka,
For the first question, assuming you’re using our driver under portable/NetworkInterface/STM32, could you help check if FreeRTOS_NetworkDown at line 887~889 is triggered when cable is unplugged? That function call should trigger the eNetworkDown event to TCPIP stack.

Thank you

Just to add, if you are using the older network (Legacy) interface prior to V4.3.0 then the support for network down event is not present for STM32H7. You might consider updating to a newer version.

Hi @tony-josi-aws, @ActoryOu.
Hello.

I am using the latest version of the FreeRTOSv202406.01-LTS library.
I took the implementation of the network interface from …\FreeRTOSv202406.01-LTS\FreeRTOS-LTS\FreeRTOS\FreeRTOS-Plus-TCP\source\portable\NetworkInterface\STM32Hxx.

Yes, I have the ipconfigSUPPORT_NETWORK_DOWN_EVENT option enabled in FreeRTOSIPConfig.h. As I said, the vApplicationIPNetworkEventHook event with the NetworkDown flag occurs approximately every 3 seconds if you start the board with the cable disconnected. But it does not happen if you start the board with the cable connected and disconnect it, or turn on the board with the cable disconnected, connect the cable, receive the vApplicationIPNetworkEventHook connection event with the eNetworkUp flag and disconnect it again.

I understood your answer about FreeRTOS_send.

I understood the advice about ipconfigPHY_LS_HIGH_CHECK_TIME_MS and I will try it. But is there a correct way to clear the connection before the stack itself detects the fault (I understand that you can reset the MCU, but this is not a very beautiful solution). Is it possible to clear it or “force” the stack to clear, as if it itself detected that everything is bad?

Unfortunately I can’t test your practical advice until next week, but I will definitely try and write about the results.

Thanks

I checked the version of FreeRTOS-Plus-TCP here is v4.2.2. Just like @tony-josi-aws mentioned, new driver is provided from v4.3.0. Would you like to try latest released version 202406.03-LTS? It might provide the capability of eNetworkDown event you want.

In reality, the issue on network interface might be recoverable. If the stack clear when it’s down even for just 1ms, the application needs to re-establish the socket connection in this case. It’d be exhaused to handle this kind of re-connection issue. And it also waste network bandwidth. The current design decouples network interface event and the application by the +TCP stack. If the network interface back by itself in short, then user doesn’t need to take care of it.

Thank you.

I would like to know what exactly clearing the connection means for you. Is it closing the TCP connection? Or is it restarting the TCP connection when the ethernet cable is connected back again after a long time (assuming the connection with other peer has been shut down)?

Hello.
Sorry for not writing for a long time, I needed some time to check your recommendations.

I switched to FreeRTOS plus TCP version 4.3.3, for which I also switched to FreeRTOS kernel version 11.2.0. Current application view: FreeRTOS kernel version 11.2.0, FreeRTOS plus TCP version 4.3.3, interface driver from … \FreeRTOS-Plus-TCP\source\portable\NetworkInterface\STM32\Drivers\H7.
I managed to repeat everything that was done on the previous version (Ping, TCP echo server).
About my questions and your recommendations:

  1. Enabling ipconfigSUPPORT_NETWORK_DOWN_EVENT gave a result, eNetworkUp and eNetworkDown messages come, although it is strange that even with ipconfigSUPPORT_NETWORK_DOWN_EVENT disabled it works, but only once as I described earlier.
  2. I experimented with the ipconfigPHY_LS_HIGH_CHECK_TIME_MS flag and got mixed results. With the standard configuration ipconfigPHY_LS_HIGH_CHECK_TIME_MS 15000, the FreeRTOS_recv response time returns with a value less than 0 after about 32 seconds, although on the previous version of FreeRTOS plus TCP this time was about 15 seconds. When changing the ipconfigPHY_LS_HIGH_CHECK_TIME_MS flag to 5000 and even 3000, the result does not change - 32 seconds.
  3. For me, clearing the connection is a correct reset and closing of the TCP connection.
    To make it clearer, let me briefly describe the idea of ​​​​the application.
    After starting the board and configuring it, the TCP server starts, which waits for a client to connect. In this case, there can be only one client. After connecting, the application gives some information on the network with a certain period (50 ms) and waits to receive information from the client with the same period. If information is not received from the client within a certain time (for example, 3 seconds), then we consider that the client or its connection has failed. At this point, we would like to clear this connection (faster than the stack itself detects the connection failure by its own means), so that another client can connect and continue working.

Thanks

Are you talking about the events sent to the IP task’s event queue? If thats the case, the FreeRTOS+TCP stack internally use the eNetworkDownEvent to trigger a network interface initialization whenever required, for example, when the device is booted up, try reinitializing the network interface/endpoints periodically if the previous initialization has failed, etc. The ipconfigSUPPORT_NETWORK_DOWN_EVENT is to send the eNetworkDownEvent from the EMAC task whenever it detects a link down.

When using FreeRTOS_recv, the call waits for a maximum of FREERTOS_SO_RCVTIMEO period of the socket with which FreeRTOS_recv is called, thats because the FreeRTOS_recv waits only on the socket events of that particular socket and not on the network events. [refer - prvRecvWait ]
In the case of TCP server, if you have not custom modified the xConnectedSocket’s FREERTOS_SO_RCVTIMEO then it should be using the settings configured for the server socket. [refer - prvHandleListen_IPV4]

I have tested the FreeRTOS-Plus-TCP\source\portable\NetworkInterface\STM32\NetworkInterface.c by updating the ipconfigPHY_LS_HIGH_CHECK_TIME_MS to lower values and verified that it generates a eNetworkDown event and calls the vApplicationIPNetworkEventHook_Multi within the configured time on STM32F4.

Based on the duration of the network being down (using information from vApplicationIPNetworkEventHook_Multi), the application should have its own logic to reconnect the TCP connection if the other peer has already dropped the connection.

I understood about using eNetworkDownEvent for internal stack initialization and the connection of the ipconfigSUPPORT_NETWORK_DOWN_EVENT flag with sending eNetworkDownEven on disconnection.
I understood that ipconfigPHY_LS_HIGH_CHECK_TIME_MS from FreeRTOSIPConfigDefaults affects the LinkStatus polling time in the PHY and I see that changing it really works.

By setting the packet reception timeout time, I can get what I was looking for.

xReceiveTimeOut=3000;
FreeRTOS_setsockopt( xConnectedSocket, 0, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, sizeof( xReceiveTimeOut ) );

If no data is received within the time (SizeReceive==0), then I initialize the clear as if returning with an error (SizeReceive<0).


SizeReceive= FreeRTOS_recv( xConnectedSocket, IN_Array, sizeof( IN_Array ), 0 );

if( SizeReceive> 0 )
{
   vTaskResume(RTOS_Handle_Write);
}
else
{
   if(SizeReceive==0)
   {		
      /*Timeout close*/			
      break;
   }
   else
   {
      /* Socket closed */
      break;
   }
}
/* Initiate a shutdown in case it has not already been initiated. */
FreeRTOS_shutdown( xConnectedSocket, FREERTOS_SHUT_RDWR );

/* Wait for the shutdown to take effect, indicated by FreeRTOS_recv()
returning an error. */
xTimeOnShutdown = xTaskGetTickCount();
do
{
   if( FreeRTOS_recv( xConnectedSocket, cReceivedString, ipconfigTCP_MSS, 0 ) < 0 )
   {
      break;
   }
} while( ( xTaskGetTickCount() - xTimeOnShutdown ) < tcpechoSHUTDOWN_DELAY );

/* Finished with the socket and the task. */
FreeRTOS_closesocket( xConnectedSocket );
vTaskDelete( NULL );

I have one last question on this topic, more for understanding the work.

If you use the same code, without clearing the connection by socket read timeout:

SizeReceive= FreeRTOS_recv( xConnectedSocket, IN_Array, sizeof( IN_Array ), 0 );

if( SizeReceive> 0 )
{
   vTaskResume(RTOS_Handle_Write);
}
else
{
   if(SizeReceive==0)
   {		
      /*Timeout close*/			
      //break;
   }
   else
   {
      /* Socket closed */
      break;
   }
}
/* Initiate a shutdown in case it has not already been initiated. */
FreeRTOS_shutdown( xConnectedSocket, FREERTOS_SHUT_RDWR );

/* Wait for the shutdown to take effect, indicated by FreeRTOS_recv()
returning an error. */
xTimeOnShutdown = xTaskGetTickCount();
do
{
   if( FreeRTOS_recv( xConnectedSocket, cReceivedString, ipconfigTCP_MSS, 0 ) < 0 )
   {
      break;
   }
} while( ( xTaskGetTickCount() - xTimeOnShutdown ) < tcpechoSHUTDOWN_DELAY );

/* Finished with the socket and the task. */
FreeRTOS_closesocket( xConnectedSocket );
vTaskDelete( NULL );

and disconnect the cable from the PC side with the client connected, then the SizeReceive<0 reading error from FreeRTOS_recv comes in about 32 seconds. What does this time depend on?

Thank you very much for your help and patience.

@Rone4ka

If we haven’t set a read timeout and the Ethernet link disconnection happens at the other peer, in your case the PC side, by default there isn’t a way the other TCP peer can know the connection is active or not, as there is no way the other peer can communicate the connection has been dropped, as the link disconnection happens abruptly. If read timeout is not set for xConnectedSocket, the default read timeout is the read timeout of the server socket that was listening for incoming TCP connections. If you haven’t configured a read timeout for the listening server socket then the default read timeout will be the one used and is defined by ipconfigSOCK_DEFAULT_RECEIVE_BLOCK_TIME. By default this macro is defined to portMAX_DELAY, so unless you have redefined this macro in the FreeRTOSIPConfig.h the block time is indefinite.

You can check if you redefined ipconfigSOCK_DEFAULT_RECEIVE_BLOCK_TIME in FreeRTOSIPConfig.h.

In the case of link disconnection from the other end, there is a mechanism provided by TCP to detect dormant connection using keep-alive messages. You can read more about it here, note that this feature needs to be enabled explicitly by enabling the macro - ipconfigTCP_KEEP_ALIVE.

Also, instead of relying on the return value of FreeRTOS_recv, another alternate approach is to signal your TCP tasks that the connection has been dropped, maybe by a task notification or semaphore from the vApplicationIPNetworkEventHook_Multi whenever a network down event happens; that would be a faster way to know the link disconnection, and then you can reconfigure your connection.

I know about TCP keep-alive mechanism and that freertos plus tcp supports it, but my question is a little different.
Let me describe in more detail what is happening, so that it is clear what my question is.
I have redefined macros in FreeRTOSIPConfig.h

# define ipconfigSOCK_DEFAULT_RECEIVE_BLOCK_TIME ( 5000 )
# define ipconfigSOCK_DEFAULT_SEND_BLOCK_TIME ( 5000 )

and then I explicitly set timeouts during initialization. In this case, FreeRTOS_recv correctly uses them and returns according to the specified timeouts.

We start the board, go through initialization, make sure that there is ping, after which we connect as a TCP client from the PC to the server on the MCU, and the read, write and write stream is launched by the timer. From the client PC we send packets with a certain period.

Listen Task and create other Tasks
void prvConnectionListeningTask( void *pvParameters )
{
struct freertos_sockaddr xClient, xBindAddress;
Socket_t xListeningSocket, xConnectedSocket;
socklen_t xSize = sizeof( xClient );
static const TickType_t xReceiveTimeOut = portMAX_DELAY;
const BaseType_t xBacklog = TCP_NUMBER_OF_CLIENTS;

    /* Attempt to open the socket. */
    xListeningSocket = FreeRTOS_socket( FREERTOS_AF_INET4, /* Or FREERTOS_AF_INET6 for IPv6. */
                                        FREERTOS_SOCK_STREAM,  /* SOCK_STREAM for TCP. */
                                        FREERTOS_IPPROTO_TCP);

    /* Check the socket was created. */
    configASSERT( xListeningSocket != FREERTOS_INVALID_SOCKET );

    /* Set a time out so accept() will just wait for a connection. */
    FreeRTOS_setsockopt( xListeningSocket,
                         0,
                         FREERTOS_SO_RCVTIMEO,
                         &xReceiveTimeOut,
                         sizeof( xReceiveTimeOut ) );

    /* Set the listening port to TCP_Port. */
    memset( &xBindAddress, 0, sizeof(xBindAddress) );
    xBindAddress.sin_port = ( uint16_t ) TCP_Port;
    xBindAddress.sin_port = FreeRTOS_htons( xBindAddress.sin_port );
    xBindAddress.sin_family = FREERTOS_AF_INET4; /* FREERTOS_AF_INET6 to be used for IPv6 */

    /* Bind the socket to the port that the client RTOS task will send to. */
    FreeRTOS_bind( xListeningSocket, &xBindAddress, sizeof( xBindAddress ) );

    /* Set the socket into a listening state so it can accept connections.
       The maximum number of simultaneous connections is limited to 20. */
    FreeRTOS_listen( xListeningSocket, xBacklog );

    for( ;; )
    {
        /* Wait for incoming connections. */
        xConnectedSocket = FreeRTOS_accept( xListeningSocket, &xClient, &xSize );
				Client_ConnectedSocket=xConnectedSocket;
        configASSERT( xConnectedSocket != FREERTOS_INVALID_SOCKET );

        /* Spawn a RTOS task to handle the connection. */
				Client_Connect=true;
        xTaskCreate( prvServerConnectionRead,
                     "TCP_Connection_Read",
                     usUsedStackSize,
                     ( void * ) xConnectedSocket,
                     tskIDLE_PRIORITY,
                     &RTOS_Handle_Read );
										 
        xTaskCreate( prvServerConnectionWrite,
                     "TCP_Connection_Write",
                     usUsedStackSize,
                     ( void * ) xConnectedSocket,
                     tskIDLE_PRIORITY,
                     &RTOS_Handle_Write );
										 
        xTaskCreate( prvServerConnectionTimer,
                     "TCP_Connection_Timer",
                     usUsedStackSize,
                     0,
                     tskIDLE_PRIORITY,
                     &RTOS_Handle_Timer );		
    }
}
Read Task
void prvServerConnectionRead( void *pvParameters )
{
uint8_t cReceivedString[ ipconfigTCP_MSS ];
Socket_t xConnectedSocket;
static const TickType_t xReceiveTimeOut = pdMS_TO_TICKS( 3000 );
static const TickType_t xSendTimeOut = pdMS_TO_TICKS( 3000 );
TickType_t xTimeOnShutdown;

	ulConnectionCount++;
	xConnectedSocket = ( Socket_t ) pvParameters;
	FreeRTOS_setsockopt( xConnectedSocket, 0, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, sizeof( xReceiveTimeOut ) );
	FreeRTOS_setsockopt( xConnectedSocket, 0, FREERTOS_SO_SNDTIMEO, &xSendTimeOut, sizeof( xReceiveTimeOut ) );

	for( ;; )
	{
		if(Client_Connect)
		{
			/* Receive data on the socket. */
			Size = FreeRTOS_recv( xConnectedSocket, IN_Array, sizeof( IN_Array ), 0 );

			if( Size > 0 )
			{				
				vTaskResume(RTOS_Handle_Write);
			}
			else
			{
				if(Size==0)
				{					
					//break;
				}
				else
				{
					/* Socket closed? */
					if(Size==-pdFREERTOS_ERRNO_ENOMEM)
						break;
					if(Size==-pdFREERTOS_ERRNO_ENOTCONN)
						break;
					if(Size==-pdFREERTOS_ERRNO_EINTR)
						break;
					if(Size==-pdFREERTOS_ERRNO_EINVAL)
						break;
				}
			}
		}
		else
		{
			break;
		}
	}

	Client_Connect=false;
	/* Initiate a shutdown in case it has not already been initiated. */
	FreeRTOS_shutdown( xConnectedSocket, FREERTOS_SHUT_RDWR );

	/* Wait for the shutdown to take effect, indicated by FreeRTOS_recv()
	returning an error. */
	xTimeOnShutdown = xTaskGetTickCount();
	do
	{
		if( FreeRTOS_recv( xConnectedSocket, cReceivedString, ipconfigTCP_MSS, 0 ) < 0 )
		{
			break;
		}
	} while( ( xTaskGetTickCount() - xTimeOnShutdown ) < tcpechoSHUTDOWN_DELAY );

	/* Finished with the socket and the task. */
	FreeRTOS_closesocket( xConnectedSocket );
	vTaskResume(RTOS_Handle_Write);
	vTaskDelete( NULL );
}
Write Task
void prvServerConnectionWrite( void *pvParameters )
{
int32_t lSent;
Socket_t xConnectedSocket;

	xConnectedSocket = ( Socket_t ) pvParameters;
	for( ;; )
	{
		if(Client_Connect==false)
		{
			vTaskDelete(RTOS_Handle_Timer);
			vTaskDelete( NULL );
		}
		vTaskSuspend(NULL);
		
		if(Timer_Size==0)
		{
			lSent = FreeRTOS_send( xConnectedSocket, IN_Array, Size, 0 );
		}
		else		
		{
			lSent = FreeRTOS_send( xConnectedSocket, Timer_Array, Timer_Size, 0 );
			Timer_Size=0;
		}
		
		if( lSent < 0 )
		{
			/* Socket closed */
			Client_Connect=false;
			vTaskDelete(RTOS_Handle_Timer);
			vTaskDelete( NULL );
		}
	}
}
Timer Task
void prvServerConnectionTimer()
{
	for (int i=0;i<20;i++)
	{
		Timer_Array[i]=i+48;
		Timer_Size=20;
	}
	Timer_Array[Timer_Size-1]='\0';
	for( ;; )
	{
		Timer_Size=20;
		vTaskResume(RTOS_Handle_Write);	
		vTaskDelay(500);	
	}
}

Accordingly, the MCU returns the received packets back to the client, and also sends other packets to the client according to the timer (500 ms). At the same time, according to the read timeout (FreeRTOS_recv==0), we do not disconnect the client and do not clear the connection.

If you disconnect the network cable from the PC, then after 32 seconds FreeRTOS_recv will return from the next reading with the value -pdFREERTOS_ERRNO_ENOTCONN, which means that the socket has closed or was closed.
This is clearly due to attempts to send packets according to the 500 ms timer, because if you turn it off, then I do not know when FreeRTOS_recv will return with a negative value and whether it will return at all (this did not happen within a reasonable time).
Actually, the question is what it depends on when the socket decides that attempts to send packets are pointless and closes it, which will lead to FreeRTOS_recv==-pdFREERTOS_ERRNO_ENOTCONN.

Thanks

@Rone4ka

In that case, what you are looking for is ipconfigTCP_HANG_PROTECTION_TIME, ipconfigTCP_HANG_PROTECTION is by default enabled, and the ipconfigTCP_HANG_PROTECTION_TIME is set to ~30 seconds. This is the maximum time the FreeRTOS+TCP stack will wait for either a TCP state change or handling a packet (RX). If that time limit is exceeded, the socket state is changed to eCLOSE_WAIT, leading to FreeRTOS_recv returning pdFREERTOS_ERRNO_ENOTCONN.

On a side note, using task suspend and resume to signal/synchronize tasks might lead to incorrect behavior. If vTaskResume is called before vTaskSuspend (which is quite possible), the task will not suspend. FreeRTOS direct to task notifications should be a possible alternative.

@tony-josi-aws

Now I think I figured it out and learned everything I wanted.

Yes, in a real application I will use the vTaskNotifyGiveFromISR and ulTaskNotifyTake mechanisms in the task. Here it was more of a test (and not very clean) code to clarify my question.

Thanks a lot.

Thanks for reporting back.