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;
BOOL g_bTickIrqFlag = 0;
BOOL g_bReloadSysTickFlag = 0;
volatile UINT32 g_uwSleepTicks = 0;
主要函数:
static inline UINT32 osSleepTicksGet(VOID)
inline VOID osUpdateKernelTickCount(UINT32 uwHwiIndex)
VOID osTicklessStart(VOID)
用户通过LOS_TicklessEnable()和LOS_TicklessDisable()函数来设置g_bTicklessFlag的值,以此控制Tickless模式下是否要改变时基。在执行空闲任务的时候会调用osTicklessHandler()函数来进入睡眠模式,接管中断模式下如下:
VOID osTicklessHandler(VOID)
{
if (g_bTickIrqFlag)
{
g_bTickIrqFlag = 0;
osTicklessStart();
}
osEnterSleep();
}
osTicklessStart()函数做进入睡眠模式之前的准备,主要是计算要睡眠的tick数,并以此来设置systick的Reload值。源码解析(个人理解):
VOID osTicklessStart(VOID)
{
UINT32 uwCyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;
UINT32 uwMaxTicks = LOSCFG_SYSTICK_LOAD_RELOAD_MAX / uwCyclesPerTick;
UINTPTR uvIntSave = 0;
UINT32 uwSleepTicks = 0;
uvIntSave = LOS_IntLock();
LOS_SysTickStop();
if (LOS_SysTickGetIntStatus())
{
goto out;
}
uwSleepTicks = osSleepTicksGet();
if (uwSleepTicks > 1)
{
UINT32 uwSleepCycles, uwCurrSysCycles;
if (uwSleepTicks >= uwMaxTicks)
{
uwSleepTicks = uwMaxTicks;
}
uwSleepCycles = uwSleepTicks * uwCyclesPerTick;
uwCurrSysCycles = LOS_SysTickCurrCycleGet();
#if (LOSCFG_SYSTICK_CNT_DIR_DECREASE == YES)
LOS_SysTickReload(uwSleepCycles - uwCyclesPerTick + uwCurrSysCycles);
#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)
{
if (g_uwSleepTicks > 1)
{
UINT32 uwCyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;
UINT32 uwCurrSysCycles, uwElapseSysCycles, uwElapseTicks, uwRemainSysCycles;
g_bReloadSysTickFlag = 0;
#if (LOSCFG_PLATFORM_HWI == YES)
if (uwHwiIndex == (SysTick_IRQn + OS_SYS_VECTOR_CNT))
#else
if (g_uwSysTickIntFlag == TICKLESS_OS_TICK_INT_SET)
#endif
{
uwElapseTicks = (g_uwSleepTicks - 1);
LOS_SysTickReload(OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND);
}
else
{
uwCurrSysCycles = LOS_SysTickCurrCycleGet();
#if (LOSCFG_SYSTICK_CNT_DIR_DECREASE == YES)
uwElapseSysCycles = ((g_uwSleepTicks * uwCyclesPerTick) - uwCurrSysCycles);
#else
uwElapseSysCycles = uwCurrSysCycles;
#endif
uwElapseTicks = uwElapseSysCycles / uwCyclesPerTick;
uwRemainSysCycles = uwElapseSysCycles % uwCyclesPerTick;
if (uwRemainSysCycles > 0)
{
LOS_SysTickReload(uwRemainSysCycles);
g_bReloadSysTickFlag = 1;
}
else
{
LOS_SysTickReload(uwCyclesPerTick);
}
}
osTickHandlerLoop(uwElapseTicks);
g_uwSleepTicks = 0;
#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左右。
|