精华内容
下载资源
问答
  • NVIC中断优先级管理 STM32中断优先级简介 如何管理中断? 什么是“抢占优先级”和“响应优先级”呢? 优先级是如何定义的? 特别说明 函数介绍 中断优先级分组选择函数 对于每个中断怎么设置优先级? 如何...

    目录

    NVIC中断优先级管理

    STM32中断优先级简介

    如何管理中断?

    什么是“抢占优先级”和“响应优先级”呢?

    优先级是如何定义的?

    特别说明

    函数介绍

    中断优先级分组选择函数

    对于每个中断怎么设置优先级?

    如何理解中断挂起与解挂的含义?

    获取中断状态的函数

    清除/设置中断标志位的函数

    针对于中断标志位的操作有什么用?

    举例说明


    NVIC中断优先级管理

    STM32中断优先级简介

     

    NVIC的缩写是“嵌套向量中断控制器(Nested Vectored Interrupt Controller)”。

    如何管理中断?

    STM32中断优先级管理采用“响应优先级和抢占优先级”结合的方法,并且进行了中断分组。

     

    以第1组为例:1bit抢占优先级说明抢占优先级共有两级,3bits响应优先级说明响应优先级共有8级。我们可以看到无论如何分组,中断优先级总有16种。

     

    什么是“抢占优先级”和“响应优先级”呢?

    抢占优先级和响应优先级都是区分那个中断优先进行的标志,但是两者的含义去不尽相同。

    抢占优先级从名字上就比响应优先级霸气一些,因此抢占优先级起决定作用,当抢占优先级相同时,响应优先级高的才会优先发生。

    优先级是如何定义的?

    值越小,优先级越大。

    例如:假定设置中断优先级组为2,然后设置中断3(RTC中断)的抢占优先级为2,响应优先级为1。  中断6(外部中断0)的抢占优先级为3,响应优先级为0。中断7(外部中断1)的抢占优先级为2,响应优先级为0。

    优先级顺序:中断7>中断3>中断6

    结论:抢占优先级高的必定先抢占中断,如果抢占优先级相同,那就看响应优先级,此时谁的响应优先级高,谁就先发生。

    除此之外,如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个,排序越考上,中断越优先。中断向量地址可以参考“STM32中文参考手册->中断->中断异常向量”部分的内容。部分示例:

     

    特别说明

    一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组。随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。

    函数介绍

    在操作函数的底层中,我们一般只用到ISER、ICER、IP这3个寄存器。ISER用于使能中断,ICER用来清除中断,IP用来设置中断优先级。

    中断优先级分组选择函数

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    我们选择的是按照组2的方式进行分组,即抢占优先级总数为4级(0-3),响应优先级总级数为4级(0-3)。我们可以看到无论如何分组,中断优先级总有16种。

     

    对于每个中断怎么设置优先级?

    NVIC_InitTypeDef NVIC_InitStructure;  
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  // 2<3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //3<=3
    NVIC_Init(&NVIC_InitStructure);  

     

    如何理解中断挂起与解挂的含义?

    中断的挂起与解挂针对的是中断标志位,也可以叫做允许中断发生的位。

    获取中断状态的函数

    函数原型
    
    static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);
    
    函数应用
    
    uint32_t IterruptFlag = NVIC_GetPendingIRQ(USART1_IRQn);
    
    // 这个函数获取了串口1中断的状态

    清除/设置中断标志位的函数

    函数原型
    
    static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn);
    
    static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
    
    函数使用
    
    NVIC_SetPendingIRQ(USART1_IRQn);
    
    NVIC_ClearPendingIRQ(USART1_IRQn);

    针对于中断标志位的操作有什么用?

    1、一般来说单片机的中断发生有两个条件,一是中断标志位置位,二是中断允许,如果这两个条件都满足则进入中断,因为正常情况下中断一直是允许的,那么只能通过标志位来区分是否有中断挂起;

    2、如果进入中断不清除标志位,那么这一中断服务程序结束后由于标志位还是置位的并且中断是允许的,那么还会再次进入该中断,就会发生一直在执行中断程序的情况。

    举例说明

    在A中断中设置B中断的中断标志位为1,那么当B中断条件满足时,B中断可以发生,但是如果清除A中B中断标志位,那么无论是否满足触发B中断的条件,A中断中永远不可能执行B中断,因为中断发生的条件是:“中断标志位有效+中断条件满足”。

    展开全文
  • 这一期虽然也是按键控制数码管,但这次是利用IO脚产生外部中断,执行中断服务函数来实现的。剧透一下:这次的实现效果和上一期按键扫描的效果不太一样,希望细心的你看过视频后可以发现。(文末公布答案)一、前置...

    前言

    今天我们来学习外部中断(EXTI)的初始化及简单应用。可能会有读者感到奇怪,上一期不就是数码管么,怎么这一期又是?这一期虽然也是按键控制数码管,但这次是利用IO脚产生外部中断,执行中断服务函数来实现的。剧透一下:这次的实现效果和上一期按键扫描的效果不太一样,希望细心的你看过视频后可以发现。(文末公布答案)

    一、前置知识——中断与STM32外部中断简述

    1、中断的概念

    因为这个概念非常基础,学习过51单片机或者学校有开微机原理与接口技术的同学应该都知道;所以我就简单通俗的讲一下就行。中断是相对于CPU的一个概念。CPU在执行正常的程序时,突然CPU内部发生一个错误,或是有其他的工作需要打断当前代码执行顺序,这时就发生了中断。迫使CPU停止当前正常代码执行流程的信号称为中断源,CPU离开正常代码而去执行的代码段被称为中断服务程序。

    2、CPU响应中断时的工作流程(单层中断)

    CPU响应了中断,也就是终端服务程序结束后还要返回原先被打断的地方继续执行正常程序的。因此,如果把响应中断这一事件从具体CPU中抽象出来,CPU的工作流程大致如下所示:

    断点处下一条待执行指令的地址入栈--->CPU标志寄存器入栈--->CPU各寄存器值入栈--->关闭中断--->执行中断服务程序--->开启中断--->CPU各寄存器值出栈--->CPU标志寄存器出栈--->断点处下一条待执行指令的地址出栈

    3、中断优先级

    在早期的MCU(比如Intel 8051)或者MPU(比如Intel 8086/8088)中,中断优先级一般就只有一个含义,仅仅指优先级高的中断可以打断正在执行的、优先级低的中断(抢占优先级)。但是以ARM Cortex-M3为内核的STM32F103存在两种类型的中断优先级——抢占优先级和响应优先级。

    抢占优先级是针对嵌套中断而言的。举个例子就很容易理解了。假如中断A比中断B的抢占优先级要高,B发生后正在执行B的中断服务程序;这时A也发生了,那么CPU将从B的中断服务程序中响应中断A,转而执行A的中断服务程序。

    响应优先级则是指当两个抢占优先级相同的中断同时发生时,CPU将优先处理响应优先级高的中断。

    4、中断与事件

    有时候我们会混着讲“中断事件”,但其实二者是有区别的。二者的共同点在于都由中断触发,都可打断当前正常程序流程转而进行其他操作;区别在于中断是依赖于CPU的,必须通过中断服务程序起作用,而事件不通过CPU,由硬件电路直接联动实现。

    5、NVIC——嵌套向量中断控制器

    个人感觉这玩意的作用其实和8255A差不太多,都是用于管理中断并允许用户设置外部中断优先级的一个器件。用户可以通过库函数NVIC_PriorityGroupConfig(参数:NVIC_PriorityGroup_0/1/2/3/4)为NVIC设置五个不同的优先级组中的一个以配置外部中断的中断优先级。

    6、用户程序使用外部中断的程序流程

    这一部分内容太多,我就把我自己画的假流程图放上来吧。

    13fe014c098407e2fb4325e29c32bee4.png

    二、实验效果

    857a16513f0b3cf24a5e26481e7ae4ed.png
    https://www.zhihu.com/video/1238892513039425536

    三、本实验的用户函数(硬件资源参见上一期即可)

    1、NVIC初始化(设置优先级)

    void NVICConfig(void){
    	NVIC_InitTypeDef keyNVIC;
    	/*PC8和PC9配置外部中断*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    	keyNVIC.NVIC_IRQChannel = EXTI9_5_IRQn;//设置产生外部中断的IO引脚为5-9(实际使用8和9)
    	keyNVIC.NVIC_IRQChannelCmd = ENABLE;//开中断
    	keyNVIC.NVIC_IRQChannelPreemptionPriority = 0;//设置抢占优先级(数字越小级别越高)
    	keyNVIC.NVIC_IRQChannelSubPriority = 0;//设置响应优先级(数字越小级别越高)
    	NVIC_Init(&keyNVIC);
    	/*PD2配置外部中断*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    	keyNVIC.NVIC_IRQChannel = EXTI2_IRQn;//设置产生外部中断的IO引脚为2
    	keyNVIC.NVIC_IRQChannelCmd = ENABLE;//开中断
    	keyNVIC.NVIC_IRQChannelPreemptionPriority = 0;//设置抢占优先级(数字越小级别越高)
    	keyNVIC.NVIC_IRQChannelSubPriority = 0;//设置响应优先级(数字越小级别越高)
    	NVIC_Init(&keyNVIC);
    }

    2、外部中断初始化(配置外部中断工作模式)

    void KeyInterruptInit(void){
    	EXTI_InitTypeDef key;
    	NVICConfig();//配置NVIC
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource8);//PC8配置为中断源
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource9);//PC9配置为中断源
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource2);//PD2配置为中断源
    	/*配置PC8工作模式*/
    	key.EXTI_Line = EXTI_Line8;//中断源发生线为EXTI8
    	key.EXTI_LineCmd = ENABLE;//允许响应中断
    	key.EXTI_Mode = EXTI_Mode_Interrupt;//设置为中断模式
    	key.EXTI_Trigger = EXTI_Trigger_Rising;//设置为上升沿触发
    	EXTI_Init(&key);
    	/*配置PC9工作模式*/
    	key.EXTI_Line = EXTI_Line9;
    	key.EXTI_LineCmd = ENABLE;
    	key.EXTI_Mode = EXTI_Mode_Interrupt
    	key.EXTI_Trigger = EXTI_Trigger_Rising;
    	EXTI_Init(&key);
    	/*配置PD2工作模式*/
    	key.EXTI_Line = EXTI_Line2;
    	key.EXTI_LineCmd = ENABLE;
    	key.EXTI_Mode = EXTI_Mode_Interrupt;
    	key.EXTI_Trigger = EXTI_Trigger_Rising;
    	EXTI_Init(&key);
    }
    

    3、中断服务程序

    extern intr_flag;//全局变量,用于识别哪一个中断发生
    /*KEY0与KEY1*/
    void EXTI9_5_IRQHandler(void){
    	int n = 100000;
    	while(n--);//延时,消抖
    	if(EXTI_GetITStatus(EXTI_Line8) != RESET){
    		intr_flag = 1;//PC8上发生中断(KEY0被按下)
    		EXTI_ClearITPendingBit(EXTI_Line8);//清除CPU内部中断标志位
    	}
    	if(EXTI_GetITStatus(EXTI_Line9) != RESET){
    		intr_flag = 2;
    		EXTI_ClearITPendingBit(EXTI_Line9);
    	}
    }
    
    /*KEY2*/
    void EXTI2_IRQHandler(void){
    	int n = 100000;
    	while(n--);
    	if(EXTI_GetITStatus(EXTI_Line2) != RESET){
    		intr_flag = 3;
    		EXTI_ClearITPendingBit(EXTI_Line2);
    	}
    }

    4、数码管显示程序

    主体

    void ShowNumDynamicByEXTI(void){
    	static int countA=0, countB=0, countC=0;
    	HC595_WriteData(num[17]);//消影
    	switch(intr_flag){
    		case 1:
    			countA++;//按键1号计数器自增
    		        intr_flag = 0;//重置中断发生标志位
    			break;
    		case 2:
    			countB++;
    			intr_flag = 0;
    			break;
    		case 3:
    			countC++;
    			intr_flag = 0;
    			break;
    		default:
    			break;
    	}
    	show(&countA, &countB, &countC);//调用辅助函数显示数字
    }

    辅助函数(考虑到这部分的语句和上一期的显示语句完全相同,我就把这部分抽象出来写了个函数,减少重复代码的情况)

    void show(int* a, int* b, int* c){
    	/*KEY0*/
    	if(*a<=15)
    				HC595_WriteData(num[*a]);
    	else{
    				HC595_WriteData(num[0]);
    				*a = 0;
    	}
    	GPIO_ResetBits(GPIOC, 0x1c00)
    	delay();
    	/*KEY1*/
    	if(*b<=15)
    				HC595_WriteData(num[*b]);
    	else{
    				HC595_WriteData(num[0]);
    				*b = 0;
    	}
    	GPIO_ResetBits(GPIOC, GPIO_Pin_12 | GPIO_Pin_11);
    	GPIO_SetBits(GPIOC, GPIO_Pin_10);
    	delay();
    	/*KEY2*/
    	if(*c<=15)
    				HC595_WriteData(num[*c]);
    	else{
    				HC595_WriteData(num[0]);
    				*c = 0;
    	}
    	GPIO_ResetBits(GPIOC, GPIO_Pin_10 | GPIO_Pin_12);
    	GPIO_SetBits(GPIOC, GPIO_Pin_11);
    	delay();
    }
    

    四、总结

    相信细心的你已经发现了,上一期和这一期有两点不同。

    1、数字越界后的处理

    上一期数字越界后直接亮的小数点,数字部分熄灭;这次在越界后又从0开始进入下一个轮回。仔细观察show()函数就会发现,我传入了三个指针,这三个指针用于修改三个按键计数器的值——一旦发生越界,即清0相应的计数器。

    2、按键切换数字时,数码管不再熄灭一小段时间

    上一期中,我们每按下一次按键,所有数码管都要熄灭,直到我们松开按键才会被重新点亮;而这次无论松开还是按下按键,数码管都是被点亮的。

    还记得上一期的按键扫描程序吗?在函数KeyScan()中,我们有一句while(GPIO_ReadInputDataBit(GPIOx, Pin) == ON);这句的意思就是等待按键松开后再进行后续操作。当按键松开后,该函数返回一个值给显示数字的ShowNumDynamic()函数,收到返回值后才开始进行GPIO的相关操作,点亮数码管。因此,只要我们一直按住按键,这个while循环一直不会结束,一直不会有返回值,“关闭所有显示”后的if语句一直都不会执行结束,数码管也就会一直熄灭了。

    而这期呢,我们得从外部中断初始化那儿说起。在KeyInterruptInit()中,我们将三个中断源都配置为“上升沿触发”。这就是关键所在。在我们按下按键后,对应的GPIO由高电平——>低电平,产生一个下降沿,是不会触发中断的;我们一直按着按键,对应的GPIO一直是低电平,中断也没有被触发;直到我们松开按键,产生了一个由低电平到高电平的上升沿跳变,成功触发了中断!也就是说,在我们松开按键的那一瞬间之前的时间里,由于中断一直没有被触发,在执行ShowNumDynamicByEXTI()时switch-case结构是一直被跳过的,直接执行了用于点亮数码管的show()函数。因此数码管一直都被点亮,没有熄灭。

    五、结语

    STM32真好玩!!!!!!!!!!!

    展开全文
  • 1.这个中断时如何控制的,怎么判断谁先谁后 2.外设怎么能用到中断呢 3.中断那么多,是不是哪一个都可以用 怎么用其实很简单理解,我也是看普中的教学视频,他那里实行的是,用按键来中断,如up键亮led1的灯,然后...

    中断时什么概念我想学过学过单片机的人都理解,就不多说了。直接说说我所迷惑的地方
    1.这个中断时如何控制的,怎么判断谁先谁后
    2.外设怎么能用到中断呢
    3.中断那么多,是不是哪一个都可以用

    怎么用其实很简单理解,我也是看普中的教学视频,他那里实行的是,用按键来中断,如up键亮led1的灯,然后down键灭掉led1的灯,就是这样,那如何用中断来实现呢?首先我们讨论下,那个键的优先级高,那个的优先级低,这时我们就要提到 NVIC ,NVIC的全称是Nested vectoredinterrupt controller,即嵌套向量中断控制器,通俗的讲就是管理谁先谁后的一个工具吧,他的一些扩展就不说了,很容易找得到,对于我这stm32f1XX的芯片,只用了4位来管理,什么意思呢,就是二进制有4位,所以共有16种选择,2的4次方等于16,所以说优先级有十六种,但并没有那么简单,在这里 优先级 有两种 抢占优先级 和 响应优先级,看到这里我就迷惑,这两个拿来干嘛的呢? 原来是这样的 ,在比较谁先谁后时,先看抢占优先级再看响应优先级,所以设置的时候,根据这两个来选择谁先谁后,但这两种优先级使用是有规则的:

    1.先看抢占优先级,比如 a的高于b,那么b工作的时候,a可以打断b,b也可以是一个中断,就是中断可以打断中断,前提是,抢占优先级要高
    2.若是抢占优先级相同,就比较响应优先级,但是不可以打断中断,只能等中断所做的事,做完了才可以继续下一个中断。

    既然有两种优先级,NVIC有十六种选择(在这里不知道用那个词,反正能理解就行),有些时候需要多一点的抢占优先级,那么怎么办?

    这里的主优先级就是抢占优先级,子优先级是响应优先级
    有五组,可以配选择不同种类的优先级,就先这样吧,具体还有很多要操作的,其实看视频一步一步学,学的更加透彻。

    展开全文
  • 内容导读:本文从任务如何切换开始讲起,引出RTOS内核中的就绪列表、优先级表,一层一层为你揭开RTOS内核优先级抢占式调度方法的神秘面纱,只有对内核的深入了解,才能创造出更好的应用。1.知识点回顾1.1. 上文回顾...

    内容导读:

    本文从任务如何切换开始讲起,引出RTOS内核中的就绪列表、优先级表,一层一层为你揭开RTOS内核优先级抢占式调度方法的神秘面纱,只有对内核的深入了解,才能创造出更好的应用。


    1.知识点回顾

    1.1. 上文回顾

    上篇文章讲述了任务的三大元素:任务控制块、任务栈、任务入口函数,并讲述了编写RTOS任务入口函数时三个重要的注意点。

    如果你还没有阅读上一篇文章,请先阅读,这有助于对本文的理解:

    • RTOS内功修炼记(一)—— 任务到底应该怎么写?

    1.2. 双向循环链表

    双向链表是链表的一种,区别在于每个节点除了后继指针外,还有一个前驱指针,双向链表的节点长下面这样:d7a855c3b879e50d4bf9f3ed21ca62be.png

    如果你对双向循环列表的实现及使用还不熟悉,请一定要先阅读这篇文章:

    • TencentOS-tiny中的双向循环链表的实现及使用

    2. 任务是如何切换的

    在RTOS内核中,一个任务切换到下一个任务的原理是:

    「手动触发PendSV异常,在PendSV异常服务函数中实现任务切换」

    2.1. 如何触发PendSV异常

    stm32中,将中断及状态控制寄存器ICSR(Interrupt control and state register)的第28位置1,即可手动触发 PendSV 异常,如图:

    f9000d83ed369468e1c1d0d41fd07c2e.png

    tos中触发异常的底层函数为port_context_switch,实现在 arch\arm\arm-v7m\cortex-m4\armcc\port_s.S 中,如下:

        GLOBAL port_context_switch
    port_context_switch
    LDR R0, =NVIC_INT_CTRL
    LDR R1, =NVIC_PENDSVSET
    STR R1, [R0]
    BX LR

    上面这段汇编猛一看有点难,再看看两个值具体是多少:

    NVIC_INT_CTRL   EQU     0xE000ED04
    NVIC_PENDSVSET EQU 0x10000000

    所以上面这段汇编代码,不正是完成了将寄存器 ICSR(代码中是NVIC_INT_CTRL) 的第28位置1的操作吗?

    2.2. 异常服务中实现任务切换

    在 stm32 中 PendSV 的异常服务函数名为 PendSV_Handler,默认在stm32l4xx_it.c中提供了一个弱定义,所以tos中的实现直接重定义此函数即可,源码在 arch\arm\arm-v7m\cortex-m4\armcc\port_s.S中,主要步骤有四个:

    「关闭全局中断」(NMI 和 HardFault 除外),防止任务切换过程被中断:

    CPSID   I

    「保存上文环境」:保存当前CPU寄存器组的值、PSP栈顶指针的值到任务栈中;

    「加载下文环境」:加载当前任务栈中的值到CPU寄存器组、PSP栈顶指针中;

    「打开全局中断」,实时响应系统所有中断:

    CPSIE   I

    记住任务切换的这四个过程即可,深入研究每行汇编指令是什么意思,没有太大的作用和帮助。

    2.3. CPU何时响应PendSV异常

    我们都知道,「高优先级的中断会打断低优先级的中断」,这也是系统实时性的一个重要保障,所以就引入了一个问题:

    相比起GPIO中断、定时器中断、串口中断这些外部中断,PendSV异常的优先级更高呢?还是更低呢?

    想象这样一种情况:

    ① CPU正在开心的运行着任务1……

    ② 此时你按下了按键,产生了一个GPIO中断,CPU收到后马上跑去执行中断处理函数……

    ③ 处理过程中,此时系统产生了一个PendSV异常,CPU收到后,嘲讽了一句:“我就是从普通任务跑来处理中断的,还没处理完,现在又让我执行下一个普通任务,脑子抽风了?”,说完继续处理中断……

    所以说,无论任务的优先级有多高,它都没有中断高,「系统的PendSV异常优先级必须设为最低的」,以避免在外部中断服务函数中产生任务切换。

    设置PendSV异常优先级的寄存器如下,值可以为0-255:8994cffef539703902ffd83d525f4ba6.png

    tos中在启动调度时设定pendsv异常的优先级,源码如下:

    NVIC_SYSPRI14   EQU     0xE000ED22
    NVIC_PENDSV_PRI EQU 0xFF

    同样,设置pendSV异常优先级为最低的汇编代码如下:

    set pendsv priority lowest
    ; otherwise trigger pendsv in port_irq_context_switch will cause a context switch in irq
    ; that would be a disaster
    MOV32   R0, NVIC_SYSPRI14
    MOV32   R1, NVIC_PENDSV_PRI
    STRB    R1, [R0]

    3. 就绪列表

    3.1. 就绪列表长啥样

    就绪列表其实就是好多条双向链表+一张优先级表,它的类型定义在tos_sched.h,如下:

    typedef struct readyqueue_st
    {

        k_list_t    task_list_head[TOS_CFG_TASK_PRIO_MAX];
        uint32_t    prio_mask[K_PRIO_TBL_SIZE];
        k_prio_t    highest_prio;
    readyqueue_t;

    「给每个优先级都分配了一个双向链表的首节点,用于挂载该优先级的任务」

    TOS_CFG_TASK_PRIO_MAX 是最大任务优先级,在tos_config.h中配置,默认是10:

    #define TOS_CFG_TASK_PRIO_MAX           10u

    节点类型 k_list_t 是一个双向链表节点类型:

    typedef struct k_list_node_st
    {

        struct k_list_node_st *next;
        struct k_list_node_st *prev;
    k_list_t;

    所有双向链表节点初始化完毕之后,每一个双向结点的next指针指向自己(橙色线),prev指针也指向自己,如图:b68f83042f442296859d4ffb839581bd.png

    「用于指示系统目前所使用优先级的优先级表」

    优先级表的大小由宏定义 K_PRIO_TBL_SIZE 决定:

    #define K_PRIO_TBL_SIZE         ((TOS_CFG_TASK_PRIO_MAX + 31) / 32)

    这儿定义的时候比较讲究,如果最大优先级不大于32,则该宏的值为1,使用一个uint32_t类型的变量即可,每个优先级的表示占一位。

    初始化后的优先级表长下面这个样子:d906eacce4a75460ae8db1bd5198fa16.png「最高优先级指示成员」

    就绪列表中的 highest_prio 成员是 k_prio_t 类型,其实就是一个uint8_t类型:

    typedef uint8_t             k_prio_t;

    该成员表示系统中当前所存在任务的最高优先级,默认是系统定义的最大优先级。

    3.2. 系统中的就绪列表

    系统中有多少条就绪列表呢?

    对了,答案当然是:「仅有唯一的一条就绪列表」

    tos_global.h中声明,便于在整个内核的所有文件中使用:

    /* ready queue of tasks                         */
    extern readyqueue_t         k_rdyq;

    tos_global.c中定义:

    readyqueue_t        k_rdyq;

    「记住它的名字,它叫k_rdyq」,k就是kernel,rdyq就是ready queue的缩写,后面会经常出现。

    3.3. 初始化就绪列表

    知道了就绪列表长啥样,就绪列表的初始化就变得非常简单了,都是常规操作,在tos_sched.c文件中实现:

    __KNL__ void readyqueue_init(void){
        uint8_t i;

        k_rdyq.highest_prio = TOS_CFG_TASK_PRIO_MAX;

        for (i = 0; i         tos_list_init(&k_rdyq.task_list_head[i]);
        }

        for (i = 0; i         k_rdyq.prio_mask[i] = 0;
        }
    }

    第①步是设置最高优先级成员的初始值,为系统当前配置的最高优先级;

    第②步是遍历初始化每个双向链表节点;

    第③步就是初始化优先级表的所有值,为0。

    4. 任务如何挂载到就绪列表

    在任务创建API的最后,会调用 readyqueue_add_tail 函数将任务加入到就绪列表中,那么,任务究竟是被如何挂载上去的呢?

    此函数的源码实现如下:

    __KNL__ void readyqueue_add_tail(k_task_t *task){
        k_prio_t task_prio;
        k_list_t *task_list;

        task_prio = task->prio;
        task_list = &k_rdyq.task_list_head[task_prio];

        if (tos_list_empty(task_list)) {
            readyqueue_prio_mark(task_prio);
        }

        tos_list_add_tail(&task->pend_list, task_list);
    }

    「获取该任务的优先级在就绪列表中所对应的首节点」

    「在优先级表中记录此优先级」

    判断系统中该优先级是否第一次出现,如果是,则将优先级表中此优先级的标志位置1,表示系统中存在此优先级的任务,并重新赋值就绪列表中的最高优先级指示成员(注:优先级值越小,表示优先级越高):

    __STATIC_INLINE__ void readyqueue_prio_insert(k_prio_t prio){
        k_rdyq.prio_mask[K_PRIO_NDX(prio)] |= K_PRIO_BIT(prio);
    }

    __STATIC_INLINE__ void readyqueue_prio_mark(k_prio_t prio){
        readyqueue_prio_insert(prio);

        if (prio         k_rdyq.highest_prio = prio;
        }
    }

    举个例子,当我们创建了一个优先级为2的任务后,则优先级表如下:29701d6a6a34b3f22188bcb77df1d361.png

    「将该任务的任务控制块的pendlist节点,挂载到第一步获取到的首节点所指示的链表尾部」

    任务控制块中的pend_list成员也是一个双向链表节点:

    /**
     * task control block
     */

    struct k_task_st {
     //……
     
        k_list_t pend_list; /**//……
    };

    使用双向链表将此pendlist节点添加到第①步获取到的链表尾部,添加之后如图:0b0c310e2af22e6841e0ddac67a6c1f4.png

    5. 优先级抢占式调度

    5.1. 调度规则

    理解了上面的三节内容,再来看优先级抢占式调度,简直就是水到渠成。

    同样,先放上优先级抢占式调度的源码,在tos_syc.c中:

    __KNL__ void knl_sched(void){
        TOS_CPU_CPSR_ALLOC();

        if (unlikely(!tos_knl_is_running())) {
            return;
        }

        if (knl_is_inirq()) {
            return;
        }

        if (knl_is_sched_locked()) {
            return;
        }

        TOS_CPU_INT_DISABLE();
        k_next_task = readyqueue_highest_ready_task_get();
        if (knl_is_self(k_next_task)) {
            TOS_CPU_INT_ENABLE();
            return;
        }

        cpu_context_switch();
        TOS_CPU_INT_ENABLE();
    }

    在源码中可以看到,优先级抢占式调度其实就是两个步骤:

    ① 获取就绪列表中的最高优先级的任务控制块指针;

    ② 启动上下文切换;

    总结一下,优先级抢占式调度的规则就是:

    「每当符合调度条件时时,就切换到就绪列表中优先级最高的任务开始运行」

    5.2. 如何获取最高优先级的任务

    别忘了就绪列表中有一个成员叫highest_prio,该成员指示出了系统当前存在的最高优先级,可以很方便的获取到挂载最高优先级的任务链表,函数源码如下:

    __KNL__ k_task_t *readyqueue_highest_ready_task_get(void){
        k_list_t *task_list;

        task_list = &k_rdyq.task_list_head[k_rdyq.highest_prio];
        return TOS_LIST_FIRST_ENTRY(task_list, k_task_t, pend_list);
    }

    但是需要注意,在就绪列表上挂载的是任务控制块中的pend_list节点,如图:0b0c310e2af22e6841e0ddac67a6c1f4.png

    已知任务控制块中pend_list节点地址,如何知道它所在任务控制块的基地址呢?

    其实它是通过 TOS_LIST_FIRST_ENTRY 这个宏来获取的,具体的使用方法,请阅读我在文章开头提出的第二篇文章。

    6. 优先级表有什么用?

    优先级表的作用是:

    「在将任务从就绪列表中移出时,用来获取当前就绪列表中的最高优先级」

    优先级抢占式调度器可是六亲不认的,才不管任务当前状态是什么,反正就是永远寻找调度列表中最高优先级的任务。

    所以当任务调用要主动挂起时,必须要从就绪列表中移出,源码如下:

    __API__ k_err_t tos_task_suspend(k_task_t *task){
        TOS_CPU_CPSR_ALLOC();

     //一堆参数判断,省略了

        TOS_CPU_INT_DISABLE();

        if (task_state_is_ready(task))
        { 
         // kill the good kid
            readyqueue_remove(task);
        }
        task_state_set_suspended(task);

        TOS_CPU_INT_ENABLE();
        knl_sched();

        return K_ERR_NONE;
    }

    其中核心的就绪列表移出函数 readyqueue_remove 源码如下:

    __KNL__ void readyqueue_remove(k_task_t *task){
        k_prio_t task_prio;
        k_list_t *task_list;

        task_prio = task->prio;
        task_list = &k_rdyq.task_list_head[task_prio];

        tos_list_del(&task->pend_list);

        if (tos_list_empty(task_list)) {
            readyqueue_prio_remove(task_prio);
        }

        if (task_prio == k_rdyq.highest_prio) {
            k_rdyq.highest_prio = readyqueue_prio_highest_get();
        }
    }

    ① 获取任务当前优先级在就绪列表中的首节点;

    ② 将该任务控制块与该条双向链表断开(并没有删除任务);

    ③ 如果断开后该链表变空,则表示就绪列表中不存在该优先级的任务,在优先级表中将该位清零;

    「重新获取就绪列表中的最高优先级」

    这个时候优先级表的作用就体现出来了,之前讲到,优先级表中记录了当前就绪列表中所存在任务的优先级,所以可以通过遍历查找优先级表,来获取到最高优先级,最后赋值给就绪列表中的指示成员。

    源码如下:

    __STATIC__ k_prio_t readyqueue_prio_highest_get(void){
        uint32_t *tbl;
        k_prio_t prio;

        prio    = 0;
        tbl     = &k_rdyq.prio_mask[0];

        while (*tbl == 0) {
            prio += K_PRIO_TBL_SLOT_SIZE;
            ++tbl;
        }
        prio += tos_cpu_clz(*tbl);
        return prio;
    }

    7. 总结

    讲述了这么多内容,非常有必要来总结出值得注意的点:

    「RTOS内核中通过手动触发PendSV异常来启动一次切换,任务切换在PendSV异常服务函数中实现」

    「RTOS内核中PendSV异常的优先级被设为最低,避免在外部中断处理函数中产生任务切换」

    「RTOS内核所谓的优先级抢占式调度规则就是永远从就绪队列中找出最高优先级的任务来运行」

    当然,有了优先级抢占式调度规则,才勉强撑起来了一个RTOS内核的肉体,什么时候进行调度,才是一个RTOS内核的灵魂,接下来的文章与大家再会,我是Mculover666,一个喜欢玩板子的小码农。

    「接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。」a52cf1099de275bd376992ed8b739738.png

    展开全文
  • stm32中,将中断及状态控制寄存器 NVIC_INT_CTRL 的第28位置1,即可触发 PendSV 异常,tos中触发异常的底层函数为port_context_switch,实现在 arch\arm\arm-v7m\cortex-m4\gcc\port_s.S中,如下: GLOBAL port
  • 前面我们讲了中断控制的简单应用,下面就让我们结合键盘输入和七色灯的知识,看看怎么实现按键优先级屏蔽吧! 功能四:按键优先级屏蔽 具体:使用主板蓝色按键作为一个低优先级中断源,键盘阵列选择其中的一个按键...
  • 通用IO的中断

    千次阅读 2016-12-22 13:45:34
    前言有的人问我,为什么我的博客没有讲有多少引脚,分别是什么之类的问题。...第一步,中断优先级的配置已知IP1_X和IP0_X是设置优先级的,其中X是中断优先级组的组名。0的优先级别最低,3的优先级别
  • 21.1 NVIC基础知识NVIC的全称是Nested vectored interrupt controller,即嵌套向量中断控制器。对于M3/M4/M7内核的MCU,每个中断优先级都是用寄存器中的8位来设置的。8位的话就可以设置2^8 = 256级中断,实际中用...
  • X86架构中断的处理

    千次阅读 2013-02-26 18:38:24
    中断控制器8259A的原理和使用   讨论这些问题,首先要了解中断的概念,什么是中断中断怎么一回事。...结合中断的概念,比如中断控制器接受到一个中断请求,而该中断优先级高于cpu在处理的问
  • 内核控制路径就是内核里面的代码,即位于0xc000 0000以上的函数代码,包括中断处理函数,异常处理函数(比如驱动里的实现read的函数),内核线程函数 那么怎么才能让cpu去执行这个代码呢? 中断处理函数:硬件通知cpu...
  • 内容导读: ...RTOS内功修炼记(二)—— 优先级抢占调度到底是怎么回事? 建议先阅读上文,对RTOS内核的抢占式调度机制理解之后,再阅读本文也不迟。 这篇文章将讲述RTOS内核到底是如何管理中断
  • 本文所有的测试是基于STM32F103测试的,因为STM32F103是Cortex-M3内核的芯片,而且恰好我手里有这...那么内核的异常优先级怎么设置呢,比如Sistick,PendSV呢?内核异常优先级由系统控制块管理。 那我们先看一下内核的
  • 关于 arm的中断系统

    千次阅读 2011-04-11 19:30:00
    控制着整个中断源和优先级的判别。下面是别人写的有关这方面的知识:ARM有七种模式,我们这里只讨论SVC、IRQ和FIQ模式。 我们可以假设ARM核心有两根中断引脚(实际上是看不见的),一根叫 irq pin, 一根叫fiq pin....
  • 控制着整个中断源和优先级的判别。 下面是别人写的有关这方面的知识: ARM有七种模式,我们这里只讨论SVC、IRQ和FIQ模式。 我们可以假设ARM核心有两根中断引脚(实际上是看不见的),...
  • Linux 并发控制

    2019-12-13 14:47:40
    灵魂拷问: 什么叫并发? 并发会导致什么不良后果? 并发的场景都有哪些? 怎么解决并发带来的问题? 并发: 多个执行单元同时并行被执行,访问同一块共享资源...中断被高优先级中断抢占; SMP多核CPU 每个CPU内部...
  • RTOS内功修炼记(七)—— 内存管理

    千次阅读 2020-07-25 18:18:36
    内容导读: ...RTOS内功修炼记(二)—— 优先级抢占调度到底是怎么回事? 第三篇文章讲述了RTOS内核到底是如何管理中断的?用户该如何编写中断处理函数?以及用户如何设置临界段? RTOS内功修炼
  • 内容导读: ...RTOS内功修炼记(二)—— 优先级抢占调度到底是怎么回事? 第三篇文章讲述了RTOS内核到底是如何管理中断的?用户该如何编写中断处理函数?以及用户如何设置临界段? RTOS内功修炼
  • 内容导读: ...RTOS内功修炼记(二)—— 优先级抢占调度到底是怎么回事? 第三篇文章讲述了RTOS内核到底是如何管理中断的?用户该如何编写中断处理函数?以及用户如何设置临界段? RTOS内功修炼
  • 个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的 CPU 使用权就被剥夺了, 或者说被挂起了, 那个高优先级的任务立刻得到了 CPU 的控制权。 它是怎么实现这一点的呢?ucosii依靠系统时钟开达到目的...
  • 单片机MSP430入门理论⑨--定时器模块-定时器A④ ...上图先简单说明下,TATV中断向量寄存器,看上图,TATV的值为0A(十进制的10)时,就表明目前触发的是溢出定时器中断标志TAIFG,属于优先级最低的中断,当TA..
  • ARM Cortex M4F 处理器和嵌入式向量中断控制器(NVIC)在Handler(不知道怎么翻译,句柄?)模式按着优先级处理所有的中断异常。异常发生时,处理器状态自动存储在栈中并在中断处理程序(ISR)结束时自动回复到栈中...
  • 第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二...
  • 第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二...
  • C语言编程要点

    2017-09-18 00:10:37
    14.13. 可以通过BIOS控制鼠标吗? 207 第15章 可移植性 209 15.1. 编译程序中的C++扩充功能可以用在C程序中吗? 210 15.2. C++和C有什么区别? 210 15.3. 在c程序中可以用“∥”作注释吗? 211 15.4. char,short,int和...
  • DOM</li><li>React Component 的生命周期函数是怎么被调用的?</li></ul> 在开始源码分析之前,首先先简单介绍一下React的一些基础概念 基础概念 <p>React定位是一个构建用户界面的JavaScript类库ÿ...
  • 就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个...
  • 就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个...
  • 就算括号不行,操作符优先级是否能够控制计算顺序呢? 64 3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码…… 64 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经...
  • 就算括号不行,操作符优先级是否能够控制计算顺序呢?  3.6 可是&&和||操作符呢?我看到过类似while((c=getchar())!=EOF&&c!='\n')的代码……  3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个...
  • 就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c=getchar())!=EOF&&c!='\n')的代码…… 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式...

空空如也

空空如也

1 2
收藏数 36
精华内容 14
关键字:

中断优先级怎么控制