IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 基于STM32L431的Liteos低功耗Tickless模式的使用 -> 正文阅读

[嵌入式]基于STM32L431的Liteos低功耗Tickless模式的使用

Liteos源码
以下涉及到的源码为Liteos的develop支线版本。

Liteos的tick是通过systick中断来实现的,软件定时器和所有的延时也是基于tick进行的。

#define OS_SYS_CLOCK                                        (SystemCoreClock)
#define LOSCFG_BASE_CORE_TICK_PER_SECOND                    (200UL)

OS_SYS_CLOCK为系统时钟频率,设置为40MHz,tick周期设置为200,也就是说每秒systick产生200次中断,每个tick5ms。那么软件定时器和延时函数所能处理的最小时间刻度就是5ms。另外,使用Liteos提供的延时函数LOS_TaskDelay()的参数如果设置为0,那么会立即进行任务切换。

Tickless模式是通过在执行空闲任务的时候使MCU进入睡眠模式实现的。STM32L431的睡眠模式,在数据手册中表述如下:
在这里插入图片描述
可以看到有任何的中断或者事件到来都会退出睡眠模式,但是系统的tick是5ms,也就是说每5ms一次的systick中断就必定会唤醒MCU,显然是不符合低功耗的要求的。因此Tickless模式通过在空闲时改变systick的Reload值来改变systick的中断时间,以此来拉长MCU进入睡眠模式的时间。要使用Tickless模式,需要在target_config.h中把LOSCFG_KERNEL_TICKLESS宏设置为YES。

但是SysTick 是一个 24 位的倒计数定时器,当计到 0 时,将从Reload寄存器中自动重装载定时初值。因此延长睡眠时间也不是没有限制的,Reload值最大为0xFFFFFFUL。在睡眠前要先计算需要睡眠的tick数,这个tick数是通过osSleepTicksGet()函数来实现的,是通过取最近一个任务切换即将到达或者最近一个软件定时器定时即将到达的时间来确定,二者取小值(如果没有用软件定时器,那么其值为0xFFFFFFFF,所以就是取最近一个任务切换即将到达的时间)。然后通过睡眠的tick数来设置Reload的值,如果要睡眠的tick数超过最大限制,那么本次要睡眠的tick数就是最大值,也就是在一定时间内必须产生一次systick中断。

使用Tickless模式时关键的几个全局变量定义:

BOOL g_bTicklessFlag = 1;			//是否要进入tickless模式,用户
BOOL g_bTickIrqFlag = 0;			//是否要进入tickless模式,系统
BOOL g_bReloadSysTickFlag = 0;		//是否要重新设置systick的reload值
volatile UINT32 g_uwSleepTicks = 0;	//要睡眠的tick数

主要函数:

static inline UINT32 osSleepTicksGet(VOID)				//获取要睡眠的tick数
inline VOID osUpdateKernelTickCount(UINT32 uwHwiIndex)	//更新内核的TICK数,入参:中断号
VOID osTicklessStart(VOID)

用户通过LOS_TicklessEnable()和LOS_TicklessDisable()函数来设置g_bTicklessFlag的值,以此控制Tickless模式下是否要改变时基。在执行空闲任务的时候会调用osTicklessHandler()函数来进入睡眠模式,接管中断模式下如下:

VOID osTicklessHandler(VOID)
{
    if (g_bTickIrqFlag)//tickless模式下如果要改变时基,在systick中断中被赋值
    {
        g_bTickIrqFlag = 0;
        osTicklessStart();//开始进入sleep模式,通过可以睡眠的tick个数来设置systick的reload值实现
    }

    osEnterSleep();//进入休眠
}

osTicklessStart()函数做进入睡眠模式之前的准备,主要是计算要睡眠的tick数,并以此来设置systick的Reload值。源码解析(个人理解):

VOID osTicklessStart(VOID)
{
    UINT32 uwCyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;//40M/200 = 200000
    UINT32 uwMaxTicks = LOSCFG_SYSTICK_LOAD_RELOAD_MAX / uwCyclesPerTick;//获取最大休眠时间(40M时钟下,大概83.8个tick,每个tick5ms,Tickless最长时间415ms)
    UINTPTR uvIntSave = 0;
    UINT32 uwSleepTicks = 0;

    uvIntSave = LOS_IntLock();
    LOS_SysTickStop();//失能SysTick定时器
    if (LOS_SysTickGetIntStatus()) /* SysTick interrupt pend 发生了SysTick中断*/
    {
        goto out;
    }

    uwSleepTicks = osSleepTicksGet();//获取要睡眠多少tick,为最近一个任务切换即将到达或者最近一个软件定时器定时即将到达的时间,二者取小值
    if (uwSleepTicks > 1)
    {
        UINT32 uwSleepCycles, uwCurrSysCycles;
        if (uwSleepTicks >= uwMaxTicks)//如果要睡眠的tick个数大于最大个数,那么本次要睡眠的时间就是最大个数,因为SysTick是一个24位定时器
        {
            uwSleepTicks = uwMaxTicks;
        }

        uwSleepCycles = uwSleepTicks * uwCyclesPerTick;//获取睡眠周期200000*83
        uwCurrSysCycles = LOS_SysTickCurrCycleGet();//获取下一次reload之前的剩余计数值
    #if (LOSCFG_SYSTICK_CNT_DIR_DECREASE == YES)
        LOS_SysTickReload(uwSleepCycles - uwCyclesPerTick + uwCurrSysCycles);//重新设置reload
    #else
        LOS_SysTickReload(uwSleepCycles - uwCurrSysCycles);
    #endif
        g_uwSleepTicks = uwSleepTicks;
    #if (LOSCFG_PLATFORM_HWI == NO)
        if (g_uwSysTickIntFlag == TICKLESS_OS_TICK_INT_INIT)
        {
            g_uwSysTickIntFlag = TICKLESS_OS_TICK_INT_WAIT;
        }
    #endif
    }
out:
    LOS_SysTickStart();
    LOS_IntRestore(uvIntSave);

    return;
}

在osTicklessStart()函数中有对是否发生SysTick中断的判断,之所以进行这个判断是因为在进入sleep模式的时候会改变OS系统的时基,因此一旦发生中断后会唤醒MCU,就需要对睡眠过程中没有计入的tick数进行补偿,也需要改变systick的Reload值。

在系统发生任何中断后会唤醒MCU,每次唤醒之后都需要执行osUpdateKernelTickCount()函数来对系统的tick数进行补偿,否则会导致整个系统的计时不准。源码解析(个人理解):

inline VOID osUpdateKernelTickCount(UINT32 uwHwiIndex)//更新内核的TICK数,入参:中断号
{
    /** this function must be called in interrupt context */
    if (g_uwSleepTicks > 1)//如果要睡眠的tick数大于1
    {
        UINT32 uwCyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;//40M/200 = 200000
        UINT32 uwCurrSysCycles, uwElapseSysCycles, uwElapseTicks, uwRemainSysCycles;

        g_bReloadSysTickFlag = 0;//不需要重新设置systick的reload值
    #if (LOSCFG_PLATFORM_HWI == YES)
        if (uwHwiIndex == (SysTick_IRQn + OS_SYS_VECTOR_CNT))//如果本次是systick中断
    #else
        if (g_uwSysTickIntFlag == TICKLESS_OS_TICK_INT_SET) /* OS tick interrupt */
    #endif
        {
            uwElapseTicks = (g_uwSleepTicks - 1);//已经过去的tick数等于要睡眠的tick数减1
            LOS_SysTickReload(OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND);//重新设置systick的reload值
        }
        else//其他中断
        {
            uwCurrSysCycles = LOS_SysTickCurrCycleGet();//获取下一次reload之前的剩余计数值
        #if (LOSCFG_SYSTICK_CNT_DIR_DECREASE == YES)
            uwElapseSysCycles = ((g_uwSleepTicks * uwCyclesPerTick) - uwCurrSysCycles);//下一次reload之前已经过去的周期(计数值)
        #else
            uwElapseSysCycles = uwCurrSysCycles;
        #endif
            uwElapseTicks = uwElapseSysCycles / uwCyclesPerTick;//下一次reload之前已经过去的tick数
            uwRemainSysCycles = uwElapseSysCycles % uwCyclesPerTick;//不足一个周期的计数值,对200000取余
            if (uwRemainSysCycles > 0)
            {
                LOS_SysTickReload(uwRemainSysCycles);//重新设置systick的reload值,tick补偿
                g_bReloadSysTickFlag = 1;//需要重新设置systick的reload值
            }
            else
            {
                LOS_SysTickReload(uwCyclesPerTick);//设置为正常的reload值
            }
        }
        osTickHandlerLoop(uwElapseTicks);//系统tick补偿
        g_uwSleepTicks = 0;//要睡眠的tick数清零
     #if (LOSCFG_PLATFORM_HWI == NO)
        g_uwSysTickIntFlag = TICKLESS_OS_TICK_INT_INIT;
     #endif
    }
}

Tickless模式下实际进入睡眠模式调用的是osEnterSleep()函数:

LITE_OS_SEC_TEXT_INIT VOID osEnterSleep(VOID)
{
    __DSB();//数据同步隔离,当所有在它前面的存储器访问都执行完毕后,才执行它在后面的指令
    __WFI();//休眠并且在发生中断时被唤醒
    __ISB();//指令同步隔离,保证所有它前面的指令都执行完毕之后,才执行它后面的指令
}

STM32L431的HAL库中进入sleep模式可以调用HAL_PWR_EnterSLEEPMode()函数,参数要用PWR_MAINREGULATOR_ON和PWR_SLEEPENTRY_WFI。因为使用PWR_LOWPOWERREGULATOR_ON需要改变系统的时钟为2MHz以下,PWR_SLEEPENTRY_WFE是等待事件唤醒,不符合要求。

LITE_OS_SEC_TEXT_INIT VOID osEnterSleep(VOID)
{
	HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
}

使用上面两种方式进入sleep模式后功耗表现是一样的,不过最好还是用Liteos提供的函数。

实际测量下来,在不使用Tickless模式下,功耗在6.5mA左右;使用Tickless模式但是不调用osEnterSleep()函数时功耗在5.2mA左右;使用Tickless模式并且调用osEnterSleep()函数时功耗在2.2mA左右。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:50:48  更:2022-10-17 12:53:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:33:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码