FreeRTOS操作系统——低功耗Tickless模式(十九)

FreeRTOS操作系统学习

前言

很多应用场合对于空耗的要求很严格,比如长期无人照看的数据采集仪器,可穿戴设备等。其实很多 MCU 都有相应的低功耗模式,以此来降低设备运行时的功耗,进行裸机开发的时候就可以使用这些低功耗模式。但是现在我们要使用操作系统,因此操作系统对于低功耗的支持也显得尤为重要,这样硬件与软件相结合,可以进一步降低系统的功耗。这样开发也会方便很多,毕竟系统已经原生支持低功耗了,我们只需要按照系统的要求来做编写相应的应用层代码即可。

一、低功耗模式

STM32 本身就支持低功耗模式
睡眠(Sleep)模式。
停止(Stop)模式。
待机(Standby)模式。
在这里插入图片描述
我们只了解睡眠模式即可

进入睡眠模式:
进入睡眠模式有两种指令:WFI(等待中断)和WFE(等待事件)。根据Cortex-M 内核的SCR(系统控制)寄存器可以选择使用立即休眠还是退出时休眠,当 SCR 寄存器的 SLEEPONEXIT(bit1)位为 0 的时候使用立即休眠,当为 1 的时候使用退出时休眠。

退出休眠模式:
如果使用 WFI 指令进入休眠模式的话那么任意一个中断都会将 MCU 从休眠模式中唤醒,如果使用WFE 指令进入休眠模式的话那么当有事件发生的话就会退出休眠模式,比如配置一当 STM32F103 处于休眠模式的时候 Cortex-M3 内核停止运行,但是其他外设运行正常,比如 NVIC、SRAM 等。休眠模式的功耗比其他两个高,但是休眠模式没有唤醒延时,应用程序可以立即运行

二、Tickless模式

1、低功耗详解

FreeRTOS 的系统时钟是由滴答定时器中断来提供的,系统时钟频率越高,那么滴答定时器中断频率也就越高。中断是可以将 STM32从睡眠模式中唤醒,周期性的滴答定时器中断就会导致 STM32周期性的进入和退出睡眠模式。因此,如果滴答定时器中断频率太高的话会导致大量的能量和时间消耗在进出睡眠模式中,这样导致的结果就是低功耗模式的作用被大大的削弱。为此,FreeRTOS 特地提供了一个解决方法——Tickless 模式,当处理器进入空闲任务周期以后就关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒。

这个时候会产生两个问题:

一、关闭系统节拍中断会导致系统节拍计数器停止,系统时钟就会停止。FreeRTOS 的系统时钟是依赖于系统节拍中断(滴答定时器中断)的,如果关闭了系统节拍中断的话就会导致系统时钟停止运行,这是绝对不允许的!

解决方法:
我们可以记录下系统节拍中断的关闭时间,当系统节拍中断再次开启运行的时候补上这段时间就行了。这时候我们就需要另外一个定时器来记录这段该补上的时间,如果使用专用的低功耗处理器的话基本上都会有一个低功耗定时器。STM32F103 没有这种定时器那么就接着使用滴答定时器来完成。

二、如何保证下一个要运行的任务能被准确的唤醒?
即使处理器进入了低功耗模式,但是我的中断和应用层任务也要保证及时的响应和处理。中断自然不用说,本身就可以将处理器从低功耗模式中唤醒。但是应用层任务就不行了,它无法将处理器从低功耗模式唤醒,无法唤醒就无法运行!

解决方法:
既然应用层任务无法将处理器从低功耗模式唤醒,那么我们就借助其他的力量来完成这个功能。如果处理器在进入低功耗模式之前能够获取到还有多长时间运行下一个任务那么问题就迎刃而解了,我们只需要开一个定时器,定时器的定时周期设置为这个时间值就行了,定时时间到了以后产生定时中断,处理器不就从低功耗模式唤醒了。这里似乎又引出了一个新的问题,那就是如何知道还有多长时间执行下一个任务?这个时间也就是低功耗模式的执行时间,值得庆辛的是 FreeRTOS已经帮我们完成了这个工作

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2、具体实现

要想使用 Tickless 模式,首先必须将 FreeRTOSConfig.h 中的宏 configUSE_TICKLESS_IDLE设置为 1

#define configUSE_TICKLESS_IDLE 1 //1 启用低功耗 tickless 模式

过程:
1、获得下一个任务运行时刻,也就是周期T
2、初始化一个定时器,获得周期T
3、降低系统主频,关闭某些外设等(降低功耗)
4、WFI指令让CPU进入低功耗模式
5、退出低功耗模式(中断发生,SYSTICK)
6、恢复系统主频、打开外设等
7、应用层任务

//确保滴答定时器的 Reload(重装载)值不会溢出,也就是不能超过滴答定时器最大计数值。
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )1{
xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
}
//停止滴答定时器。
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;

xExpectedIdleTime 表示处理器将要在低功耗模式运行的时长(单位为时钟节拍数),这个时间会使用滴答定时器来计时,但是滴答定时器的计数寄存器是 24 位的,因此这个时间值不能超过滴答定时器的最大计数值。最大时长93毫秒

//根据参数 xExpectedIdleTime 来计算滴答定时器的重装载值。
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +\ (2( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );

根据参数 xExpectedIdleTime 来计算滴答定时器的重装载值,因为处理器进入低功耗模式以后的计时是由滴答定时器来完成的。

if( ulReloadValue > ulStoppedTimerCompensation )3{
ulReloadValue -= ulStoppedTimerCompensation;
}

从滴答定时器停止运行到把统计得到的低功耗模式运行的这段时间补偿给 FreeRTOS系统时钟也是需要时间的,这期间也是有程序在运行的。这段程序运行的时间我们要留出来

__disable_irq();4__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );

在执行 WFI 前设置寄存器 PRIMASK 的话处理器可以由中断唤醒但是不会处理这些中断,退出低功耗模式以后通过清除寄存器 PRIMASK 来使 ISR 得到执行,其实就是利用PRIMASK 来延迟 ISR 的执行。函数__disable_irq()是用来设置寄存器 PRIMASK 的,清除寄存器 PRIMASK 使用函数__enable_irq()

//确认是否可以进入低功耗模式
if( eTaskConfirmSleepModeStatus() == eAbortSleep )5{
//不能进入低功耗模式,重新启动滴答定时器
portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
__enable_irq(); (6)
 }

调用函数 eTaskConfirmSleepModeStatus()来判断是否可以进入低功耗模式,此函数通过检查是否还有就绪任务来决定处理器能不能进入低功耗模式,如果返回 eAbortSleep 的话就表示不能进入低功耗模式,既然不能进入低功耗那就需要重新恢复滴答定时器的运行。
(6)调用函数__enable_irq()重新打开中断,因为在上面我们调用函数__disable_irq()关闭了中断。

//可以进入低功耗模式,设置滴答定时器
portNVIC_SYSTICK_LOAD_REG = ulReloadValue; (8)
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
xModifiableIdleTime = xExpectedIdleTime;

进入低功耗模式的时间已经在(2)中计算出来了,这里将这个值写入到滴答定时器的重装载寄存器中。

configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); (9)

configPRE_SLEEP_PROCESSING()是个宏,在进入低功耗模式之前可能有一些其他的事情要处理,比如降低系统时钟、关闭外设时钟、关闭板子某些硬件的电源等等,这些操作就可以在这个宏中完成

if( xModifiableIdleTime > 0 )
{
__dsb( portSY_FULL_READ_WRITE );
__wfi(); (10)
__isb( portSY_FULL_READ_WRITE );
}

使用 WFI 指令进入睡眠模式。

//当代码执行到这里的时候说明已经退出了低功耗模式!
configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); (11)

代码执行到这里处理器已经退出了低功耗模式,退出低功耗模式以后也可能需要处理一些事情。比如恢复系统时钟,使能外设时钟,打开板子某些硬件的电源等等。

//停止滴答定时器
ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG; (12)
portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL &\
 ~portNVIC_SYSTICK_ENABLE_BIT );

读取滴答定时器 CTRL(控制和状态)寄存器,后面要用。

__enable_irq(); (13)

调用函数__enable_irq()打开中断。

//判断导致退出低功耗的是由外部中断引起的还是滴答定时器计时时间到引起的
if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ) (14) {
uint32_t ulCalculatedLoadValue;
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue -\
 portNVIC_SYSTICK_CURRENT_VALUE_REG );
if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) ||\
 ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
{
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
}
portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
}
else //外部中断唤醒的,需要进行时间补偿
{
ulCompletedSysTickDecrements = ( xExpectedIdleTime * \
ulTimerCountsForOneTick ) -\
portNVIC_SYSTICK_CURRENT_VALUE_REG;
ulCompleteTickPeriods = ulCompletedSysTickDecrements / \
 ulTimerCountsForOneTick;
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * \
ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
}

判断退出低功耗模式是由滴答定时器中断引起的还是由其他中断引起的,因为这两种原因所对应的系统时钟赔偿值的计算方法不同,这个系统时钟补偿值的单位是时钟节拍。

//重新启动滴答定时器,滴答定时器的重装载值设置为正常值。
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portENTER_CRITICAL(); {
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( ulCompleteTickPeriods ); (15)
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
}

调用函数 vTaskStepTick()补偿系统时钟,函数参数是要补偿的值
这大概就是STM32低功耗的运行过程,当然这些FreeRTOS操作系统已经给我们做好了

总结

在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,
比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源。
低功耗Tickless模式实验放到下一节

版权声明:本文为CSDN博主「我与nano」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_51963216/article/details/122775605

生成海报
点赞 0

我与nano

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

RT-Thread Studio移植LAN8720A驱动

RTT网络协议栈驱动移植(霸天虎) 1、新建工程 ​ 工程路径不含中文路径名,工程名用纯英文不含任何符号。 2、用CubeMx配置板子外设 2.1、配置时钟 ​ 按照自己板子配置相应时钟。

【STM32Cube笔记】12-配置外部中断

【STM32Cube笔记】系列文章目录 1-基于STM32的VSCode入门级教程前言 2-STM32Cube安装教程 3-STM32CubeIDE汉化 4-STM32Cube配置时钟设置 5-跑马灯引脚配置 6-Cortex-M7内核基本配

stm32cubemx+HAL+串口接收中断

stm32cubemxHAL串口接收中断 在cubemx配置完串口和global interrupt后需要在keil中添加如下代码。 第一步:在main函数中添加接收中断标志位开启函数 HAL_UART_Receive_IT