前言
临界段代码也叫临界区,是指那些必须完整运行,不能被打断的代码,比如有的外设的初始化需要按照严格的时序,初始化过程不能被打断。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。
在FreeRTOS系统本身就有很多的临界段保护代码,我们自己写的时候只需要在一些需要严格保持时序的模块中加入临界段代码保护即可。
一、临界段代码保护函数
- 任务中使用:
taskENTER_CRITICA()/taskEXIT_CRITICAL()
- ISR中使用:
taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()
二、如何使用
1、任务中使用
taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的
eg:
void taskcritical_test(void)
{
while(1)
{
taskENTER_CRITICAL(); (1)//进入临界区
TTS_Play("欢迎光临");//语音播报
taskEXIT_CRITICAL(); (2)//退出临界区
vTaskDelay(1000);
}}
以上就是典型的例子,在我们常见的语音播报中,如何不在任务进行临街保护,如何发生任务调度和中断发生,会导致我们的语音播报发生停顿,所以我们在这上加上临街保护,可解决此问题
2、中断中使用
//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
status_value=taskENTER_CRITICAL_FROM_ISR(); (1)//进入临界区,进入有一个返回值要保存起来
total_num+=1;
printf("float_num 的值为: %d\r\n",total_num);
taskEXIT_CRITICAL_FROM_ISR(status_value); (2)//退出临界区,用上面的返回值作为参数写进来才可以完成
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
}
上面的语句就是在中断服务函数中使用的临街保护语句。
我们现在知道如何使用,但是原理我们需要知道,究其根本才有应对问题的发生解决,下面使用点需要你我清楚知道,那我们接下里探讨下
三、临界保护原理
1、下面是taskENTER_CRITICAL源码
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
从代码中可以看出,进入临界区其实就是关闭中断,然后将中断嵌套变量加一,并检查这个函数是否在中断中调用。
uxCriticalNesting是记录中断嵌套层数,防止中断嵌套导致退出中断后无法正常打开中断。
关闭中断其实是通过设置BASEPRI寄存器实现的,然后这个值是根据configMAX_SYSCALL_INTERRUPT_PRIORITY来的
好了,那接下来我们就看下BASEPRI寄存器寄存器和configMAX_SYSCALL_INTERRUPT_PRIORITY的值
2、BASEPRI寄存器
FreeRTOS之所以能进行临街保护,是的被保护的代码能够完整运行结束,其根本是它控制着中断,FreeRtos通过直接操作CortexM4或者CortexM3的BASEPRI寄存器来屏蔽中断。
BASEPRI的作用是屏蔽优先级不高于某个具体数值的中断。当它被设置为某个值时,所有优先级值大于等于这个数值的中断都会被关闭(中断的优先级值越大,优先级越低)。若被设置为0,则不关闭任何中断。
CortexM4或者CortexM3的BASEPRI寄存器各位的含义如下:
由图可看出:
(1)BASEPRI寄存器实际只有高四位有效 (7-4)
(2)高四位值若被设置为0,则无法控制,即不关闭任何中断
我们再看Freertosconfig.h文件配置的configMAX_SYSCALL_INTERRUPT_PRIORITY 的值
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
总结:
通过计算可以得到configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的值为0x50,BASEPRI寄存器实际只有高四位有效,也就是说进入临界区函数taskENTER_CRITICAL其实只是屏蔽了优先级数值大于5的所有其他优先级,并非是关闭所有的中断。
即按照现在这个配置 0 1 2 3 4这四个高优先级中断,你是屏蔽不了的,只能屏蔽5-15的中断。
而控制着Freertos运行任务调度的滴答定时优先级是最低的。所以到现在应该都明白了。
到这里,我们就明白为什么可以对代码进行临界保护的原理,所以我们在使用中一定要注意你配置的中断优先级,以及你函数的分配与高优先级中断的合理分配。
上面讲解任务中的保护,那么中断的保护taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,当前的这个中断的优先级值一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY!