STM32中用 stop 模式 配合低功耗模式下的自动唤醒(AWU) 能否实现FreeRTOS tickless 模式

已经实现  ,2018年11月17日11:56:42,具体 如下:

第一步 : 修改 void vPortSetupTimerInterrupt( void ) 函数 ,修改原来的 systick 定时器初始化 改为  RTC 初始化 

    void vPortSetupTimerInterrupt( void )
    {
        NVIC_InitTypeDef NVIC_InitStructure;
        EXTI_InitTypeDef EXTI_InitStructure;
    
            /* Enable PWR and BKP clocks */  /* PWR时钟(电源控制)与BKP时钟(RTC后备寄存器)使能 */  
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
        
        /* Allow access to BKP Domain */ /*使能RTC和后备寄存器访问 */  
        PWR_BackupAccessCmd(ENABLE);

        RCC_LSICmd(ENABLE); /* 使能内部32.768K时钟 */

        /* 等待内部32.768K时钟就绪 */
        while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
        
        /* 选择内部32.768K时钟为RTC时钟 */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        
        /* Set RTC prescaler: set RTC period to 1 millisecond */   
        /* suozhang,使用外部32.768K晶振,设置32分频,计数1024次为1S,2018年11月15日09:28:52 */ 
        RTC_SetPrescaler( 40 );

        /* Enable the RTC Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;                                                                                    
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configKERNEL_INTERRUPT_PRIORITY;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                     
    NVIC_Init(&NVIC_InitStructure);
    
        /* Enable the RTC Alarm Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configKERNEL_INTERRUPT_PRIORITY;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);  
  
        //闹钟中断接到第17线外部中断
   EXTI_ClearITPendingBit(EXTI_Line17);
    
   EXTI_InitStructure.EXTI_Line = EXTI_Line17;
   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
   EXTI_InitStructure.EXTI_LineCmd = ENABLE;
   EXTI_Init(&EXTI_InitStructure);
     
        /* 使能电源管理时钟 */
         RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR , ENABLE);
     
         /* Enable RTC Clock */
        RCC_RTCCLKCmd(ENABLE);
        /* Wait until last write operation on RTC registers has finished */
        RTC_WaitForLastTask();
        
        /* 使能RTC根据选择的时钟源以及分频值的设置产生的定时中断,当做FreeRTOS时钟源 */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        RTC_WaitForLastTask();
        
    }

第二步:增加 RTC 中断服务函数

void RTCAlarm_IRQHandler( void )
{
    //必须加上这个中断服务函数,否则RTC闹钟唤醒后,只能执行中断服务函数,suozhang,2018年11月15日13:47:34
}

void RTC_IRQHandler( void )
{
    
 if(RTC_GetITStatus(RTC_IT_SEC)!= RESET)//RTC定时中断
 {
        RTC_WaitForLastTask();
        RTC_ClearITPendingBit(RTC_IT_SEC);  //清除RTC定时中断
     
        /* The SysTick runs at the lowest interrupt priority, so when this interrupt
        executes all interrupts must be unmasked.  There is therefore no need to
        save and then restore the interrupt mask value as its value is already
        known - therefore the slightly faster vPortRaiseBASEPRI() function is used
        in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
        vPortRaiseBASEPRI();
        {
            /* Increment the RTOS tick. */
            if( xTaskIncrementTick() != pdFALSE )
            {
                /* A context switch is required.  Context switching is performed in
                the PendSV interrupt.  Pend the PendSV interrupt. */
                portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
            }
        }
        vPortClearBASEPRIFromISR();
 
 }

 if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
 {
        RTC_WaitForLastTask();
        RTC_ClearITPendingBit(RTC_IT_ALR);  //清闹钟中断

        RTC_WaitForLastTask();
        EXTI_ClearITPendingBit(EXTI_Line17);

        if(PWR_GetFlagStatus(PWR_FLAG_WU) != RESET)
        {
            RTC_WaitForLastTask();
            PWR_ClearFlag(PWR_FLAG_WU); //清自动唤醒中断( AWU )
        }
 }    
            
}

第三步:因为要实现 tickless 模式,因此要实现 void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) 这个函数接口

 void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
    {
    uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
    TickType_t xModifiableIdleTime;
        
        uint32_t exitTime = 0;

        /* 停止RTC产生 1ms 的定时中断 */     
        RTC_ITConfig(RTC_IT_SEC, DISABLE);    
        /* Wait until last write operation on RTC registers has finished */
        RTC_WaitForLastTask();

        /* 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. */
        //根据参数xExpectIdleTime来计算滴答定时器的重载值,进入低功耗之后,计时由滴答定时器计算。
        ulReloadValue = xExpectedIdleTime - 1UL ;

        if( ulReloadValue > ulStoppedTimerCompensation )
        {
            ulReloadValue -= ulStoppedTimerCompensation;//上面已经停止RTC了,下面一直到启动RTC闹钟的 程序运行时间补偿,小于1mS,因此这里为0
        }

        /* Enter a critical section but don't use the taskENTER_CRITICAL()
        method as that will mask interrupts that should exit sleep mode. */
        __disable_irq();
        __dsb( portSY_FULL_READ_WRITE );
        __isb( portSY_FULL_READ_WRITE );

        /* 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 )
        {

            /* 再次启动 RTC产生 1ms 的定时中断*/
            RTC_ITConfig(RTC_IT_SEC, ENABLE);

            /* Re-enable interrupts - see comments above __disable_irq() call
            above. */
            __enable_irq();
        }
        else
        {
            /* RTC 计数器清0 */
            RTC_SetCounter( 0 );
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
            
            /* RTC 计数器清0 */    
            RTC_SetAlarm( ulReloadValue ); // 设置经过ulReloadValue 计时后,产生闹钟
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
            
            RTC_ClearITPendingBit(RTC_IT_ALR);  //清闹钟中断
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
            
            PWR_ClearFlag(PWR_FLAG_WU); //清自动唤醒中断( AWU )
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
            
            /* 启动 RTC 闹钟中断. */
            RTC_ITConfig(RTC_IT_ALR, ENABLE);
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
            
            /* 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 )
            {
                __enable_irq();
                PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE);//进入停止模式
            }
            configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
            
            /* 关闭 RTC 闹钟中断. */
            RTC_ITConfig(RTC_IT_ALR, DISABLE);
            
            /* 再次启动 RTC产生 1ms 的定时中断*/
            RTC_ITConfig(RTC_IT_SEC, ENABLE);
            
            /* Re-enable interrupts to allow the interrupt that brought the MCU
            out of sleep mode to execute immediately.  see comments above
            __disable_interrupt() call above. */
            __enable_irq();
            __dsb( portSY_FULL_READ_WRITE );
            __isb( portSY_FULL_READ_WRITE );

            /* Disable interrupts again because the clock is about to be stopped
            and interrupts that execute while the clock is stopped will increase
            any slippage between the time maintained by the RTOS and calendar
            time. */
            __disable_irq();
            __dsb( portSY_FULL_READ_WRITE );
            __isb( portSY_FULL_READ_WRITE );
            
            /* 读取RTC计数值,用于补偿真正的系统时间,有可能其他事件唤醒,而不是闹钟唤醒,2018年11月16日11:07:31,suozhang */
            exitTime = RTC_GetCounter();
            /* Wait until last write operation on RTC registers has finished */
            RTC_WaitForLastTask();
                
            if( exitTime )
                vTaskStepTick(exitTime-0); //补偿真正的系统时间,进入闹钟定时之前,已经把计数器清0 因此这里减0

            /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
            again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
            value. */

            /* Exit with interrpts enabled. */
            __enable_irq();
        }
    }

最后  有 两个 函数 说明一下:

         configPRE_SLEEP_PROCESSING( xModifiableIdleTime );  // 进入低功耗之前 要执行的函数,用户可自定义!
         configPOST_SLEEP_PROCESSING( xExpectedIdleTime );   // 退出低功耗 第一个 执行的函数,用户可自定义!
 
最后测试一下: 测试代码 如下
void vLedTask( void *pvParameters )
{
    
    for(;;)
    {

            //PC13 LED1 
            bsp_LedToggle(1);
        
            vTaskDelay( 1000*5 ); 


    }
}

功耗测试结果 如下:  5S 高电流 即 LED 点亮时间, 5S低电流 是 13uA  正好达到裸机 进入 STOP 模式 的电流!

 
原文地址:https://www.cnblogs.com/suozhang/p/9946097.html