在单片机系统运行过程中,大部分时间均处于空闲状态,为了减小功耗,可以让ThreadX管理在空闲时进入低功耗模式。在utility\low_power
文件夹有关于这个模块使用的详细说明。
移植说明
这个工具位于utility\low_power
文件夹下,使用时将这个文件夹下的源文件添加进工程,C/C++的包含路径也添加这个文件夹。ThreadX可用的配置有TX_LOW_POWER_TIMER_SETUP
和TX_LOW_POWER_TICKLESS
,这两个选项组合能产生三种配置(不使能TX_LOW_POWER_TIMER_SETUP
、使能TX_LOW_POWER_TICKLESS
无效),第一种是TX_LOW_POWER_TIMER_SETUP
不被宏定义(无论是否宏定义TX_LOW_POWER_TICKLESS
),这种情况下退出低功耗时与进入低功耗的所有时刻均相同。第二种是TX_LOW_POWER_TIMER_SETUP
被宏定义,TX_LOW_POWER_TICKLESS
不被宏定义,这种情况下时刻正常增加。第三种是TX_LOW_POWER_TIMER_SETUP
和TX_LOW_POWER_TICKLESS
都被宏定义,这种情况下在若有激活的定时器则与第二种情况相同,若没有激活的定时器则与第一种情况相同。
使能low_power
在tx_user.h
中添加宏定义TX_LOW_POWER
以使能low_power工具。
函数实现(使能TX_ENABLE_WFI)
使用该模块还需要实现TX_LOW_POWER_TIMER_SETUP
、TX_LOW_POWER_USER_ENTER
、TX_LOW_POWER_USER_EXIT
、TX_LOW_POWER_USER_TIMER_ADJUST
四个函数中的若干个。对于ARM,可以使用低功耗指令WFI(Wait For Interrupt)进入低功耗模式,WFI指令可以让CPU进入standby模式,即低功耗模式,此时内核会暂停其他活动,一直等待中断事件的发生,检测到中断发生后,WFI指令执行完成,CPU退出standby模式。ThreadX给出的样例port文件中已经帮我们写好了这一部分代码,因此也不需要实现TX_LOW_POWER_USER_ENTER
、TX_LOW_POWER_USER_EXIT
函数,在第一种情况下也不需要实现TX_LOW_POWER_TIMER_SETUP
、TX_LOW_POWER_USER_TIMER_ADJUST
。因此只需要在tx_user.h
中添加宏定义TX_ENABLE_WFI
来使能进入WFI的代码。
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_enter // Possibly enter low power mode
POP {r0-r3}
#endif
#ifdef TX_ENABLE_WFI
DSB // Ensure no outstanding memory transactions
WFI // Wait for interrupt
ISB // Ensure pipeline is flushed
#endif
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_exit // Exit low power mode
POP {r0-r3}
#endif
结果
我们采用第一种配置,当ThreadX空闲时则进入低功耗模式,通常SysTick中断会使内核从低功耗模式中退出(当然也可能是其他的中断),ThreadX内部时钟运行也是正常的。当然,这样做并不完美,因为SysTick引起的中断可能并不会引起有新的线程就绪,更高级的用法还需要配置TX_LOW_POWER_TIMER_SETUP
和TX_LOW_POWER_TICKLESS
。
一点小问题:使用BASEPRI来屏蔽中断时,较低优先级的中断无法使单片机退出低功耗模式
若ThreadX使用的是BASEPRI来屏蔽中断,且屏蔽中断的优先级比SysTick大(SysTick默认的优先级为6,见ARM单片机中断的各类问题),会导致SysTick中断不能使内核退出低功耗模式,关键代码如下:
__tx_ts_wait
#ifdef TX_PORT_USE_BASEPRI
LDR r1, =TX_PORT_BASEPRI // Mask interrupt priorities =< TX_PORT_BASEPRI
MSR BASEPRI, r1
#else
CPSID i // Disable interrupts
#endif
LDR r1, [r2] // Pickup the next thread to execute pointer
STR r1, [r0] // Store it in the current pointer
CBNZ r1, __tx_ts_ready // If non-NULL, a new thread is ready!
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_enter // Possibly enter low power mode
POP {r0-r3}
#endif
#ifdef TX_ENABLE_WFI
DSB // Ensure no outstanding memory transactions
WFI // Wait for interrupt
ISB // Ensure pipeline is flushed
#endif
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_exit // Exit low power mode
POP {r0-r3}
#endif
#ifdef TX_PORT_USE_BASEPRI
MOV r4, #0 // Disable BASEPRI masking (enable interrupts)
MSR BASEPRI, r4
#else
CPSIE i // Enable interrupts
#endif
B __tx_ts_wait // Loop to continue waiting
__tx_ts_wait是空闲时的循环,它会一直查看是否有新的线程就绪(一般由中断引起),在循环的开头会屏蔽所有中断,并在循环的结束才使能中断,而WFI等待的中断也会被BASEPRI屏蔽掉,导致了WFI无法正常退出,需要在使用WFI之前关闭BASEPRI,或者使用PRIMASK(CPSID i
)屏蔽中断(WFI不受PRIMASK的影响),或者修改SysTick的优先级使之不会被BASEPRI屏蔽掉。
(注:在tx_low_power_enter
(如下)函数中虽然看起来使能了中断,但实际并没有,TX_DISABLE
会将当前的BASEPRI值储存起来,而使用TX_RESTORE
恢复到原来的状态,在调用这个函数之前BASEPRI已经禁止中断,因此tx_low_power_enter
的TX_RESTORE
函数也会恢复到禁用中断的状态,导致并没有实际打开中断)
VOID tx_low_power_enter(VOID)
{
...
/* Disable interrupts while we prepare for low power mode. */
TX_DISABLE
...
/* Re-enable interrupts before low power mode is entered. */
TX_RESTORE
/* User code to enter low power mode. This allows the application to power down
peripherals and put the processor in sleep mode.
*/
#ifdef TX_LOW_POWER_USER_ENTER
TX_LOW_POWER_USER_ENTER;
#endif
/* If the low power code returns, this routine returns to the tx_thread_schedule loop. */
}
这个问题已经向ThreadX仓库提交azure-rtos/threadx Issues #279,官方也给出来一种变通方法,在进入低功耗前后使用BASEPRI来屏蔽中断解决,但是感觉这样做不够好,WFI本就是等待中断,应该在进入低功耗前打开中断。按照一般处理方法和原代码的注释来看,在调用
之前应该使能中断,而不是在退出低功耗时使能中断,即修改tx_low_power_enter
__tx_ts_wait
循环如下:
__tx_ts_wait:
#ifdef TX_PORT_USE_BASEPRI
LDR r1, =TX_PORT_BASEPRI // Mask interrupt priorities =< TX_PORT_BASEPRI
MSR BASEPRI, r1
#else
CPSID i // Disable interrupts
#endif
LDR r1, [r2] // Pickup the next thread to execute pointer
STR r1, [r0] // Store it in the current pointer
CBNZ r1, __tx_ts_ready // If non-NULL, a new thread is ready!
#ifdef TX_PORT_USE_BASEPRI
MOV r4, #0 // Disable BASEPRI masking (enable interrupts)
MSR BASEPRI, r4
#else
CPSIE i // Enable interrupts
#endif
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_enter // Possibly enter low power mode
POP {r0-r3}
#endif
#ifdef TX_ENABLE_WFI
DSB // Ensure no outstanding memory transactions
WFI // Wait for interrupt
ISB // Ensure pipeline is flushed
#endif
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_exit // Exit low power mode
POP {r0-r3}
#endif
B __tx_ts_wait // Loop to continue waiting