5.stm32L476在freeRTOS下使用低功耗

之前说过了硬件层次的降低功耗,stm32中通过tickless降低功耗的方法等(ma级),但是功耗还是不够低。在使用stm32L476单片机过程中发现有更多的低功耗模式,和时钟选择。由于系统需要低功耗,但是同时对实时性要求也很高(快速唤醒),在低功耗的时候,串口通讯还要及时,定时器还要工作。基于这种需求,最终选择了stm32L系列的stop2模式。(stop2+LPUART+LPTIM1+RTC)

5.stm32L476在freeRTOS下使用低功耗_第1张图片

stop2模式下,sRAM中的数据不会丢失,同时LSE,LSI时钟不会关闭,支持多种的唤醒方式,目前这里使用RTC唤醒,LPTIM唤醒和LPUART。

这种方式下,最重要的就是要考虑好1.时钟如何配置,2.进入stop2模式前配置好唤醒源的初始化3.唤醒后的时钟恢复4.最重要的还有当我们进入stop2后,主时钟都会关闭,那么睡眠后的这段时间就会丢失,当唤醒后,重新进入低功耗就会出现任务调度异常,如何找回这段时间是成功的关键。

 

在实现之前,我们一般都会找些参考程序,st官方提供了RTC唤醒和LPUART唤醒stop2模式的历程

STM32Cube_FW_L4_V1.14.0\Projects\NUCLEO-L476RG\Examples\PWR\PWR_STOP2_RTC

STM32Cube_FW_L4_V1.14.0\Projects\NUCLEO-L476RG\Examples_LL\LPUART\LPUART_WakeUpFromStop2

但是都是不带系统的,但是我们一般实现一个项目,使用裸机前后台任务操作用于多任务的处理是比较麻烦的,这里添加了FreeRTOS实时系统。

将官方代码移植到系统中,单独的通过RTC唤醒是没有问题的,单独的通过LPUART也是没问题,但是结合到一块就出现问题了,分析,在RTC的配置中,

 tick_sleep = ((uint32_t)prvGetExpectedIdleTime());

 HAL_RTCEx_DeactivateWakeUpTimer(&RTCHandle);//配置RTC唤醒

HAL_RTCEx_SetWakeUpTimer_IT(&RTCHandle,tick_sleep*2,RTC_WAKEUPCLOCK_RTCCLK_DIV16)

 

通过系统自带的函数,动态的计算出到下个任务的时间,也就是可睡眠的时间,然后设置到RTC中,此时的睡眠时间是可知的,睡眠是不影响任务调度的。但是加入LPUART的唤醒后,调度上就会变得复杂,因为不知道你的串口唤醒什么时候来,来了之后就会恢复时钟,此时的调度就会出错了。现象也确实如此,定义了两个任务,一个串口任务,一个1s周期性开灯任务,引入LPUART唤醒后,灯的闪动就出现了异常,也就是影响了系统调度时间。

 

一.时钟分配

时钟的分配,是低功耗里很关键的一步。如何设置?

5.stm32L476在freeRTOS下使用低功耗_第2张图片

 

1.这里面有几个比较关键的地方,RTC使用的是LSI  32Khz的时钟,当然也可以使用LSE,这里只要将RTC与相应的时钟关联好,同时RTC初始时,将分频初始化好,不然唤醒时间就会不准,RTC时间如何计算可参考这个博客

https://blog.csdn.net/qingwufeiyang12346/article/details/80686350

5.stm32L476在freeRTOS下使用低功耗_第3张图片

 

注意我们的LSI是32K,他的里面是用的别的型号,时钟不同。

 

2.我们主时钟使用的MSI,最大48M,使用HSI也可以,但是HSI只能到16M,这里使用48M原因和唤醒时间还有一定关系。官方资料说,在48MHz的MSI下,能够1us唤醒。

注意不要使用HSE时钟(没用过)。同时系统中需要手动配置唤醒之后的时钟 LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_MSI);

 

3.LPUART选用LSE 不能选用PCLK,因为低功耗后会被关闭,但是注意选用LSE,波特率最大仅支持到9600,再大的话会出现丢数据情况(一般丢首字节)。

4.唤醒后,要恢复stop2睡眠时停掉的时钟,不然不能正常使用。

 

 

二.如何将stop2唤醒和睡眠等操作加入到FreeRtos中?

st提供的历程中一般都是裸机操作,那么怎么加入到freertos中,在之前也说过,freeRtos低功耗原理上是在空闲任务的时候进入睡眠,任务调度的时候进行唤醒,空闲时间越长,肯定功耗就越低。我们需要参考freeRtos提供的tickless机制。

 

1.freeRTos功耗实现思路

在FreeRTOSConfig.h中打开#define configUSE_TICKLESS_IDLE 1

此时的tickless功耗模式就启动了,tickless中大概的实体函数是vPortSuppressTicksAndSleep()大体思路是这样的:

当处理器进入空闲任务后,关闭系统时钟(关闭滴答定时器中断),当其他中断发生或者其他任务需要处理的时候,处理器唤醒,因为关闭了系统时钟,需要补上这段时间(它使用了滴答定时器实现),通过一些机制得到低功耗模式的执行时间,唤醒后进行时间补偿,实现准确唤醒。

 

同时为了更低功耗,还做了一些接口,用于用户补充。

#define configPRE_SLEEP_PROCESSING(x) RTOS_PreSleepProcessing(x)//进入低功耗睡眠之前的第一个函数

#define configPOST_SLEEP_PROCESSING(x) RTOS_PostSleepProcessing(x)//低功耗唤醒后的第一个函数

 

这里面实现用户自己的函数,如降频,关中断,关外设等操作。

 

2.当我们使用RTC睡眠的时候

vPortSuppressTicksAndSleep不做修改,RTOS_PreSleepProcessing(x)进行睡眠操作(配置RTC唤醒,设置唤醒时间,进入stop2模式)

void OS_PreSleepProcessing(uint32_t *ulExpectedIdleTime)
{
       *ulExpectedIdleTime = 0;  //屏蔽掉默认的wfi指令执行方式,防止系统滴答唤醒
         

        tick_sleep = ((uint32_t)prvGetExpectedIdleTime()); 获取睡眠时间

        if(tick_sleep > 2) //判断是否进入低功耗
        {
            vTaskSuspendAll(); //挂起调度器

            HAL_RTCEx_DeactivateWakeUpTimer(&RTCHandle);//配置RTC唤醒

            HAL_RTCEx_SetWakeUpTimer_IT(&RTCHandle, tick_sleep*2,             
            RTC_WAKEUPCLOCK_RTCCLK_DIV16);

            EnterSTOP2Mode(); //进入低功耗

        }
}

void EnterSTOP2Mode(void)
{
  LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2);
  LL_LPM_EnableDeepSleep();  
  __WFI();
}

RTOS_PostSleepProcessing(x)进行唤醒操作(从新配置时钟)
void OS_PostSleepProcessing(uint32_t *ulExpectedIdleTime)
{
   xTaskResumeAll(); //恢复调度器
   SYSCLKConfig_STOP();//恢复时钟
}

void SYSCLKConfig_STOP(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  uint32_t pFLatency = 0;

  /* Enable Power Control clock */
  __HAL_RCC_PWR_CLK_ENABLE();

  /* Get the Oscillators configuration according to the internal RCC registers */

  HAL_RCC_GetOscConfig(&RCC_OscInitStruct);

  /* Enable PLL */

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_NONE;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {

  }

  /* Get the Clocks configuration according to the internal RCC registers */
  HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency);

  /* Select PLL as system clock source and keep HCLK, PCLK1 and PCLK2 clocks dividers as before */

  RCC_ClkInitStruct.ClockType     = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource  = RCC_SYSCLKSOURCE_PLLCLK;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK)
  {
 
  }

}

 

现象倒是正常,因为使用freeRTOS的获取可睡眠的时间,但是我们LPUART等外设唤醒时间不确定,同时stop模式下滴答时钟是停止的,运行肯定是有问题的。

FreeRTOS也确实支持vPortSuppressTicksAndSleep的重构

#define configUSE_TICKLESS_IDLE 2 允许重构。

那么滴答时钟改用LPTIM来记录补偿时间???重构下vPortSuppressTicksAndSleep??

直接用LPTIM作为滴答时钟,这样的思路应该也是可以的,但是不知道咋替换为滴答时钟啊??

 

三.结论:

1.Stop 2 模式基于 Cortex-M4 深度睡眠模式与外设时钟门控。在 Stop 2 模式下, Vcore 域中的所有时钟都会停止, PLL、 MSI、 HSI16 和 HSE 振荡器也被禁止。一些带有唤醒功能(I2C3 和 LPUART)的外设可以开启 HSI16 以获取帧,如果该帧不是唤醒帧,也可以在接收到帧后关闭 HSI16。SRAM1、 SRAM2、 SRAM3 和寄存器内容将保留,所有 I/O 引脚的状态与运行模式下相同。

2.根据手册可知,Stop 2 模式会关闭系统时钟,当前的OS Tick 基于内核的 Systick 定时器。那么在系统时钟停止后,OS Tick 也会停止,对于某些依赖 OS Tick 的应用,在进入 Stop 2 模式,又被中断唤醒后,就会出现问题,因此需要在系统唤醒后,对 OS Tick 进行补偿。Stop 2 模式下,绝大多数外设都停止工作,仅低功耗定时器 1(LP_TIM1)选择 LSI 作为时钟源后,仍然能正常运行,所以选择 LP_TIM1 作为 Stop 2 模式的时间补偿定时器。

 

3.接下来通过LP_TIM1进行睡眠时间的获取,确实获取到了,但是又该如何将补偿到时钟源中,那就直接赋值给系统时钟吧。但是在再次睡眠时,freertos自带的获取下个任务时间的函数却出错了,说明这么恢复系统时钟还不太对。

整体思路应该是对的,但是卡死在这了,后来想了想如果串口中断频繁触发,从stop2模式不断恢复,功耗也会受影响,决定将两种情况区分开来,如果当前一段时间可以睡眠就stop2+RTC唤醒,如果此阶段有必要串口接收就LPRUN低功耗模式或者sleep等其他系统时钟不停止的模式,实现多种模式的切换。不同的情况切换为不同的低功耗方式。

我的stm32l476的板子,48MHZ系统时钟 sleep模式下功耗在3ma左右。stop模式下在1ma以内,通过测试发现大部分的功耗就是在时钟这块。(freeRTos中只开启了一个闪灯任务,周期1s)

 

注:一般在低功耗的应用中,我们常会将管脚进行下拉操作,防止漏电流,但是要注意硬件和软件的匹配。假如当串口加了下拉电阻,你配置的时候却配置为上拉输出,此时就会导致一部分不必要的电量损失。

在满足性能的情况下想要实现更低的功耗,不继续整看来是不行啊,还好弄出来了,使用没问题,但是稳定性还要验证,核心的几个函数标出来。

1.系统时钟配置(就是上面cubmx框图的配置)

void SystemClock_Config(void)
{
	LL_FLASH_SetLatency(LL_FLASH_LATENCY_2);

	if(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_2)
	{
		Error_Handler();
	}
	LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);

	LL_RCC_LSI_Enable();

	/* Wait till LSI is ready */
	while(LL_RCC_LSI_IsReady() != 1)
	{

	}
	LL_RCC_MSI_Enable();

	/* Wait till MSI is ready */
	while(LL_RCC_MSI_IsReady() != 1)
	{

	}
	LL_RCC_MSI_EnablePLLMode();
	LL_RCC_MSI_EnableRangeSelection();
	LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
	LL_RCC_MSI_SetCalibTrimming(0);
	LL_PWR_EnableBkUpAccess();
	LL_RCC_ForceBackupDomainReset();
	LL_RCC_ReleaseBackupDomainReset();
	LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_LOW);
	LL_RCC_LSE_Enable();

	/* Wait till LSE is ready */
	while(LL_RCC_LSE_IsReady() != 1)
	{

	}
	LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI);
	LL_RCC_EnableRTC();
	LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 24, 
    LL_RCC_PLLR_DIV_2);
	LL_RCC_PLL_EnableDomain_SYS();
	LL_RCC_PLL_Enable();

	/* Wait till PLL is ready */
	while(LL_RCC_PLL_IsReady() != 1)
	{

	}
	LL_RCC_PLLSAI1_ConfigDomain_48M(LL_RCC_PLLSOURCE_MSI, LL_RCC_PLLM_DIV_1, 24,     
    LL_RCC_PLLSAI1Q_DIV_2);
	LL_RCC_PLLSAI1_EnableDomain_48M();
	LL_RCC_PLLSAI1_Enable();

	/* Wait till PLLSAI1 is ready */
	while(LL_RCC_PLLSAI1_IsReady() != 1)
	{

	}
	LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

	/* Wait till System clock is ready */
	while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
	{

	}
	LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
	LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
	LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
	LL_SetSystemCoreClock(48000000);
	LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_LSE);
	LL_RCC_SetI2CClockSource(LL_RCC_I2C2_CLKSOURCE_PCLK1);
	LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI);
	LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_LSI);
	LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLLSAI1);
	LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK);
}

2.用户唤醒的RTC的配置

void SystemPower_Config(void)
{
  /* Configure RTC */
  RTCHandle.Instance = RTC;

  RTCHandle.Init.HourFormat = RTC_HOURFORMAT_24;
  RTCHandle.Init.AsynchPrediv = RTC_ASYNCH_PREDIV;
  RTCHandle.Init.SynchPrediv = RTC_SYNCH_PREDIV;
  RTCHandle.Init.OutPut = RTC_OUTPUT_DISABLE;
  RTCHandle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  RTCHandle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

  if( HAL_RTC_Init(&RTCHandle) != HAL_OK)
  {
    /* Initialization Error */

  }
}

stm32l4xx_hal_msp.c添加代码

void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_PeriphCLKInitTypeDef  PeriphClkInitStruct;

  /*##-1- Configure the RTC clock source ######################################*/
  /* -a- Enable LSI Oscillator */
  RCC_OscInitStruct.OscillatorType =  RCC_OSCILLATORTYPE_LSI;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    while(1);
  }

  /* -b- Select LSI as RTC clock source */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  { 
    while(1);
  }

  /*##-2- Enable the RTC peripheral Clock ####################################*/
  /* Enable RTC Clock */
  __HAL_RCC_RTC_ENABLE();
  
  /*##-3- Configure the NVIC for RTC Alarm ###################################*/
  HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0x0, 0);
  HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

/**
  * @brief RTC MSP De-Initialization 
  *        This function freeze the hardware resources used in this example:
  *          - Disable the Peripheral's clock
  * @param hrtc: RTC handle pointer
  * @retval None
  */
void HAL_RTC_MspDeInit(RTC_HandleTypeDef *hrtc)
{
  /*##-1- Reset peripherals ##################################################*/
  __HAL_RCC_RTC_DISABLE();
}
void HAL_MspInit(void)
{
  /* USER CODE BEGIN MspInit 0 */

  /* USER CODE END MspInit 0 */

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_RCC_PWR_CLK_ENABLE();

  /* System interrupt init*/

  /* USER CODE BEGIN MspInit 1 */

  /* USER CODE END MspInit 1 */
}

3.核心的功耗操作,及时钟的恢复

void OS_PreSleepProcessing(uint32_t *ulExpectedIdleTime)
{

   *ulExpectedIdleTime = 0;  //屏蔽掉默认的wfi指令执行方式
   if(g_stopmode_flag)
   {
      tick_sleep = ((uint32_t)prvGetExpectedIdleTime());
      if(tick_sleep > 4) //判断是否进入低功耗
      {
          LPUART_PrepareToStopMode();//配置串口唤醒
          HAL_RTCEx_DeactivateWakeUpTimer(&RTCHandle);//配置RTC唤醒
          HAL_RTCEx_SetWakeUpTimer_IT(&RTCHandle, (tick_sleep)*2,     
          RTC_WAKEUPCLOCK_RTCCLK_DIV16);// Wakeup Time Base = 16 /(~32.000KHz) = ~0.5 ms
          count_start(); //重置计数
          tick_sys_old = xTaskGetTickCount();
         HAL_PWREx_EnterSTOP1Mode(PWR_STOPENTRY_WFI);//进入低功耗
         g_stopmode_start_flag = 1;
      }
    }

 }
void OS_PostSleepProcessing(uint32_t *ulExpectedIdleTime)
{
  time_count_sys =  LL_LPTIM_GetCounter(LPTIM2)+4;
  count_stop();
  if(g_stopmode_flag && g_stopmode_start_flag)
  {
    g_stopmode_start_flag = 0;
    if(time_count_sys == tick_sleep)
    {
    //表示为RTC唤醒
    rtc_wakeup_flag =1;

    }
    SYSCLKConfig_STOP();//恢复时钟
  }

}
//时钟的恢复,要和配置对应,怎么配置的怎么恢复
void SYSCLKConfig_STOP(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  uint32_t pFLatency = 0;

  /* Enable Power Control clock */
  __HAL_RCC_PWR_CLK_ENABLE();

  /* Get the Oscillators configuration according to the internal RCC registers */
  HAL_RCC_GetOscConfig(&RCC_OscInitStruct);

  /* Enable PLL */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_NONE;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {

  }

  /* Get the Clocks configuration according to the internal RCC registers */
  HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency);

  /* Select PLL as system clock source and keep HCLK, PCLK1 and PCLK2 clocks dividers as         
  before */
  RCC_ClkInitStruct.ClockType     = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource  = RCC_SYSCLKSOURCE_PLLCLK;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK)
  {

  }
}

4.tickless函数里的操作,也可以直接从新生成一个函数,我是直接基于tickless改的。只添加了两处

__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
	{
	uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL;
	TickType_t xModifiableIdleTime;

		/* Make sure the SysTick reload value does not overflow the counter. */
		if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
		{
			xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
		}

		/* Stop the SysTick momentarily.  The time the SysTick is stopped for
		is accounted for as best it can be, but using the tickless mode will
		inevitably result in some tiny drift of the time maintained by the
		kernel with respect to calendar time. */
		portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;

		/* Calculate the reload value required to wait xExpectedIdleTime
		tick periods.  -1 is used because this code will execute part way
		through one of the tick periods. */
		ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
		if( ulReloadValue > ulStoppedTimerCompensation )
		{
			ulReloadValue -= ulStoppedTimerCompensation;
		}

		/* Enter a critical section but don't use the taskENTER_CRITICAL()
		method as that will mask interrupts that should exit sleep mode. */
		__disable_interrupt();
		__DSB();
		__ISB();


		/* If a context switch is pending or a task is waiting for the scheduler
		to be unsuspended then abandon the low power entry. */
		if( eTaskConfirmSleepModeStatus() == eAbortSleep )
		{
			/* Restart from whatever is left in the count register to complete
			this tick period. */
			portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;

			/* Restart SysTick. */
			portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

			/* Reset the reload register to the value required for normal tick
			periods. */
			portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;

			/* Re-enable interrupts - see comments above __disable_interrupt()
			call above. */
			__enable_interrupt();
		}
		else
		{
			/* Set the new reload value. */
			portNVIC_SYSTICK_LOAD_REG = ulReloadValue;

			/* Clear the SysTick count flag and set the count value back to
			zero. */
			portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

			/* Restart SysTick. */
			portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

			/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
			set its parameter to 0 to indicate that its implementation contains
			its own wait for interrupt or wait for event instruction, and so wfi
			should not be executed again.  However, the original expected idle
			time variable must remain unmodified, so a copy is taken. */
			xModifiableIdleTime = xExpectedIdleTime;
			configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
			if( xModifiableIdleTime > 0 )
			{
				__DSB();
				__WFI();
				__ISB();
			}
			configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

			/* Stop SysTick.  Again, the time the SysTick is stopped for is
			accounted for as best it can be, but using the tickless mode will
			inevitably result in some tiny drift of the time maintained by the
			kernel with respect to calendar time. */
			ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG;
			portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT );

			/* Re-enable interrupts - see comments above __disable_interrupt()
			call above. */
			__enable_interrupt();

			if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 || (rtc_wakeup_flag ==1))
			{
				uint32_t ulCalculatedLoadValue;
                                rtc_wakeup_flag = 0;
                                if(g_stopmode_flag)  //1.这里是新添加的
                                {
                                 ulCompleteTickPeriods = time_count_sys;
                                }
                                else
                                {
                                  /* The tick interrupt has already executed, and the SysTick
                                  count reloaded with ulReloadValue.  Reset the
                                  portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
                                  period. */
                                  ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );

                                  /* Don't allow a tiny value, or values that have somehow
                                  underflowed because the post sleep hook did something
                                  that took too long. */
                                  if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
                                  {
                                          ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
                                  }

                                  portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;

                                  /* The tick interrupt handler will already have pended the tick
                                  processing in the kernel.  As the pending tick will be
                                  processed as soon as this function exits, the tick value
                                  maintained by the tick is stepped forward by one less than the
                                  time spent waiting. */
                                  ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
                                }
                               
			}
			else
			{
				/* Something other than the tick interrupt ended the sleep.
				Work out how long the sleep lasted rounded to complete tick
				periods (not the ulReload value which accounted for part
				ticks). */
				//ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
                                if(g_stopmode_flag) //2.这里是新添加的
                                {
                                 ulCompletedSysTickDecrements = time_count_sys;
                                }
                                else
                                {
                                  ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
                                }
				/* How many complete tick periods passed while the processor
				was waiting? */
				ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;

				/* The reload value is set to whatever fraction of a single tick
				period remains. */
				portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
			}

			/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
			again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
			value.  The critical section is used to ensure the tick interrupt
			can only execute once in the case that the reload register is near
			zero. */
			portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
			portENTER_CRITICAL();
			{
				portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
				vTaskStepTick( ulCompleteTickPeriods );
				portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
			}
			portEXIT_CRITICAL();
		}
	}

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(低功耗,#,FreeRTOS)