前言:本文将从”这是什么?“ ”为什么需要它?“ “如何配置操作它”三个角度展开讨论分析

目录

中断简介

抢占优先级和子优先级

中断分组

配置要点

EXTI

EXTI框图讲解

信号产生过程

编程要点


中断简介

中断,即机器运行过程中出现某些意外情况,需要机器停止正在运行的程序并转入处理新情况的程序,处理完毕之后又返回原来被暂停的程序继续运行。

理解中断

想象一个这样的场景:

你在认真的敲代码,你妈喊你出房间去客厅吃饭,并且以不出来就拔网线为威胁。这时候你能怎么办?只能乖乖保存好现在的进度,然后出去恰饭,恰完之后再回来打开之前的文件继续敲咯。

我们分析一下上面的场景:

  • 正在敲代码——正在执行的事件
  • 你妈喊你吃饭——中断源
  • 你听到你妈喊你吃饭——接收到中断请求
  • 威胁你——事件优先级别高
  • 保存已经写好的部分——保存现场(中断响应)
  • 去吃饭——执行中断事件
  • 吃完饭回房间——中断返回
  • 打开文件继续敲代码——恢复现场

对于机器来说也是一样,无论是内核出现问题(异常),还是因为存在其他比当前正在执行的程序的优先级更高的事件发生(外部中断),都会打断当前运行的程序,去处理更加重要的事情。

毕竟,就算机器的运算速度很快,处理事件也有先后之分不是?

接下来,让我们再次回到刚刚那个场景,考虑下面两种情况:

  1. 当正在吃饭的时候,你忽然又想去WC解决下个人问题。这时你会停止吃饭的这个动作,然后去WC解决完之后再回来吃饭。吃完饭才会继续。
  2. 当正在吃饭的时候,你收到快递小哥的短信,告诉你丰巢的验证码可以取件的时候,你肯定会先吃完饭,然后去取完快递之后(也可能先在那放着不取)再回房间敲代码。

对于情况1而言,为了避免某些比较尴尬的事情发生,先去WC再回来吃饭几乎是必然选择。因为它比吃饭紧急。而对于情况2而言,取快递相对来说没那么急迫的需求,所以一般而言我们都会先吃晚饭,再去拿快递,最后再回房间敲代码。

当然如果敲代码比较着急,那么拿快递这件事情自然就得等到我们敲完代码之后再做了。

从上面两个场景中,我们很容易得发现中断时可以嵌套的,且它有一定的优先级概念。

很显然,我们的日常生活中也会有许多中断事件。我们人脑是怎么处理它们的?认真思考不难发现,其实对于很多事件,我们早就给他们定义了优先级。而定义这些事件的优先级并严格执行的就是我们的大脑。

对于CPU来说也一样,当中断事件很少的时候,不需要分门别类的整理,直接比较判断优先级就可以了。

而对于中断事件较多的复杂系统,对于事件的分组和优先级设置也就成了刚需。

下面进入到正题,以STM32F10x的中断系统为例,展开学习。

STM32中的中断设置

我们知道,STM32用的是Cortex-M3内核

而CM3内核支持256个中断,256级可编程设置(优先级设置),其中:

  • 内核中断16个
  • 外设中断240个

且以上配置属于STM32中的顶配,具体的STM32F103系列只有:

  • 内核中断16个
  • 外设中断44个

小结一下以上的数据,可以发现,实际中断事件的多少取决于芯片的定位。高端的,即外设多的,芯片中断必然多,反之必然少。

NVIC

而要处理那么多的中断事件,CM3内核给出的解决方案是——用NVIC(Nested Vectored Interrupt Controller)嵌套向量中断控制器

NVIC首先定义了一段地址区域,(一般从0地址开始)用于存放中断事件服务函数的地址。定义的这段地址就称为中断向量表。

以上是《stm32f10x中文参考手册》中的截图

可以看到它一个地址对应着一个中断事件,其中灰色部分是内核相关的中断的位置。每一条指令执行完之后,系统都会检查一遍看看有没有中断事件发生。如果有的话,就会在中断向量表中找到对应的中断事件,执行中断事件所在地址指向的函数(即中断函数)。

也就是因为中断事件所在的地址存的是一个地址,它指向的是该中断对应发生的事件(函数)。所以其实中断向量表这个名字起的很符合它的实际含义——表里的是一个个有指向的值

至于定义这些中断事件的优先级和它们具体是干嘛的,NVIC把设置的权利交给了开发者,它只给了可以完成相关操作的寄存器组。

/** @addtogroup CMSIS_CM3_NVIC CMSIS CM3 NVICmemory mapped structure for Nested Vectored Interrupt Controller (NVIC)@{*/
typedef struct
{__IO uint32_t ISER[8];                      /*!< Offset: 0x000  Interrupt Set Enable Register           */uint32_t RESERVED0[24];                                   __IO uint32_t ICER[8];                      /*!< Offset: 0x080  Interrupt Clear Enable Register         */uint32_t RSERVED1[24];                                    __IO uint32_t ISPR[8];                      /*!< Offset: 0x100  Interrupt Set Pending Register          */uint32_t RESERVED2[24];                                   __IO uint32_t ICPR[8];                      /*!< Offset: 0x180  Interrupt Clear Pending Register        */uint32_t RESERVED3[24];                                   __IO uint32_t IABR[8];                      /*!< Offset: 0x200  Interrupt Active bit Register           */uint32_t RESERVED4[56];                                   __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */uint32_t RESERVED5[644];                                  __O  uint32_t STIR;                         /*!< Offset: 0xE00  Software Trigger Interrupt Register     */
}  NVIC_Type;
/*@}*/ /* end of group CMSIS_CM3_NVIC */

在固件库头文件core_cm3.h中可以找到以上NVIC的寄存器组映射

其中如果我们使用寄存器编程的话,最关心的三个寄存器是:

  • ISER:Interrupt Set Enable Register ,中断使能设置寄存器
  • ICER:Interrupt Clear Enable Register,中断使能清除寄存器
  • IP:Interrupt Priority Register,中断优先级寄存器

在这里满,中断优先级寄存器是需要我们着重研究的。有意思的是,固件库命名的时候对于该寄存器的简写直接只用IP,而不是IPR,害我一度怀疑这两个是不是同一个东西。直到我查了手册才确定是一个东西。

IPR中断优先级寄存器

原CM3设计中,NVIC_IPRx用于配置外部中断的优先级。IPR宽度为8bit,理论上可以配置的优先级为0-255,数值越小优先级越高。但和大多数的CM3芯片设计都会精简一样,STM32也做了相关的精简设计,只用了高4bit.也就是说,实际上STM32允许配置的优先级只有16个

抢占优先级和子优先级

CM3把优先级划分成了抢占优先级和子优先级(又叫响应优先级)。即,对于同一个中断事件,它有抢占优先级和子优先级两个优先级。

为什么要这么设置?

因为如果只是简单的一种优先级的话,太过粗线条了。像CM3内核设置最多有256级,即使是stm32也有16级,级数太多,没有主次不方便使用和管理。

类比我们平时生活中的状态,就像社会上有256个人,每个的级别都一样,估计你会眼花缭乱,解决这个问题的办法就是把这256级的中断分类归层,层级内再分子级的分层管理形式,达到压缩层级的效果。如先分统治阶级,奴隶主、奴隶等,同一阶级在区分小级别。

对应于中断分组就是,把256级优先级根据本工程的需求,先规定了中断层数(即抢占优先级)和每层有多少小级别个数。

抢占优先级和子优先级比较

只有当抢占优先级相同的时候,子优先级才会发挥作用。若子优先级也相同,就去比较两个外设的硬件终端编号,即该中断在中断向量表中的位置,同样是编号越小,优先级越高的原则。

值得注意的是抢占式优先级和子优先级还有些许差别:

  • 抢占优先级:无关中断产生的先后,只比较优先级的高低。优先级高的中断信号即使比中断优先级低的中断信号后到,也可以直接中断优先级低的事件直接抢占系统资源。
  • 子优先级:当两个中断信号有相同的抢占优先级,分两种情况考虑:
    • 两个信号同时到达,子优先级高的中断先响应
    • 子优先级低的中断事件正在发生,子优先级高的中断信号刚到的时,需要等到当前中断完成后,在执行刚到的中断信号。

中断分组

从前面的讨论中我们可以知道,为了中断更加可控,CM3内核做了两种优先级的分层机制。

但是对于不同的需求,我们对于阶层和小阶级的数量要求也是不同的,以身份证为例,在发放身份证之前,我们要总体考虑全国要分多少个区域,然后确定地区需要设置几位数才够。熟悉计算机网络的朋友可以发现,这里其实和子网划分是一个道理。

到这里,我们也可以看到,在一个工程中,我们一般只会设置一次,且每次只设置一个分组。还是拿身份证举例,你不可能前一秒还说身份证前3位表示A省,下一秒就说前4位表示B省吧?编码格式统一是一切的基础。

所以,我们平时在设置分组时,若不是非常特殊的需求

一个工程只设置一次分组!!!

一个工程只设置一次分组!!!

一个工程只设置一次分组!!!

重要的事情说三遍!!!

这个分组管理的设置由内核外设SCB_AIRCR(应用程序中断及复位控制寄存器)的prigroup[10:8]控制。

因为它通过3bit控制,所以理论上可以分成8组(0-7组),其中每组可设置的抢占优先级和子优先级的位数时不同的。

如上图,可以看到,随着组号的增大,抢占优先级可设置的位越少,子优先级越多。

上面这种分组时针对IPR里的8bits都用上的情况设计的,而我们知道STM32的NVIC是CM3的NVIC的子集,只用了IPR的高4bits,所以分组自然而然就减少了。

有了以上的原理性的认知之后,我们可以进入到编程配置环节。

配置要点

  • 使能某个中断
  • 中断分组
  • 初始化NVIC_InitTypeDef结构体
    • 设置中断源
    • 设置抢着优先级和子优先级
    • 配置中断使能/失能
  • 编写中断服务函数(就是中断发生之后,告诉机器去干嘛)

上面的流程中提到的NVIC_InitTypeDef结构体,就是如下图中的:

** * @brief  NVIC Init Structure definition  */typedef struct
{uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.This parameter can be a value of @ref IRQn_Type (For the complete STM32 Devices IRQ Channels list, pleaserefer to stm32f10x.h file) */uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channelspecified in NVIC_IRQChannel. This parameter can be a valuebetween 0 and 15 as described in the table @ref NVIC_Priority_Table */uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specifiedin NVIC_IRQChannel. This parameter can be a valuebetween 0 and 15 as described in the table @ref NVIC_Priority_Table */FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannelwill be enabled or disabled. This parameter can be set either to ENABLE or DISABLE */
} NVIC_InitTypeDef;

它跟NVIC_Type结构体的区别在于,NVIC_Type只是寄存器组的映射,而NVIC_InitTypeDef是固件库抽象提炼出来帮助我们简化配置流程用的。

下面我们以一个实例体会上述的过程。

EXTI

EXTI(External interrupt/event controller)——外部中断/事件控制器,用于管理控制器的20个中断/事件线(互联型才有20根,其他类型只有19根)。

NVIC和EXTI的关系

我们首先来理一理这东西根NVIC的关系:

1、NVIC是CM3内核的东西,它用于整个整个芯片的中断控制。而EXTI是ST公司设计用于控制外设的,它不是内核里面的东西。

2、EXTI通过魔改引出的NVIC中的20根中断信号线,对外设事件的控制能力进一步增强了。

在这里,我们先理清三个概念:

  • 事件
  • 中断
  • 中断事件

比如一老师在教室里面给学生上课,课堂上的学生可能做出各种行为的动作,比如做笔记,打哈欠,翻书包,讲小话等,我们把这些行为统称为事件,其中有些行为老师往往只是视而不见,继续他的上课;而有些行为可能导致老师的上课中止,比方讲小话,并对学生的行为予以警告、批评或纠正等,然后继续上课。我们把老师因为学生的某些行为而中止授课,并产生后续动作,之后接着上课的这个过程理解为中断或中断响应。我们把可能导致老师上课中断的学生行为理解为中断事件。

EXTI框图讲解

下面,我们结合EXTI框图理解上述的概念。

首先,我们对上述的整个框图有个大概的认识:

  • 绿色+红色部分:跟外设连接在一起。绿色部分用于和蓝色部分信息交互,红色部分用于传输事件信号。
  • 蓝色部分:寄存器组,用于控制事件信号的采集和走向。
  • 黄色部分:两个输出端,用于事件信号的输出,中断信号去NVIC,非中断信号用于其他外设(如DMA)

信号产生过程

下面我们分析下两者的产生过程:

如上图所示,首先会经过边沿检测电路,这个检测电路会通过查看上边沿触发和下边沿触发寄存器的值,去判断信号到底在哪个边沿采集。(所谓上边沿和下边沿,即电平从低->高或者高->低变化时的那条竖直边)

经过边沿检测电路之后,就来到上图中的3位置,这里是个或门连接着输入信号和软件中断事件寄存器,也就是说,除了信号触发中断之外,我们还可以通过控制相关的寄存器从而产生中断。

我们先来看看中断的产生过程:

首先中断请求信号会先进入请求挂起寄存器。这个寄存器的存在意义在于,如果中断发生时,正在处理同级或高优先级中断,则中断不能立即得到响应,此时中断被悬起。悬挂意味着等待而不是舍去,当优先级高的或者同等级的先发生的中断执行完之后,被挂起的中断才会执行。

落实到STM32这里,可以看到标号4的位置,是一个与门,所以中断请求什么时候推送到NVIC控制器执行,就是中断屏蔽寄存器应该控制的事了。

同理,对于事件来收,事件是否生成,就是事件屏蔽器的事了。不同点在于,事件的产生没有优先级的概念,所以无需挂起寄存器这么个东西。

代码示例:这里我实现的事按键中断控制LED灯的亮灭

编程要点

  • 初始化要用来产生中断的GPIO;
  • 初始化EXTI
  • 配置NVIC
  • 编写中断服务函数
#include "bsp_exit.h"
void LED_GPIO_Config(void)
{/*定义一个GPIO_InitTypeDef类型的结构体*/GPIO_InitTypeDef GPIO_InitStruct;/*打开GPIO对应的时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);/*设置为推挽输出*/GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;/*选择要控制的GPIO引脚*/GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_5 | GPIO_Pin_1;/*设置引脚输出速率*/GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);/*关闭所有的LED灯*/GPIOB->ODR |= 0x23;
}   /*NVIC的配置*/
static void NVIC_Config(void)
{ /*定义一个NVIC_InitTypeDef类型的结构体*/NVIC_InitTypeDef NVIC_InitStruct;/*配置NVIC为优先组1*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);/*配置中断源:按键1 */NVIC_InitStruct.NVIC_IRQChannel = KEY1_EXTI_INT_IRQ;/*配置抢占优先级 */NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;/*配置子优先级 */NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;/*使能中断通道 */NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;/*调用库函数,初始化NVIC*/NVIC_Init(&NVIC_InitStruct);/*配置中断源:按键2,其他使用上面一样的配置*/NVIC_InitStruct.NVIC_IRQChannel = KEY2_EXTI_INT_IRQ;NVIC_Init(&NVIC_InitStruct);}
/**@brief 配置 IO为EXTI中断口,并设置中断优先级*param  无*retval 无
*/void KEY_INT_Config(void)
{/*定义GPIO_InitTypeDef和EXTI_InitTypeDef类型结构体*/GPIO_InitTypeDef KEY_InitStruct;EXTI_InitTypeDef EXIT_InitStruct;//开启外设按键和外部中断EXTI的时钟,这里可以简化的,源代码非常好RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);RCC_APB2PeriphClockCmd(KEY1_INT_CLK, ENABLE);RCC_APB2PeriphClockCmd(KEY1_EXIT_INT_CLK,ENABLE);RCC_APB2PeriphClockCmd(KEY2_INT_CLK,ENABLE);/*配置NVIC中断*/NVIC_Config();/*————————KEY1-----------------配置*//*选择按键用到的GPIO引脚*/KEY_InitStruct.GPIO_Pin  = KEY1_INT_PIN;/*配置为浮空输入,浮空输入的意思为由外部电路决定初始电平信号*/KEY_InitStruct.GPIO_Mode =  GPIO_Mode_IN_FLOATING;GPIO_Init(KEY1_INT_PORT, &KEY_InitStruct);/*选择EXTI的信号源*/GPIO_EXTILineConfig(KEY1_EXIT_INT_PORTSource, KEY1_EXIT_INT_PINSource);EXIT_InitStruct.EXTI_Line = EXTI_Line0;//EXTI设置为中断模式EXIT_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//上升沿中断EXIT_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;//使能中断通道EXIT_InitStruct.EXTI_LineCmd = ENABLE;//外部中断EXTI的初始化EXTI_Init(&EXIT_InitStruct);/*---------------------KEY2配置--------------*//*选择按键用到的GPIO引脚*/KEY_InitStruct.GPIO_Pin = KEY2_INT_PIN;GPIO_Init(KEY2_INT_PORT, &KEY_InitStruct);/*选择EXTI的信号源*/GPIO_EXTILineConfig(KEY2_EXTI_INT_PORTSource, KEY2_EXTI_INT_PINSource);EXIT_InitStruct.EXTI_Line = KEY2_EXTI_INT_LINE;/*下降沿触发*/EXIT_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;/*EXTI为中断模式*/EXIT_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;/*时钟中断通道*/EXIT_InitStruct.EXTI_LineCmd = ENABLE;EXTI_Init(&EXIT_InitStruct);

中断服务函数:

void KEY1_EXTI_Handler(void)
{if(EXTI_GetITStatus(KEY1_EXIT_INT_LINE) != RESET ){LED_G_TOGGLE;  EXTI_ClearITPendingBit(KEY1_EXIT_INT_LINE);  //清除中断标志位}}void KEY2_EXTI_Handler(void)
{
if(EXTI_GetITStatus(KEY2_EXTI_INT_LINE) != RESET ){LED_R_TOGGLE;;EXTI_ClearITPendingBit(KEY2_EXTI_INT_LINE);}}

STM32——理解中断与中断配置相关推荐

  1. STM32的异常“、“中断”和“事件”区别和理解

    1 异常与中断(Cortex-M3) 1.1 异常与中断 原话:  Cortex‐M3 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断.  其中,编号为 1-15 的对应系统异常 ...

  2. STM32时钟系统和TIMER配置(溢出中断/PWM)实例

    目录: 1. STM32时钟系统 2. STM32的定时器典型配置之溢出中断 3. STM32的定时器典型配置之PWM输出 1. STM32时钟系统 (1)Clock tree 可以在官方手册(Stm ...

  3. hal库开启中断关中断_「正点原子NANO STM32开发板资料连载」第十章 外部中断实验...

    1)实验平台:ALIENTEK NANO STM32F411 V1开发板 2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第十章 外 ...

  4. hal库开启中断关中断_[STM32]HAL库下GPIO按键中断与去抖问题分析(分析源码解决问题)...

    自上篇文章STM32 非阻塞HAL_UART_ReceiveIT解析与实际应用,具体总结了HAL库下套娃函数中如何看清库函数的脉络,更细致的认识调用的过程,以解决潜在的问题.又又又遇到了新的问题(GP ...

  5. stm32中断优先级_STM的中断系统

    STM32的中断系统 STM32具有十分强大的中断系统,将中断分为了两个类型:内核异常和外部中断.并将所有中断通过一个表编排起来,下面是stm32中断向量表的部分内容: 上图-3到6这个区域被标黑了, ...

  6. stm32——外部中断及中断概念小讲(一)(初学者参考)

    (零基础请看本篇,有基础的直接跳) 下一篇内容会涉及外部中断的配置代码.中断处理函数的编写, 下下篇会围绕中断控制器展开,涉及"中断向量表"和"中断嵌套"的内容 ...

  7. STM32使用串口IDLE中断的两种接收不定长数据的方式

    现在有很多数据处理都要用到不定长数据,而单片机串口的RXNE中断一次只能接收一个字节的数据,没有缓冲区,无法接收一帧多个数据,现提供两种利用串口IDLE空闲中断的方式接收一帧数据,方法如下: 方法1: ...

  8. STM32 关于外部中断线、中断源和中断服务函数的问题

    STM32 关于外部中断线.中断源和中断服务函数的问题 中断线问题: 上图可以看出,PA0.PB0...PG0共用的EXTI0中断线,PA1.PB1...PG1共用的EXTI1中断线,也就是 编程里面 ...

  9. openmv串口数据 串口助手_STM32 串口接收不定长数据 STM32 USART空闲检测中断

    编者注: 单片机串口接收不定长数据时,必须面对的一个问题为:怎么判断这一包数据接收完成了呢?常见的方法主要有以下两种: 1.在接收数据时启动一个定时器,在指定时间间隔内没有接收到新数据,认为数据接收完 ...

最新文章

  1. python开发工程师面试题-一名python web后端开发工程师的面试总结
  2. C++ 类对象作为类成员
  3. 《复杂》读书笔记(part5)--复杂性度量
  4. 【前端工程师手册】JavaScript作用域拾遗
  5. 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复
  6. Java 中自定义时间格式
  7. 彩色静电植绒印花工艺的五个方法
  8. [转]唐骏谈职场 —— 管理者要学会让员工感动
  9. 力扣Java编译器_力扣(LeetCode)位1的个数 个人题解
  10. Rust : range,[],vec,array中元素的类别
  11. 数据库系统概论第五版(第 1 章 绪论)习题答案
  12. 常见网络安全威胁及防范
  13. Halcon教程十二:回形针识别进阶
  14. 阿里海量大数据平台的运维智能化实践
  15. 微信小程序实现选项卡切换功能
  16. matlab所有颜色,MATLAB 颜色选择及应用
  17. 前端工作中碰到的一些小问题总结
  18. java 断点跳到注释,一个空指针异常,代码如下,打了断点,一到“TOPICID”那里(在下方注释4那里)就抛异常-_-||...
  19. 不会汇报工作,还敢拼职场
  20. android录音笔记软件,录音笔记app

热门文章

  1. (20200208已解决)PyCharm中Tab键无效
  2. 计算机考研abc区划分,研究生地区分类-考研ABC区域的划分考研ABC区域的划分, – 手机爱问...
  3. python循环语句打印三角形_python循环输出三角形图案的例子
  4. lol无限乱斗服务器,LOL无线乱斗时间2019
  5. ARM实验板移植Linux操作系统,LCD显示汉字(名字)
  6. Java中的多线程安全问题
  7. java浅谈线程安全之锁
  8. TreeMap与TreeSet(初步了解)
  9. rua噗实验(rip实验)
  10. gaussdb200 数据导入