FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护

一、中断优先级

1.1、 NVIC 基础知识 (回顾)

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第1张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第2张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第3张图片

1.2、FreeRTOS 配置 NVIC

  Note:官方强烈建议将中断优先级分组设置为 4,即抢占优先级可配置范围是 0-15,数值越小,抢占优先级越高。

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第4张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第5张图片

1.3、SVC,PendSV 与 Systick 中断

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第6张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第7张图片

SVC 中断:

  在 FreeRTOS 的移植文件 ports.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS 中仅执行一次,用于启动第一个要执行的任务。另外,由于 FreeRTOS 没有配置 SVC 的中断优先级,默认没有配置的情况下,SVC 中断的优先级就是最高的 0。

PendSV 与 Systick 中断:

1.4、不受 FreeTOS 管理的中断

  FreeRTOS 内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。比如在源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。如果这是一个紧急的中断事件,得不到及时执行的话,后果是可想而知的。

  针对这种情况,FreeRTOS 就专门做了一种新的开关中断实现机制。关闭中断时仅关闭受 FreeRTOS 管理的中断,不受 FreeRTOS 管理的中断不关闭,这些不受管理的中断都是高优先级的中断,用户可以在这些中断里面加入需要实时响应的程序。

  FreeRTOS 能够实现这种功能的奥秘就在于 FreeRTOS 开关中断使用的是寄存器 basepri:

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第8张图片

1.5、STM32CubeMX 配置

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第9张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第10张图片

二、任务优先级

2.1、任务优先级说明

  ① FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1。比如我们配置此宏定义为 5,那么用户可以使用的优先级号是 0,1,2,3,4,不包含 5,对于这一点,初学者要特别的注意。

  ② 用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0。

  ③ 建议用户配置宏定义 configMAX_PRIORITIES 的最大值不要超过 32,即用户任务可以使用的优先级范围是 0 到 31。因为对于 CM 内核的移植文件,用户任务的优先级不是大于等于 32 的话,portmacro.h 文件中的宏定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 会优化优先级列表中要执行的最高优先级任务的获取算法(对于 CM 内核的移植文件,此宏定义默认是使能的,当然,用户也可以在 FreeRTOSConfig.h 文件中进行配置)。

  ④ 如果用户在 FreeRTOSConfig.h 文件中配置宏定义 configUSE_TIME_SLICING 为 1,或者没有配置此宏定义,时间片调度都是使能的。另外,只要芯片资源允许,可以配置任意多个同优先级任务。

  ⑤ FreeRTOS 中处于运行状态的任务永远是当前能够运行的最高优先级任务。

2.2、任务优先级分配方案

  对于初学者,有时候会纠结任务优先级设置为多少合适,因为任务优先级设置多少是没有标准的。对于这个问题,这里为大家推荐一个标准,任务优先级设置推荐方式如下图:

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第11张图片

  ① IRQ 任务:IRQ 任务是指通过中断服务程序进行触发的任务,此类任务应该设置为所有任务里面优先级最高的。

  ② 高优先级后台任务:比如按键检测,触摸检测,USB 消息处理,串口消息处理等,都可以归为这一类任务。

  ③ 低优先级的时间片调度任务:比如 emWin 的界面显示,LED 数码管的显示等不需要实时执行的都可以归为这一类任务。实际应用中用户不必拘泥于将这些任务都设置为优先级 1 的同优先级任务,可以设置多个优先级,只需注意这类任务不需要高实时性。

  ④ 空闲任务:空闲任务是系统任务。

  ⑤ 特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可),只有这样,高优先级任务才会释放 CPU 的使用权,, 从而低优先级任务才有机会得到执行。

2.3、中断优先级和任务优先级区别

  部分初学者也容易在这两个概念上面出现问题。简单的说,这两个之间没有任何关系,不管中断的优先级是多少,中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序。

  另外对于 STM32,中断优先级的数值越小,优先级越高。而 FreeRTOS 的任务优先级是,任务优先级数值越小,任务优先级越低。

2.4、任务优先级修改与获取

优先级获取:

函数原型:

UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask); /* 任务句柄 */

函数描述:

  函数 vTaskPriorityGet 用于获取 FreeRTOS 任务优先级。

  • 第 1 个参数是任务句柄,用于区分不同的任务。

使用这个函数要注意以下问题:

  1. 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskPriorityGet    1
  1. 如果第二个参数里面填的是 NULL,即数值 0 的话,那么获取的优先级就是当前正在执行的任务

优先级修改:

函数原型:

void vTaskPrioritySet( TaskHandle_t xTask, /* 任务句柄 */

         UBaseType_t uxNewPriority ); /* 给任务配置的新优先级 */

函数描述:

函数 vTaskPrioritySet 用于实现 FreeRTOS 任务优先级的修改。

  • 第 1 个参数是任务句柄,用于区分不同的任务。

  • 第 2 个参数是给任务配置的新优先级。

使用这个函数要注意以下问题:

  ① 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1

#define INCLUDE_vTaskPrioritySet   1

  ② 如果第一个参数里面填的是 NULL,即数值 0 的话,那么配置的就是当前正在执行的任务

  ③ 如果被修改的任务的优先级,修改后高于正在执行的任务,将执行任务切换,切换到修改好的高优先级任务。

  ④ 第二个参数数值不可大于等于 FreeRTOSConfig.h 文件中的宏定义:#define configMAX_PRIORITIES 配置的数值。

三、开关中断与临界段函数

3.1、临界段概念

  代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。

  进入临界段前操作寄存器 basepri 关闭了所有小于等于宏定义   configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 所定义的中断优先级,这样临界段代码就不会被中断干扰到,而且实现任务切换功能的 PendSV 中断和滴答定时器中断是最低优先级中断,所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。退出临界段时重新操作 basepri 寄存器,即打开被关闭的中断(这里我们不考虑不受 FreeRTOS 管理的更高优先级中断)

  除了 FreeRTOS 操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:

  • 读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。

  • 调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第12张图片

3.2、开关中断函数

FreeRTOS 提供了一组开关中断函数,比较简单。

具体实现如下:


#define taskDISABLE_INTERRUPTS()    portDISABLE_INTERRUPTS()

#define taskENABLE_INTERRUPTS()     portENABLE_INTERRUPTS()

进一步跟踪宏定义的实现如下:

#define portDISABLE_INTERRUPTS()    vPortRaiseBASEPRI()

#define portENABLE_INTERRUPTS()     vPortSetBASEPRI( 0)

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第13张图片

Note:

这两个函数不建议使用,因为不支持嵌套。

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第14张图片

3.3、任务代码临界段处理函数

任务代码进入和退出临界段的函数如下:

#define taskENTER_CRITICAL()   portENTER_CRITICAL()

#define taskEXIT_CRITICAL()       portEXIT_CRITICAL()  

源码实现:

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第15张图片

  通过上面两个函数 vPortEnterCritical 和 vPortExitCritical 可以看出,进入临界段和退出临界段是通过函数调用开关中断函数 portENABLE_INTERRUPTS 和 portDISABLE_INTERRUPTS 实现的。细心的读者还会发现上面的这两个函数都对变量 uxCriticalNesting 进行了操作。这个变量比较重要,用于临界段的嵌套计数。

**Note:
**

临界段处理函数必须成对使用。

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第16张图片

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第17张图片

3.4、中断代码临界段处理函数

中断代码进入和退出临界段的函数如下:

#define taskENTER_CRITICAL_FROM_ISR()    portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL_FROM_ISR(x)    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

源码实现:

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第18张图片

  通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器 basepri 实现的。初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器 basepri 的数值就可以实现嵌套使用。

**Note:
**

临界段处理函数必须成对使用。

FreeRTOS 实时操作系统第六讲 - 任务与中断优先级,临界保护_第19张图片

你可能感兴趣的:(硬件家园--freeRTOS,单片机,stm32)