论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第11章 ThreadX中断优先级配置,含BasePri配置方案
本章节为大家讲解ThreadX中断优先级配置,此章节非常重要,初学者经常在这里犯迷糊。对于初学者来说,本章节务必要整明白。
11.1 NVIC基础知识
11.2 使用ThreadX时如何配置外设NVIC
11.3 ThreadX配置选项中NVIC相关配置
11.4 不受ThreadX管理中的的深入讨论
11.5 实验例程
11.6 总结
11.1 初学者重要提示
- ThreadX官方配套开发中断方案是用的PRIMASK寄存器,本章节我们分享一种使用BasePri寄存器开关中断的玩法,这样可以让高优先级中断实现零中断延迟。
- 使用这种方法,不可在不受ThreadX管理的中断里面再调用ThreadX的API函数。
11.2 NVIC基础知识
NVIC的全称是Nested vectored interrupt controller,即嵌套向量中断控制器。
对于M3和M4内核的MCU,每个中断的优先级都是用寄存器中的8位来设置的。8位的话就可以设置2^8 = 256级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如ST的STM32F1xx,F4xx,H7xx只使用了这个8位中的高四位[7:4],低四位取零,这样2^4=16,只能表示16级中断嵌套。
对于这个NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级,下面就以STM32为例进行介绍,STM32F1xx,F4xx,H7xx都是只使用了这个8位寄存器的高四位[7:4]。
从上面的表格可以看出,STM32支持5种优先级分组,系统上电复位后,默认使用的是优先级分组0,也就是没有抢占式优先级,只有子优先级,关于这个抢占优先级和这个子优先级有几点一定要说清楚。
- 具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。
- 在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。
- 在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。
- Reset、NMI、Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。
- 对于初学者还有一个比较纠结的问题就是系统中断(比如:PendSV,SVC,SysTick)是不是一定比外部中断(比如SPI,USART)要高,答案:不是的,它们是在同一个NVIC下面设置的。
掌握了这些基础知识基本就够用了。另外特别注意一点,配置抢占优先级和子优先级,他们合并成的4bit数字的数值越小,优先级越高,这一点千万不要搞错了,下面通过11.2小节举一个实例。
11.3 使用ThreadX时如何配置外设NVIC
强烈推荐用户将STM32的NVIC优先级分组设置为4,即:这样中断优先级的管理将非常方便。此函数在程序优先调用:(注意:一旦初始化好NVIC的优先级分组后,切不可以在应用中再次更改。)
设置NVIC的优先级分组为4表示支持0-15级抢占优先级(注意,0-15级是16个级别,包含0级),不支持子优先级。反映在STM32的HAL配置上就是如下:
/* ********************************************************************************************************* * 函 数 名: System_Init * 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void System_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIC优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif #if Enable_RTTViewer == 1 /* 配置通道0,上行配置*/ SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); /* 配置通道0,下行配置*/ SEGGER_RTT_ConfigDownBuffer(0, "RTTDOWN", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); #endif }
在这里继续强调下这一点,在NVIC分组为4的情况下,抢占优先级可配置范围是0-15,那么数值越小,抢占优先级的级别越高,即0代表最高优先级,15代表最低优先级。
11.4 不受ThreadX管理中断的深入讨论
11.4.1 实现原理
讲解不受ThreadX管理的中断之前要说一个小知识点----中断延迟。中断延迟时间是衡量RTOS实时操作系统的一项重要指标,那什么又是中断延迟呢?从中断触发到执行中断服务程序的第一条指令这段时间就是中断延迟时间。
ThreadX内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。比如在源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。如果这是一个紧急的中断事件,得不到及时执行的话,后果是可想而知的。
针对这种情况,我们为ThreadX就专门做了一种新的开关中断实现机制。关闭中断时仅关闭受ThreadX管理的中断,不受ThreadX管理的中断不关闭,这些不受管理的中断都是高优先级的中断,用户可以在这些中断里面加入需要实时响应的程序。ThredX能够实现这种功能的奥秘就在于ThreadX开关中断使用的是寄存器basepri,而非官方默认配套使用的primask,详情请看下面整理的表格:
对寄存器basepri我们举一个例子,帮助大家理解,比我们配置寄存器basepri的数值为16,所有优先级数值大于等于16的中断都会被关闭,优先级数值小于16的中断不会被关闭。对寄存器basepri寄存器赋值0,那么被关闭的中断会被打开。这个就是ThreadX开关中断的实现方案。
11.4.2 实现代码(AC5,AC6,IAR和GCC均支持)
实现代码如下,大家仅需修改tx_port.h文件中的TX_DISABLE,TX_RESTORE和_tx_thread_system_return_inline实现为如下即可:
#include "stm32f4xx_hal.h" #define ThreadX_MAX_INTERRUPT_PRIORITY (0x10) #define TX_INTERRUPT_SAVE_AREA uint32_t was_masked; #define TX_DISABLE was_masked = __get_BASEPRI(); __set_BASEPRI(ThreadX_MAX_INTERRUPT_PRIORITY); #define TX_RESTORE __set_BASEPRI(was_masked); #define _tx_thread_system_return _tx_thread_system_return_inline static void _tx_thread_system_return_inline(void) { unsigned int was_masked; /* Set PendSV to invoke ThreadX scheduler. */ *((ULONG *) 0xE000ED04) = ((ULONG) 0x10000000); if (__get_IPSR() == 0) { was_masked = __get_BASEPRI(); __set_BASEPRI(0); __set_BASEPRI(was_masked); } }
注意:我们这里设置宏定义ThreadX_MAX_INTERRUPT_PRIORITY为0x10,表示调用函数TX_DISABLE关闭中断的时候,仅关闭抢占优先级1到15,抢占优先级0未不关闭(NVIC的优先级分组为4,STM32仅使用高4bit)。大家可以根据自己的情况做修改调整。
11.5 实验例程
配套例子:
V6-3007_ThreadX Task Priority Change
实验目的:
- 学习ThreadX的开关中断使用BasePri寄存器方案,这样使用ThreadX可以仅开关受ThreadX管理的中断,从而可以让高优先级中断实现零中断延迟。
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
===================================================
OS CPU Usage = 1.94%
===================================================
Prio StackSize CurStack MaxStack Taskname
2 4092 383 391 App Task Start
3 4092 543 659 App Msp Pro
4 4092 391 391 App Task UserIF
5 4092 543 659 App Task COM
30 1020 519 519 App Task STAT
31 1020 143 71 App Task IDLE
0 1020 391 391 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理,这里未使用。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
App Task STAT任务 :统计任务
App Task IDLE任务 :空闲任务
System Timer Thread任务:系统定时器任务
2、 (1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
3、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
实验操作:
- K1按键按下打印任务执行情况。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
11.6 总结
本章节为大家讲解ThreadX中断优先级配置,特别是不受ThreadX管理中断的实现方案,望大家熟练掌握。