FreeRTOS+TCP disable and re-enable the Ethernet

Background: I’m working on a STM32F7 based device that uses ethernet and FreeRTOS+TCP lib for communication with PC. For a significant amount of time, the device will be powered from the backup battery and PC it’s connected turned OFF. So I’m on a quest to reduce power consumption as much as possible/reasonable during those periods, so logical action would be to shut down everything related to the ethernet connection.

At the moment, I’m trying to turn OFF the PHY IC and ETH peripheral so that interface could be turned ON again without restarting the MCU (requirement).
I’m using Network management code from the samples provided with the library with almost no modification and added functions to shut down the interface:

  • Delete EMAC task,
  • Send PHY to power-down mode,
  • deinit ETH

…and to turn it back ON:

  • Wakeup PHY
  • Send Network down event (via FreeRTOS_NetworkDown() ) to reinit ETH and create EMAC task again

But achieved only crashes and watchdog resets after a cycle or two.

Is there a recommended way how to gracefully turn the interface OFF and reinit it again? List of things to take care of? Is it even a good idea in the context of FreeRTOS+TCP?
I would appreciate any pointers in the right direction.

Hello @Igor_Petrov, would it be possible to show the code that you wrote related to Ethernet and the PHY? If you want I try it out on an STM32F7x.
Is you project using the WDT? Why would it cause a reset? Do you stop touching it?
Did you disable the ETH ISR?

Hello @Igor_Petrov,

I had typed a reply but forgot to press the button.

Hein (@htibosch) has asked the same questions that I would have asked. If there is your ‘fork’ which has the mentioned project, I would also like to test it out on my device.

Also, when does the device starts to misbehave? When you are trying to make the device go to sleep or when you are trying to wake the device up?

@Igor_Petrov wrote:

Delete EMAC task

I don’t think that is necessary, it is enough to put it asleep.

Note that the EMAC task has a for-ever loop, and it sleeps (blocks) here:

    /* No events to process now, wait for the next. */
    ulTaskNotifyTake( pdFALSE, ulMaxBlockTime );

ulMaxBlockTime is the equivalent of 100 ms. It wants to check the status of the PHY. It will also check the global flags ulISREvents, which are set by the ETH interrupt.

Now suppose the device goes into low-power mode, I would send a message to the EMAC task to make it sleep for ever:

extern volatile BaseType_t xEthernetOperational;

    /* No events to process now, wait for the next. */
    if( xEthernetOperational != 0 )
    {
        /* Normal operation, check the PHY status. */
        ulTaskNotifyTake( pdFALSE, ulMaxBlockTime );
    }
    else
    {
        /* Low-power mode, sleep for ever. */
        ulTaskNotifyTake( pdFALSE, portMAX_DELAY );
    }

A tasks that is blocked permanently is not consuming any power because it will never be scheduled.

You can wake-up the EMAC task as simple as:

    xEthernetOperational = pdTRUE;
    xTaskNotifyGive( xEMACTaskHandle );

and put it back to sleep:

    xEthernetOperational = pdFALSE;
    xTaskNotifyGive( xEMACTaskHandle );

But, as said, it is important the stop the ETH interrupt and the EMAC.

When your device goes in low-power mode, for how long will that be? How many seconds, or minutes?

I am asking this because of the IP-task. Normally it wakes up every 10 seconds, but it can be configured to sleep much longer.

See internal macros like ipconfigMAX_IP_TASK_SLEEP_TIME, ipTCP_TIMER_PERIOD_MS, ipARP_TIMER_PERIOD_MS

PS will you also use the tickless kernel?

I don’t think it’s reasonable to share the code right now, it’s a broken mess after several days of trying different things. I’ll try the suggestions from your other reply, and share the code in case of no luck.

Is you project using the WDT? Why would it cause a reset? Do you stop touching it?

Yes, I use WDT and it causes reset because code gets stuck in asserts. One cause that I tacked down is that the buffer pool gets exhausted because every call to prvDMARxDescListInit gets 4 buffers for DMA descriptors, but they are never released (I use BufferAllocation_1 + zero copy driver). Now it’s obvious to me that this function needs a serious rework for what I’m trying to achieve, just not sure in what direction should I move.

BaseType_t xNetworkInterfaceInitialise( void )
{
	BaseType_t xResult;

	if( xEMACTaskHandle == NULL )
	{
		// ...
		
		/* Initialise ETH */
		// ...

		/* Make sure that all unused fields are cleared. */
		memset( &DMATxDscrTab, '\0', sizeof( DMATxDscrTab ) );
		memset( &DMARxDscrTab, '\0', sizeof( DMARxDscrTab ) );

		// ...

		/* Initialise RX-descriptors. */
		prvDMARxDescListInit();  // <<- HERE is the problem

		// ...

		xTaskCreate( prvEMACHandlerTask, "EMAC", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xEMACTaskHandle );
		
	} /* if( xEMACTaskHandle == NULL ) */

	if( xPhyObject.ulLinkStatusMask != 0 )
	{
		xETH.Instance->DMAIER |= ETH_DMA_ALL_INTS;
		xResult = pdPASS;
	} 
	else
	{
		xResult = pdFAIL;
	}
  return xResult;
}

Did you disable the ETH ISR?

Yes, as part of ETH deinitialization.

I don’t think that is necessary, it is enough to put it asleep.

Thanks for the suggestion, I’ll try to implement that.

When your device goes in low-power mode, for how long will that be? How many seconds, or minutes?
I am asking this because of the IP-task. Normally it wakes up every 10 seconds, but it can be configured to sleep much longer.

Hours/Days. However the device still has to execute more frequent tasks (monitor sensors, log events, etc.), IP-task waking up every 10 seconds does not bother me at the moment. But thanks for the suggestion, I’ll revisit this topic later.

PS will you also use the tickless kernel?

No.

I would recommend having a closer look at the tickless kernel. It allows you to let the CPU go into a deep sleep, reducing the power consumption to a few uA.
It will wakeup by any interrupt that you have defined and enabled.

EDIT
The good thing about FreeRTOS tickless idle is that the sense of time, the amount of clockticks, is not disturbed, even though it may miss many clock interrupts. The software will correct for this.
So you can still call vTaskDelay() and get woken up at the expected time, and xTaskGetTickCount() will return the time as if the CPU hasn’t been in deep sleeps.

Update. The solution suggested @htibosch, about putting EMAC task to sleep works fine. In addition to that, I had to reorganize the code in xNetworkInterfaceInitialise to separate the initialization of ETH periphery and DMA descriptors from the creation of EMAC tasks, and make sure that all network buffers get released after Ethernet is shut down.