cancel
Showing results for 
Search instead for 
Did you mean: 

Why does power management corrupt SPI registers on STM32WLE5?

charles23
Associate III

When attempting to add an accelerometer to the LoRaWAN_End_Node program, on the SPI1 interface, I find that it works (in polled mode) until the board sleeps and is woken with an interrupt. Then the SPI1 Control Register 1 and Control Register 2 are changed (so the accelerometer can't be accessed).

I have spent ages chasing this, as the exact nature of the corruption changes as my code changes. Because the corruption showed as soon as an interrupt happens (GPIO (button press), timer, subGHz radio) I assumed the problem was related to interrupts. Then I figured the corruption might be due to activity just before the interrupt, as the chip is put in a low power state. Sure enough, changing LOW_POWER_DISABLE from 0 to 1 in sys_conf.h makes the problem vanish.

Note the corruption varies and sometimes there is no corruption: CR1 can change from 0x035f to 0x0040 or to 0x0000 and CR2 can change from 0x1700 to 0x0700. The values are the same for a given build. It is almost as though it is code-position-dependent.

Can someone please tell me why the SPI interface is broken as I describe, and what I can do about it.

I see that AN5406 includes "7.3 Low-power functions" which I assume is relevant, but I would appreciate more specific guidance.

Will this problem exist with other peripherals, as well as the SPI?

1 ACCEPTED SOLUTION

Accepted Solutions
YBOUV.1
Senior

Hello,

you can find some valuable information in this video

https://st-onlinetraining.s3.amazonaws.com/STM32WL-System-Power-control-PWR/index.html

slide 4 and 28 are of particular interest for this topic.

So indeed, when entering stop2 mode, the SPI registers are not retained and registers content will be lost.

So the SPI (and any other not retained IP) configuration must be stored before entering stop2 and restored when waking up from stop2.

from FW perspective, this must indeed be carefully managed. The low power manager (LPM) helps to do this.

  • low power is entered when there is nothing to do (IDLE) for the cortex. UTIL_LPM_EnterLowPower() is called.
  • 3 power modes are supported as @charles23​ mentions: sleep/stop/off. Please note that you may choose to enter stop1 iso stop2 by changing the default code into the Core/Src/stm32_lpm_if.c file
  • up to 32 FW modules can set their low power requirements independently and dynamically. The LPM will get to the lowest consumption possible when system will go idle
    • One example is for the PRINTF which uses the DMA. The DMA clock is stopped in stop2 mode. Therefore, when the DMA is in use (printing characters to the console), the system shall not enter in stop2 mode or below. This is where UTIL_LPM_SetStopMode is used. When DMA is used, stop2 mode is disabled ( UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_DISABLE) ). When DMA has finished all transfer, stop2 mode is enabled again ( UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_ENABLE) ). Code below is located in Core/Src/sys_app.c
/* Disable StopMode when traces need to be printed */
void UTIL_ADV_TRACE_PreSendHook(void)
{
  /* USER CODE BEGIN UTIL_ADV_TRACE_PreSendHook_1 */
 
  /* USER CODE END UTIL_ADV_TRACE_PreSendHook_1 */
  UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_DISABLE);
  /* USER CODE BEGIN UTIL_ADV_TRACE_PreSendHook_2 */
 
  /* USER CODE END UTIL_ADV_TRACE_PreSendHook_2 */
}
/* Re-enable StopMode when traces have been printed */
void UTIL_ADV_TRACE_PostSendHook(void)
{
  /* USER CODE BEGIN UTIL_LPM_SetStopMode_1 */
 
  /* USER CODE END UTIL_LPM_SetStopMode_1 */
  UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_ENABLE);
  /* USER CODE BEGIN UTIL_LPM_SetStopMode_2 */
 
  /* USER CODE END UTIL_LPM_SetStopMode_2 */
}
  • by default, the code disables off mode,  UTIL_LPM_SetOffMode((1 << CFG_LPM_APPLI_Id), UTIL_LPM_DISABLE); in Core/Src/sys_app.c
  • as well, set stop2 mode is enabled by default. But it can be disabled by setting LOW_POWER_DISABLE to 1 in Core/Inc/sys_conf.h

Hope this helps.

Best regards

View solution in original post

8 REPLIES 8
TDK
Guru

Which low power mode? Some of them conserve power by removing power from peripherals.

If you feel a post has answered your question, please click "Accept as Solution".
charles23
Associate III

I am using the code present in the LoRaWAN_End_Node example, which uses code in stm32_lpm_if.c (documented in AN4506 - inadequately, IMHO). Looking at the code it seems that ST make available three modes, intended to be increasingly severe:

  1. sleep mode - least severe, calls HAL_PWR_EnterSLEEPMode() to "Enter Sleep or Low-power Sleep mode"
  2. stop mode - medium severity, calls HAL_PWREx_EnterSTOP2Mode() to "Enter Stop 2 Mode"
  3. off mode - most severe, but there seems to be no code in PWR_EnterOffMode() for this.

There seems to be a good deal of flexibility in stm32_lpm_if.c as to what happens in each mode (but little guidance from ST as to how to choose).

In the LoRaWAN_End_Node example "off mode" is disabled by default by a call to UTIL_LPM_SetOffMode(), so be default "stop mode" would have been used. When I changed LOW_POWER_DISABLE from 0 to 1 "stop mode" was also disabled, so "sleep mode" would be used.

So my experience seems to be: Stop 2 mode corrupts SPI register settings but sleep mode does not. Sure enough: Table 37 in RM0461 Reference Manual shows there is no "R" (indicating data retained) against the SPI peripheral in Stop 2 mode (but Stop mode 0 or 1 should be OK). I guess it would also be possible to re-initialise the SPI interface on exit from low power, or before use....

So there we have the explanation. This wasted a load of my time. It would be good if ST could provide more prominant pointers to power management, and how to make intelligent selections from what is a very complex area.

YBOUV.1
Senior

Hello,

you can find some valuable information in this video

https://st-onlinetraining.s3.amazonaws.com/STM32WL-System-Power-control-PWR/index.html

slide 4 and 28 are of particular interest for this topic.

So indeed, when entering stop2 mode, the SPI registers are not retained and registers content will be lost.

So the SPI (and any other not retained IP) configuration must be stored before entering stop2 and restored when waking up from stop2.

from FW perspective, this must indeed be carefully managed. The low power manager (LPM) helps to do this.

  • low power is entered when there is nothing to do (IDLE) for the cortex. UTIL_LPM_EnterLowPower() is called.
  • 3 power modes are supported as @charles23​ mentions: sleep/stop/off. Please note that you may choose to enter stop1 iso stop2 by changing the default code into the Core/Src/stm32_lpm_if.c file
  • up to 32 FW modules can set their low power requirements independently and dynamically. The LPM will get to the lowest consumption possible when system will go idle
    • One example is for the PRINTF which uses the DMA. The DMA clock is stopped in stop2 mode. Therefore, when the DMA is in use (printing characters to the console), the system shall not enter in stop2 mode or below. This is where UTIL_LPM_SetStopMode is used. When DMA is used, stop2 mode is disabled ( UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_DISABLE) ). When DMA has finished all transfer, stop2 mode is enabled again ( UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_ENABLE) ). Code below is located in Core/Src/sys_app.c
/* Disable StopMode when traces need to be printed */
void UTIL_ADV_TRACE_PreSendHook(void)
{
  /* USER CODE BEGIN UTIL_ADV_TRACE_PreSendHook_1 */
 
  /* USER CODE END UTIL_ADV_TRACE_PreSendHook_1 */
  UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_DISABLE);
  /* USER CODE BEGIN UTIL_ADV_TRACE_PreSendHook_2 */
 
  /* USER CODE END UTIL_ADV_TRACE_PreSendHook_2 */
}
/* Re-enable StopMode when traces have been printed */
void UTIL_ADV_TRACE_PostSendHook(void)
{
  /* USER CODE BEGIN UTIL_LPM_SetStopMode_1 */
 
  /* USER CODE END UTIL_LPM_SetStopMode_1 */
  UTIL_LPM_SetStopMode((1 << CFG_LPM_UART_TX_Id), UTIL_LPM_ENABLE);
  /* USER CODE BEGIN UTIL_LPM_SetStopMode_2 */
 
  /* USER CODE END UTIL_LPM_SetStopMode_2 */
}
  • by default, the code disables off mode,  UTIL_LPM_SetOffMode((1 << CFG_LPM_APPLI_Id), UTIL_LPM_DISABLE); in Core/Src/sys_app.c
  • as well, set stop2 mode is enabled by default. But it can be disabled by setting LOW_POWER_DISABLE to 1 in Core/Inc/sys_conf.h

Hope this helps.

Best regards

charles23
Associate III

A short update ( and thanks to @YBOUV.1). I have returned to this project to look at reducing power consumption.

Previously I had set LOW_POWER_DISABLE to 1 to prevent entering STOP 2 mode and so preserve my SPI registers. I have now added code at the start of PWR_EnterStopMode() to save the values of SPI2 CR1 and CR2 registers. (There are other SPI read/write registers but I don't use them). I restore them at the end of PWR_ExitStopMode(). I can now restore LOW_POWER_DISABLE to 0, and my LIS2DW accelerometer on the SPI port works fine.

I draw 15uA from 3.3V with the accelerometer sampling at 200Hz and 2uA with the accelerometer still operational but in the 1.6Hz low power mode. This is good!

JJazb
Associate II

@charles23​, can you please share with us your modified PWR_EnterStopMode() and PWR_ExitStopMode() functions? 😊

OK, here is what I have done, but use at your own risk, and this is unsupported!

First, the changes in PWR_EnterStopMode() and PWR_ExitStopMode():

void PWR_EnterStopMode(void)
{
	/* USER CODE BEGIN EnterStopMode_1 */
 
	// Save and restore SPI registers either side of STOP2 mode, since registers are not preserved
	if (boardUsesSPI()) {
		spiSaveState();
	}
 
	/* USER CODE END EnterStopMode_1 */
	/* Suspend sysTick : work around for degugger problem in dual core (tickets 71085,  72038, 71087 ) */
	HAL_SuspendTick();
	/* Clear Status Flag before entering STOP/STANDBY Mode */
	LL_PWR_ClearFlag_C1STOP_C1STB();
 
	/* USER CODE BEGIN EnterStopMode_2 */
 
	/* USER CODE END EnterStopMode_2 */
	HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
	/* USER CODE BEGIN EnterStopMode_3 */
 
	/* USER CODE END EnterStopMode_3 */
}
 
void PWR_ExitStopMode(void)
{
	/* USER CODE BEGIN ExitStopMode_1 */
 
	/* USER CODE END ExitStopMode_1 */
	/* Resume sysTick : work around for degugger problem in dual core */
	HAL_ResumeTick();
	/*Not retained periph:
    ADC interface
    DAC interface USARTx, TIMx, i2Cx, SPIx
    SRAM ctrls, DMAx, DMAMux, AES, RNG, HSEM  */
 
	/* Resume not retained USARTx and DMA */
	vcom_Resume();
	/* USER CODE BEGIN ExitStopMode_2 */
	// Save and restore SPI registers either side of STOP2 mode, since registers are not preserved
	if (boardUsesSPI()) {
		spiRestoreState();
	}
	/* USER CODE END ExitStopMode_2 */
}

Then spiSaveState() and spiRestoreState() are added to my own spi.c file:

// Save and restore SPI registers either side of STOP2 mode, since registers are not preserved
void spiSaveState(void) {
	HAL_SPI_Save_State(&hspi);
}
 
void spiRestoreState(void) {
	HAL_SPI_Restore_State(&hspi);
}

And these calls HAL_SPI_Save_State() and HAL_SPI_Restore_State() are added to stm32wlxx_hal_spi.c as follows:

// Save SPI registers either side of STOP2 mode, since registers are not preserved
void HAL_SPI_Save_State(SPI_HandleTypeDef *hspi) {
	hspi->saved_cr1 = READ_REG(hspi->Instance->CR1);
	hspi->saved_cr2 = READ_REG(hspi->Instance->CR2);
	// There are other r/w registers but not used by this app
}
 
// Restore SPI registers either side of STOP2 mode, since registers are not preserved
void HAL_SPI_Restore_State(SPI_HandleTypeDef *hspi) {
	WRITE_REG(hspi->Instance->CR1, hspi->saved_cr1);
	WRITE_REG(hspi->Instance->CR2, hspi->saved_cr2);
}

The variables that save the state (saved_cr1 and saved_cr2) are additions to the hspi structure, as follows:

typedef struct __SPI_HandleTypeDef
{
  SPI_TypeDef                *Instance;      /*!< SPI registers base address               */
<snip>
 
  // Saved SPI register state here:
  uint32_t					saved_cr1;		// saved value of CR1
  uint32_t					saved_cr2;		// saved value of CR2
 
<snip>
} SPI_HandleTypeDef;

Obviously, I have only saved the state of registers that were causing me problems. This concept can be generalised to save whatever registers in whatever module you are using. This works well for me. Good luck.

YBOUV.1
Senior

Hi @charles23​ ,

If questions is answered please click on the Select as Best button on my reply in order to help other members of the community find the answer more quickly.

Thank you

Thank you, @charles23​!

I need something similar for I2C, because I have the same problem that you had before.

And yes, ofcourse, I will use it at my own risk 😁