1、中断的概念
概念:
程序执行过程中CPU会遇到一些特殊情况,是正在执行的程序被“中断”,cpu中止原来正在执行的程序,转到处理异常情况或特殊事件的程序去执行,结束后再返回到原被中止的程序处(断点)继续执行。
2、NVIC
NVIC 是嵌套向量中断控制器
特性:
- 68个可屏蔽中断通道(不包含16个Cortex™-M3的中断线);
- 16个可编程的优先等级(使用了4位中断优先级);
- 低延迟的异常和中断处理;
- 电源管理控制;
- 系统控制寄存器的实现;
它控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设,可以实现低延迟的中断处理和高效地处理晚到的中断,但是各个芯片厂商在设计芯片的时候会对Cortex-M3内核里面的NVIC进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC是 Cortex-M3 的 NVIC 的阉割版。
嵌套向量中断控制器管理着包括内核异常等中断。更多关于异常和NVIC编程的说明请参考 《STM32F10xxx Cortex-M3编程手册》
NVIC 结构体定义,来自固件库头文件:core_cm3.h
1 typedef struct { 2 __IO uint32_t ISER[8]; // 中断使能寄存器 3 uint32_t RESERVED0[24]; 4 __IO uint32_t ICER[8]; // 中断清除寄存器 5 uint32_t RSERVED1[24]; 6 __IO uint32_t ISPR[8]; // 中断使能悬起寄存器 7 uint32_t RESERVED2[24]; 8 __IO uint32_t ICPR[8]; // 中断清除悬起寄存器 9 uint32_t RESERVED3[24]; 10 __IO uint32_t IABR[8]; // 中断有效位寄存器 11 uint32_t RESERVED4[56]; 12 __IO uint8_t IP[240]; // 中断优先级寄存器(8Bit wide) 13 uint32_t RESERVED5[644]; 14 __O uint32_t STIR; // 软件触发中断寄存器 15 } NVIC_Type;
在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能(开中断)中断,ICER 用来失能(清楚某个中断的使能)中断,IP 用来设置中断优先级。
固件库文件 core_cm3.h 的最后,还提供了 NVIC 的一些函数
void NVIC_EnableIRQ(IRQn_Type IRQn) // 使能中断 void NVIC_DisableIRQ(IRQn_Type IRQn) // 失能中断 void NVIC_SetPendingIRQ(IRQn_Type IRQn) // 设置中断悬起位 void NVIC_ClearPendingIRQ(IRQn_Type IRQn) // 清除中断悬起位 uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) // 获取悬起中断编号 void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) // 设置中断优先级 uint32_t NVIC_GetPriority(IRQn_Type IRQn) // 获取中断优先级 void NVIC_SystemReset(void) // 系统复位
优先级的定义
在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的 优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数减少,在 F103 中,只使用了高 4bit,如下所示:
用于表达优先级的这 4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时 响应,抢占优先级高的就会 抢占 抢占优先级低的优先得到执行,如果抢占优先级相同,就 比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
优先级分组
优先级的分组由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的 PRIGROUP[10:8]位决定,F103 分为了 5 组,具体如下:主优先级=抢占优先级
设置优先级分组可调用库函数 NVIC_PriorityGroupConfig()实现,有关 NVIC 中断相关 的库函数都在库文件 misc.c 和 misc.h 中
1 /** 2 * 配置中断优先级分组:抢占优先级和子优先级 3 * 形参如下: 4 * @arg NVIC_PriorityGroup_0: 0bit for 抢占优先级 5 * 4 bits for 子优先级 6 * @arg NVIC_PriorityGroup_1: 1 bit for 抢占优先级 7 * 3 bits for 子优先级 8 * @arg NVIC_PriorityGroup_2: 2 bit for 抢占优先级 9 * 2 bits for 子优先级 10 * @arg NVIC_PriorityGroup_3: 3 bit for 抢占优先级 11 * 1 bits for 子优先级 12 * @arg NVIC_PriorityGroup_4: 4 bit for 抢占优先级 13 * 0 bits for 子优先级 14 * @注意 如果优先级分组为 0,则抢占优先级就不存在,优先级就全部由子优先级控制 15 */ 16 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) 17 { 18 // 设置优先级分组 19 SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; 20 }
中断编程
在配置每个中断的时候一般有 3 个编程要点:
1、使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送 完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
2、初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优 先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。
1 typedef struct { 2 uint8_t NVIC_IRQChannel; // 中断源 3 uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级 4 uint8_t NVIC_IRQChannelSubPriority; // 子优先级 5 FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能 6 } NVIC_InitTypeDef;
- NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即 使写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考 stm32f10x.h 头文 件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
- NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来 确定。
- NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定。
- NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的 是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
3、编写中断服务函数 在启动文件 startup_stm32f10x_hd.s 中我们预先为每个中断都写了一个中断服务函数, 只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我 们重新编写,为了方便管理我们把中断服务函数统一写在 stm32f10x_it.c 这个库文件中。 关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就 在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数, 并且在里面无限循环,实现不了中断。
3、EXTI
EXTI简介:
EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿 检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为 中断或者事件,以及触发事件的属性。
EXTI框图:
输入线:EXTI 控制器有 20 个中断/事件输入线,这些输入线可以通过寄存器 设置为任意一个 GPIO
通过这四个AFIO寄存器来选择具体使用哪一个GPIO的哪一个引脚作为输入线:
通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。对于小容量、中容量和大容量 的产品,参见6.3.7 137/754 节;对于互联型产品,参见7.3.7节
另外四个EXTI线的连接方式如下:
- EXTI线16连接到PVD输出
- EXTI线17连接到RTC闹钟事件
- EXTI线18连接到USB唤醒事件
- EXTI线19连接到以太网唤醒事件(只适用于互联型产品)
边沿检测电路:它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿 触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过 程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
或门电路:它一个输入来边沿检测电路,另外一个输入来自 软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。
与门电路:它一个输入是或门电路,另外一个输入来自中断屏蔽 寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管或门电路的输出信号是 1 还是 0,最终与门 电路输出的信号都为 0; 如果 EXTI_IMR 设置为 1 时,最终与门电路输出的信号才由或门 电路的输出信号决定, 这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。与门电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定与门 电路输出为 1 就会把 EXTI_PR 对应位置 1。
最后将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
EXTI初始化结构:
1 typedef struct { 2 uint32_t EXTI_Line; // 中断/事件线 3 EXTIMode_TypeDef EXTI_Mode; // EXTI 模式 4 EXTITrigger_TypeDef EXTI_Trigger; // 触发类型 5 FunctionalState EXTI_LineCmd; // EXTI 使能 6 } EXTI_InitTypeDef;
1) EXTI_Line:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,通过前面的学习我们知道,输入线是通过AFIO来选择的,所以这里的中断/事件线是为了编程寄存器中哪一个位,而不是选择哪一条线。
2) EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事 件(EXTI_Mode_Event)。
3) EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下 降沿触发 ( EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
4) EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用 (DISABLE)。
4、示例代码
PC8连接到EXTI用于产生中断,PC8 的电平变化通过按键来控制,产生一次中断,led反转一次。
配置按键所用的GPIO引脚
//配置中断优先级,主要在NVIC内核中断中配置 void EXTI_NVIC_Config(void){ NVIC_InitTypeDef NVIC_InitStruct; // 分组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //配置优先级 NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;//这个要注意,参数在stm32f10x.h中找,且0-4,5-9,10-15对应的参数不同 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; //子优先级 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); }
key按键原理图。
void EXTI_Key_Config(void){ GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; // 1、初始化GPIO RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//上拉输入 GPIO_Init(GPIOC, &GPIO_InitStruct); // 配置优先级 EXTI_NVIC_Config(); // 2、中断配置 //AFIO时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //选择输入线,因为输入线的选择是在AFIO_EXTICRX中进行,所以应当首先打开AFIO的时钟 GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource8); //初始化EXTI EXTI_InitStruct.EXTI_Line = EXTI_Line8;//中断屏蔽寄存器上面的位操作 EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式 EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发 EXTI_InitStruct.EXTI_LineCmd = ENABLE; //使能 EXTI_Init(&EXTI_InitStruct); }
初始化Led所在的GPIO
void Led_GPIO_Config(void){ GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); }
配置中断函数
void EXTI9_5_IRQHandler(void)// 在startup_stm32f10x_hd.s 中我们找提前写好的中断函数名。
{
//确保是否产生了中断
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
//需要的操作
//别放置耗时间的操作
}
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line8);
}
参考b站野火stm32教程。
标签:优先级,中断,NVIC,详解,寄存器,GPIO,stm32F0,EXTI From: https://www.cnblogs.com/wangkehui/p/17215907.html