FreeRTOS临界段和开关中断
本章教程为大家讲解两个重要的概念,FreeRTOS的临界段和开关中断。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407以及F429。
15.1 临界段
15.2 任务代码临界段处理
15.3 中断服务程序临界段处理
15.4 开关中断的实现
15.5 BSP板级支持包中开关中断的特别处理
15.6. 实验例程说明
15.7
15.1 临界段
代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
l
FreeRTOS的源码中有多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时性。比如此时某个任务正在调用系统API函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行,如果关中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。
FreeRTOS源码中就有多处临界段的处理,跟FreeRTOS一样,uCOS-II和uCOS-III源码中都是有临界段的,而RTX的源码中不存在临界段。另外,除了FreeRTOS操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:
u
u
总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。
15.2 任务代码临界段处理
FreeRTOS任务代码中临界段的进入和退出主要是通过操作寄存器basepri实现的。进入临界段前操作寄存器basepri关闭了所有小于等于宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY所定义的中断优先级,这样临界段代码就不会被中断干扰到,而且实现任务切换功能的PendSV中断和滴答定时器中断是最低优先级中断,所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。退出临界段时重新操作basepri寄存器,即打开被关闭的中断(这里我们不考虑不受FreeRTOS管理的更高优先级中断)。FreeRTOS进入和退出临界段的函数如下:
#define taskENTER_CRITICAL()
#define taskEXIT_CRITICAL()
上面这两个函数是供用户调用的,其中函数taskENTER_CRITICAL是进入临界段,函数taskEXIT_CRITICAL是退出临界段。进一步跟踪宏定义的实现如下:
#define portENTER_CRITICAL()
#define portEXIT_CRITICAL()
再进一步跟踪宏定义的实现如下:
void vPortEnterCritical( void )
{
}
void vPortExitCritical( void )
{
}
通过上面的两个函数vPortEnterCritical和vPortExitCritical可以看出,进入临界段和退出临界段是通过函数调用开关中断函数portENABLE_INTERRUPTS和portDISABLE_INTERRUPTS实现的。细心的读者还会发现上面的这两个函数都对变量uxCriticalNesting进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。通过嵌套计数就有效地防止了用户嵌套调用函数taskENTER_CRITICAL和taskEXIT_CRITICAL时出错。
l
比如下面的例子:
void FunctionA()
{
taskDISABLE_INTERRUPTS();
FunctionB(); 调用函数B
FunctionC(); 调用函数C
taskENABLE_INTERRUPTS();
}
void FunctionB()
{
taskDISABLE_INTERRUPTS();
代码
taskENABLE_INTERRUPTS();
}
工程中调用了FunctionA就会出现执行完FunctionB后中断被打开的情况,此时FunctionC将
不被保护了。
接下来继续说明开关中断的实现,我们要打破砂锅问到底:
#define portDISABLE_INTERRUPTS()
#define portENABLE_INTERRUPTS()
函数vPortRaiseBASEPRI和vPortSetBASEPRI的源码实现如下:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
}
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
}
经过这么多次的宏定义后,终于来到了最终的原始函数。FreeRTOS的这种层层调用宏定义的方法在带来便利操作的同时,却让用户在分析源码的时候非常不方便。
通过上面的源码实现可以看出,FreeRTOS的开关全局中断是通过操作寄存器basepri实现的,关于这个寄存器,我们已经在第12章进行了详细的讲解,这里不再赘述。
使用举例:
使用的时候一定要保证成对使用
static void vTaskLED(void *pvParameters)
{
}
嵌套使用举例:
void FunctionB()
{
}
void FunctionA()
{
FunctionB();
FunctionC();
}
15.3 中断服务程序临界段处理
与任务代码里临界段的处理方式类似,中断服务程序里面临界段的处理也有一对开关中断函数。
#define taskENTER_CRITICAL_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x )
进一步跟踪宏定义的实现如下:
#define portSET_INTERRUPT_MASK_FROM_ISR()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
再进一步跟踪宏定义的实现如下:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
}
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
}
通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器basepri实现的。
初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器basepri的数值就可以实现嵌套使用。如果大家研究过uCOS-II或者III的源码,跟这里的实现方式是一样的,具体看下面的使用举例。
使用举例:
使用的时候一定要保证成对使用
void TIM6_DAC_IRQHandler( void )
{
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
嵌套使用举例:
void FunctionB()
{
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
void TIM6_DAC_IRQHandler( void )
{
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
FunctionB();
FunctionC();
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
15.4开关中断的实现
FreeRTOS也专门提供了一组开关中断函数,实现比较简单,其实就是前面15.2小节里面临界段进入和退出函数的精简版本,主要区别是不支持中断嵌套。具体实现如下:
#define taskDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS()
进一步跟踪宏定义的实现如下:
#define portDISABLE_INTERRUPTS()
#define portENABLE_INTERRUPTS()
函数vPortRaiseBASEPRI和vPortSetBASEPRI的源码实现如下:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
}
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
}
从上面的源码可以看出,FreeRTOS的全局中断开关是通过操作寄存器basepri实现的,关于这个寄存器,我们已经在第12章进行了详细的讲解,这里不再赘述。
使用举例:
使用的时候一定要保证成对使用
static void vTaskLED(void *pvParameters)
{
}
15.5BSP板级支持包中开关中断的特别处理
前面为大家讲解了FreeRTOS临界段的处理方法和开关中断方法,加上了FreeRTOS操作系统后,我们实际编写的外设驱动又该怎么修改呢?因为外设驱动编写时,有些地方有用到开关中断操作,这里以此教程配套的STM32F103,F407和F429开发板为例进行说明,这三种开发板的外设驱动的编写架构都是统一的,用户只需将bsp.h文件里面的宏定义:
#define ENABLE_INT()
#define DISABLE_INT()
修改为如下的形式:
#define
#if USE_FreeRTOS == 1
#else
#endif
u
将中断开关设置改成了条件编译的形式,这样在使用裸机或者使用FreeRTOS时,切换自如。此宏定义配置为1表示使用FreeRTOS的开关中断API函数,配置为0表示使用裸机的方式开关中断。
u
因为BSP驱动包的源码基本没有在中断里面进行开关中断,都是在中断以外,所以开关中断是采用的任务代码里面临界段的处理函数,而且支持嵌套调用。
大家写的工程代码也可以采用类似的方案,方便裸机和FreeRTOS的切换,或者采用其它适合自己的方案。另外要注意,因为FreeRTOS存在不受其控制的更高优先级中断,用户需要根据实际情况进行特别处理,可以不采用FreeRTOS的开关中断函数,而是直接使用__set_PRIMASK实现全局中断的开关。
15.6实验例程说明
15.6.1STM32F103开发板实验
配套例子:
V4-309_FreeRTOS实验_临界段和开关中断
实验目的:
1.
实验内容:
1.
2.
3.
4.
5.
6.
FreeRTOS的配置:
FreeRTOSConfig.h文件中的配置如下:
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#endif
#define configUSE_PREEMPTION
#define configUSE_IDLE_HOOK
#define configUSE_TICK_HOOK
#define configCPU_CLOCK_HZ
#define configTICK_RATE_HZ
#define configMAX_PRIORITIES
#define configMINIMAL_STACK_SIZE
#define configTOTAL_HEAP_SIZE
#define configMAX_TASK_NAME_LEN
#define configUSE_TRACE_FACILITY
#define configUSE_16_BIT_TICKS
#define configIDLE_SHOULD_YIELD
#define configGENERATE_RUN_TIME_STATS
#define configUSE_STATS_FORMATTING_FUNCTIONS
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE()
//#define portALT_GET_RUN_TIME_COUNTER_VALUE
#define configUSE_CO_ROUTINES
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
#define INCLUDE_vTaskPrioritySet
#define INCLUDE_uxTaskPriorityGet
#define INCLUDE_vTaskDelete
#define INCLUDE_vTaskCleanUpResources
#define INCLUDE_vTaskSuspend
#define INCLUDE_vTaskDelayUntil
#define INCLUDE_vTaskDelay
#ifdef __NVIC_PRIO_BITS
#else
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS任务调试信息(按K1按键,串口打印):
上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
#define tskBLOCKED_CHAR
#define tskREADY_CHAR
#define tskDELETED_CHAR
#define tskSUSPENDED_CHAR
程序设计:
u
vTaskUserIF任务
vTaskLED任务
vTaskMsgPro任务 :2048字节
vTaskStart任务
任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
#define configTOTAL_HEAP_SIZE
u
u
int main(void)
{
}
u
硬件外设的初始化是在bsp.c文件实现:
void bsp_Init(void)
{
}
u
static void AppTaskCreate (void)
{
}
u
static void vTaskTaskUserIF(void *pvParameters)
{
}
static void vTaskLED(void *pvParameters)
{
{
}
static void vTaskMsgPro(void *pvParameters)
{
{
}
static void vTaskStart(void *pvParameters)
{
}
u
定时器中断的初始化和中断函数在bsp_timer.c 文件中实现,这个不是教程的重点,故不作介绍。 这里主要关心中断服务程序中临界段的实现方法。
static void TIM_CallBack1(void)
{
}
15.6.2STM32F407开发板实验
配套例子:
V5-309_FreeRTOS实验_临界段和开关中断
实验目的:
1. 学习FreeRTOS的临界段和开关中断设置
实验内容:
1.
2.
3.
4.
5.
6.
FreeRTOS的配置:
FreeRTOSConfig.h文件中的配置如下:
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#endif
#define configUSE_PREEMPTION
#define configUSE_IDLE_HOOK
#define configUSE_TICK_HOOK
#define configCPU_CLOCK_HZ
#define configTICK_RATE_HZ
#define configMAX_PRIORITIES
#define configMINIMAL_STACK_SIZE
#define configTOTAL_HEAP_SIZE
#define configMAX_TASK_NAME_LEN
#define configUSE_TRACE_FACILITY
#define configUSE_16_BIT_TICKS
#define configIDLE_SHOULD_YIELD
#define configGENERATE_RUN_TIME_STATS
#define configUSE_STATS_FORMATTING_FUNCTIONS
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE()
//#define portALT_GET_RUN_TIME_COUNTER_VALUE
#define configUSE_CO_ROUTINES
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
#define INCLUDE_vTaskPrioritySet
#define INCLUDE_uxTaskPriorityGet
#define INCLUDE_vTaskDelete
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend
#define INCLUDE_vTaskDelayUntil
#define INCLUDE_vTaskDelay
#ifdef __NVIC_PRIO_BITS
#else
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS任务调试信息(按K1按键,串口打印):
上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
#define tskBLOCKED_CHAR
#define tskREADY_CHAR
#define tskDELETED_CHAR
#define tskSUSPENDED_CHAR
程序设计:
u
vTaskUserIF任务
vTaskLED任务
vTaskMsgPro任务 :2048字节
vTaskStart任务
任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
#define configTOTAL_HEAP_SIZE
u
u
int main(void)
{
}
u
硬件外设的初始化是在bsp.c文件实现:
void bsp_Init(void)
{
}
u
static void AppTaskCreate (void)
{
}