STM32F4 External event -- WFE 待機模式


The STM32F4xx are able to handle external or internal events in order to wake up the core (WFE).

The wakeup event can be generated either by:

  • (I've removed normal external interrupt mode details)

  • or configuring an external or internal EXTI line in event mode.
    When the CPU resumes from WFE,
    it is not necessary to clear the peripheral interrupt pending bit
    or the NVIC IRQ channel pending bit as the pending bit corresponding to the event line is not set.

喚醒事件管理

STM32 可以處理外部或內部事件來喚醒內核(WFE)。喚醒事件可以通過下述配置產生:

在外設的控制寄存器使能一個中斷,但不在NVIC中使能,同時在Cortex-M3的系統控制寄存器中使能SEVONPEND位。
當CPU從WFE恢復后,需要清除相應外設的中斷掛起位和外設NVIC中斷通道掛起位(在NVIC中斷清除掛起寄存器中)。

配置一個外部或內部EXTI線為事件模式,當CPU從WFE恢復后,因為對應事件線的掛起位沒有被置位,
不必清除相應外設的中斷掛起位或NVIC中斷通道掛起位。

SEVONPEND位,這個位是什么作用呢?權威指南這么說:

發生異常懸起時請發送事件,用於在一個新的中斷懸起時從 WFE 指令處喚醒。
不管這個中斷的優先級是否比當前的高,都喚醒。
如果沒有 WFE導致睡眠,則下次使用 WFE 時將立即喚醒

 

在以下條件下執行WFI(等待中斷)或WFE(等待事件)指令:

– 設置Cortex-M3系統控制寄存器中的SLEEPDEEP位
– 清除電源控制寄存器(PWR_CR)中的PDDS位 進入
– 通過設置PWR_CR中LPDS位選擇電壓調節器的模式

注:為了進入停止模式,所有的外部中斷的請求位(掛起寄存器(EXTI_PR))
和RTC的鬧鍾標志都必須被清除,否則停止模式的進入流程將會被跳過,程序繼續運行

如果執行WFI進入停止模式: 設置任一外部中斷線為中斷模式(在NVIC中必須使能相應的外部中斷向量)。

如果執行WFE進入停止模式: 設置任一外部中斷線為事件模式。

//進入停止模式      
{ 
  u32 tmpreg tmpreg = PWR->CR;
  tmpreg &= ~(1<<1);      //清除PWR_CR的PDDS
  tmpreg |=(1<<0);      //設置PWR_CR的 LPDS
  PWR->CR = tmpreg;
  SCB->SCR|=1<<2;       //使能SLEEPDEEP位 (SYS->CTRL)           
  __WFI();              //執行WFI指令   
}

 

//進入待機模式
void Sys_Standby(void)
{ 
  SCB->SCR|=1<<2;         // 使能SLEEPDEEP位 (SYS->CTRL)   
  RCC->APB1ENR|=1<<28;    // 使能電源時鍾           
  PWR->CSR|=1<<8;         // 設置WKUP用於喚醒
  PWR->CR|=1<<2;          // 清除Wake-up 標志(WUF位)
  PWR->CR|=1<<1;          // PDDS置位              
  __WFI();                // 執行WFI指令        
}

 

 

 

 

So it appears the main purpose is to enable wakeups without generating an interrupt
or having to respond to interrupts during normal operation.

It's not mentioned in that guide and I'm not sure how applicable is to the STM32 architecture
but on some other devices similar schemes can be useful to catch fast events without generating an interrupt.

For example you may have an application where it's important to capture that a sub-microsecond event has occurred,
but there's no need to respond to it quickly so you can just check a flag to see if it's occurred. 

4.3.3 of the reference manual says: 

If the WFE instruction is used to enter Sleep mode, the MCU exits Sleep mode as soon as an event occurs. 
This event can be either an interrupt enabled in the peripheral control register but not in the NVIC,
or an EXTI line configured in event mode. 

Has anybody managed to set WFE and exit it using "an interrupt enabled in the peripheral control register but not in the NVIC" ? 

This implies any interrupt flag will do it, but it only wakes up for me if the interrupt is set in the nvic (and executes).

I haven't tried it myself, but it looks like there are two way to do it. 

The ST reference manual seems to be referring the method 1 below: 

1) Using the Event mask register. 

Beside from enabling the interrupt in the peripheral control,  you also need to set the Event Mask Register (EXTI_EMR)

(section 8.3.2) to enable the peripheral to trigger the event signal.

And base on the circuit diagram in figure 16 (section 8.2.2) you also need to make sure

you set the interrupt source as rising edge trigger or falling edge trigger.

(so in total you must set up as least two registers on top of the peripheral's interrupt enable). 

2) Use SEVONPEND feature on NVIC 

This control bit is in bit[4] of NVIC System Control Register (0xE000ED10) is SEVONPEND control for NVIC.

When a new interrupt pending occurred,  this should be able to

generate a internal event in the NVIC to wake up the core

To use the event mask resister, I think the event needs to be one of those linked to an exti line. 

However, setting the SEVONPEND bit works perfectly :) 

If anyone's looking, theres a firmware library function to do it: 

NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, ENABLE); 

 

I have noticed that my __WFE(); sometimes returns immdiately. 

This is described in D.7.68 of the ARMv7M Architecture Application Level Reference Manual: 

If the Event Register is set, Wait For Event clears it and returns immediately. 

By calling WFE twice in succession: 

If the Event Register is Set, the first WFE will clear it, and the second will sleep. 
If the Event Register is not set, the first will sleep until it is set, and then the second will clear it. 
However, this is clearly unsafe, since if the actual event occurs before the first WFE
(e.g. delayed by an interrupt) then it will sleep until a further event occurs (if one ever does). 
Does anybody know the address of the Event Register to clear it properly? 

The arm "Application Level" manual suggests calling ClearEventRegister(),
and the proper manual is not available for download. 

Use the following assembly sequence: 

SEV ; Send Event , this set the event register to 1 
WFE ; 1st WFE will not cause sleep, but will cause event reg to clear 
WFE ; 2nd WFE will cause sleep 

Excellent, thanks again Joseph! 
Just another note to anyone else trying this;

before calling WFE make sure that you clear the pending bit in the nvic
that you are waiting to see change (e.g. call NVIC_ClearIRQChannelPendingBit).

I forgot to mention a corner case: 

If you use SEV - WFE - WFE sequence, and if there is a interrupt take place
between the two WFE, the event register will get set again
and the second WFE will not enter sleep. 

Alternatively, you could use a loop to handle this 

  int loopexit = 0;
  while ( loopexit == 0 )
  {
    __WFE( ); // Try to sleep 
    if ( Check_If_Work_Need_To_Be_Done( ) )
    {
      Do_Some_Work( );
    }
    loopexit = Check_If_Want_To_Exit_Sleep( );
  }

In this way it doesn't matter what is the current value of the event register,
as it will repeat the __WFE until it entered sleep or if you want it to exit from sleep. 

 

Here is an example of using WFE with EXTI. Hope that helps you.

int main( void )
{
  
  /*!< At this stage the microcontroller clock setting is already configured,
   this is done through SystemInit() function which is called from startup
   file (startup_stm32f37x.s) before to branch to application main.
   To reconfigure the default setting of SystemInit() function, refer to
   system_stm32f37x.c file
   */

  /* Configure PA0 in interrupt mode */
  EXTI0_Config( );
  
  /* Configure LEDs */
  STM_EVAL_LEDInit( LED1 );  
  STM_EVAL_LEDInit( LED2 );  
  STM_EVAL_LEDInit( LED4 );
  
  /* SysTick interrupt each 10 ms */
  if ( SysTick_Config( SystemCoreClock / 100 ) )
  {   
    /* Capture error */
    while ( 1 );    
  }
  
  /* LED1 On */
  STM_EVAL_LEDOn( LED1 );
  
  /* Request to enter STOP mode with regulator in low power mode */
  PWR_EnterSTOPMode( PWR_Regulator_LowPower, PWR_STOPEntry_WFE );
  
  /* LED1 On */
  STM_EVAL_LEDOn( LED2 );
  
  while ( 1 )
  {    
  }  
}

/**
 * @brief  Configure PA0 in interrupt mode
 * @param  None
 * @retval None
 */

static void EXTI0_Config( void )
{  
  EXTI_InitTypeDef EXTI_InitStructure;  
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /* Enable GPIOA clock */
  RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE );
  
  /* Configure PA0 pin as input floating */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;  
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
  GPIO_Init( GPIOA, &GPIO_InitStructure );  
  /* Enable SYSCFG clock */
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_SYSCFG, ENABLE );
  
  /* Connect EXTI0 Line to PA0 pin */
  SYSCFG_EXTILineConfig( EXTI_PortSourceGPIOA, EXTI_PinSource0 );
  
  /* Configure EXTI0 line */
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;  
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;  
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;  
  EXTI_Init( &EXTI_InitStructure );  
}

 

  /* init the TIM7 */
  RCC->APB1ENR |= RCC_APB1ENR_TIM7EN;
  TIM7->PSC = 7; // => 1 MHz timer clock frequency (at PCLK=8 MHz)
  TIM7->ARR = 1000 - 1; // => 1 kHz interrupt rate
  TIM7->DIER = TIM_DIER_UIE; // enable the update interrupt
  NVIC_EnableIRQ( TIM7_IRQn );
  TIM7->CR1 |= TIM_CR1_CEN; // start the timer
    
  RCC->APB1ENR |= RCC_APB1Periph_PWR;
  
  /* Configure PA0 pin as input floating */
  RCC->AHBENR |= RCC_AHBPeriph_GPIOA;
  RCC->APB2ENR |= RCC_APB2Periph_SYSCFG;
GPIO_InitStructure.GPIO_Pin
= GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_Init( GPIOA, &GPIO_InitStructure ); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init( &EXTI_InitStructure );
SYSCFG_EXTILineConfig( EXTI_PortSourceGPIOA, EXTI_PinSource0 );
PWR_WakeUpPinCmd( PWR_WakeUpPin_1, ENABLE );
/* Configure the pin to be toggled: PB2 */ RCC->AHBENR |= RCC_AHBPeriph_GPIOB; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_Init( GPIOB, &GPIO_InitStructure ); /* Configure PWR registers to enter STOP mode */ PWR->CR &= 0xFFFFFFFC; // clear the PDDS and LPDS bits PWR->CR |= 0x00000000; // STOP Mode: Regulator on //PWR->CR |= 0x00000001; // STOP Mode: Regulator off /* Set SLEEPDEEP bit of Cortex System Control Register */ SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __WFE( );
/* after waking-up from STOP mode this is the first instruction to be executed */ SCB->SCR &= (uint32_t) ~( (uint32_t) SCB_SCR_SLEEPDEEP_Msk ); /* Configure PB1 as Output */ GPIOB->ODR = 0x00000002;

 

Seeing the following explanation for WFE command from ARM,
I suspect that the event register is 1. But I don't have an explanation why.
This is the ARM explanation:

WFE is a hint instruction.

If the event register is 0, WFE suspends execution until one of the following events occurs:
- an exception, unless masked by the exception mask registers or the current priority level
- an exception enters the Pending state, if SEVONPEND in the System Control Register is set
- a Debug Entry request, if Debug is enabled
- an event signaled by a peripheral or another processor in a multiprocessor system using the SEV instruction.

 

If the event register is 1, WFE clears it to 0 and returns immediately.

That is why the second WFE works fine.

 

 

We have 2 instructions for entering low-power standby state 

where most clocks are gated: WFI and WFE.

WFE: Wait For Event
WFI: Wait For Interrupt
They differ slightly in their entry and wake-up conditions,

with the main difference being that WFE makes use of the event register,
the SEV instruction and EVENTI, EVENTO signals. WFI is targeted at entering either standby, dormant or shutdown mode,
where an interrupt is required to wake-up the processor. A usage for WFE is to put it into a spinlock loop.
Where a CPU wants to access a shared resource such as shared memory,
we can use a semaphore flag location managed by exclusive load and store access.
If multiple CPUs are trying to access the resource,
one will get access and will start to use the resource
while the other CPUs will be stuck in the spinlock loop.

To save power, you can insert the WFE instruction into the loop
so the CPUs instead of looping continuously will enter STANDBTWFE.
Then the CPU who has been using the resource should execute SEV instruction
after it has finished using the resource.
This will wake up all other CPUs from STANDBYWFE
and another CPU can then access the shared resource. The reason for having EVENTI and EVENTO is to export a pulse on EVENTO
when an SEV instruction is executed by any of the CPUs.
This signal would connect to EVENTI of a second Cortex-A5 MPCore cluster
and would cause any CPUs in STANDBYWFE state to leave standby.

So these signals just expand the usage of WFE mode across multiple clusters.
If you have a single cluster, then you do not need to use them.






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM